plum

#treesitter#compiler#wasm

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

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


docs/superpowers/plans/2026-07-24-bracket-generics-syntax.md
13fd165 1
# Bracket Generics Syntax Migration Implementation Plan
13fd165 2
13fd165 3
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
13fd165 4
13fd165 5
**Goal:** Migrate Plum's generic-type syntax from parenthesized/lowercase (`type Foo(a) =`, `Option(a)`) to bracketed/uppercase (`type Foo[T] =`, `Option[T]`) across the grammar, parser, checker, stdlib, and examples — a pure syntax migration with no semantic changes.
13fd165 6
13fd165 7
**Architecture:** Bottom-up through the compiler pipeline: grammar (tree-sitter) → AST/parser (`plum-core`) → monomorphization convention (`plum-checker`) → downstream `.name`-reader call sites (`plum-checker`/`plum-wasm-codegen`) → Rust test fixtures → `.plum` source files (stdlib, examples). Each layer is verified before moving to the next, since later layers depend on earlier ones compiling/parsing correctly.
13fd165 8
13fd165 9
**Tech Stack:** tree-sitter (JS grammar + generated Rust/C parser), Rust (`plum-core`, `plum-checker`, `plum-wasm-codegen`), Cargo workspace tests.
13fd165 10
13fd165 11
## Global Constraints
13fd165 12
13fd165 13
- Spec: `docs/superpowers/specs/2026-07-24-bracket-generics-syntax-design.md`
13fd165 14
- `generic` becomes any single uppercase ASCII letter (`/[A-Z]/`); `type_identifier` becomes 2+ characters (`/[A-Z][a-zA-Z0-9]+/`) — single-letter type names are now permanently illegal.
13fd165 15
- Declaration order: generics-with-bounds first, implements-list second — `type List[T: Stringable](Stringable) =`.
13fd165 16
- Every generic-type appearance uses brackets: declarations, field types, return types, enum variant payloads (`| Some[T]`). Parens stay for value-level constructor/call argument lists and trait "implements" lists.
13fd165 17
- Method receiver annotation (`get<List>(self, ...)`) is untouched — it's an unrelated mechanism.
13fd165 18
- `is_generic_param_name` (the sole convention-detection function, in `plum-checker/src/monomorphize.rs`) flips from lowercase to uppercase; this is the only place the letter-case convention is defined.
13fd165 19
- `ReturnType` (a separate, narrower AST shape than `Type`) is removed; every `returns` field becomes `Option<ast::Type>`, fixing the pre-existing `Type`/`ReturnType` asymmetry as part of this migration.
13fd165 20
- The untracked plan `docs/superpowers/plans/2026-07-24-list-methods.md` is written in the OLD syntax and is **not** touched by this plan — it is blocked/stale and needs its own rebase before anyone executes it. Do not edit it here.
13fd165 21
- Run `cargo test --workspace` after every task from Task 2 onward — expect it to start failing once the grammar changes land (Task 1), and to be fully green again only after Task 5.
13fd165 22
- Letter conventions used in stdlib rewrites (Task 6): `List`/`Node` → `T` (map's callback output → `U`); `Map`/`Pair` → `K`, `V` (map's callback output pair → `X`, `Y`); `Option` → `T`; `Result` → `T` (Ok), `E` (Err); `examples/types.plum`'s `Box` → `T`, `Comparable` → `T`.
13fd165 23
13fd165 24
---
13fd165 25
13fd165 26
### Task 1: Grammar changes and corpus tests (`tooling/tree-sitter-plum`)
13fd165 27
13fd165 28
**Files:**
13fd165 29
- Modify: `tooling/tree-sitter-plum/grammar.js`
13fd165 30
- Modify: `tooling/tree-sitter-plum/test/corpus/type.txt`
13fd165 31
- Modify: `tooling/tree-sitter-plum/test/corpus/trait.txt`
13fd165 32
- Modify: `tooling/tree-sitter-plum/test/corpus/enum.txt`
13fd165 33
- Modify: `tooling/tree-sitter-plum/test/corpus/function.txt`
13fd165 34
13fd165 35
**Interfaces:**
13fd165 36
- Consumes: nothing (this is the bottom of the pipeline).
13fd165 37
- Produces: a regenerated tree-sitter parser where `generic` is `/[A-Z]/` (any single uppercase letter, node kind `"generic"`), `type_identifier` is `/[A-Z][a-zA-Z0-9]+/` (2+ chars), `generics` declarations use `[...]`, `class`/`trait` generics come before implements, `enum_field` payloads use `[...]`, and `return_type` no longer exists as its own rule — return positions parse as plain `$.type` (node kind `"type"`, not `"return_type"`).
13fd165 38
13fd165 39
- [ ] **Step 1: Edit `grammar.js`'s generic/type-identifier tokens and the `generics` bracket**
13fd165 40
13fd165 41
In `tooling/tree-sitter-plum/grammar.js`, replace:
13fd165 42
13fd165 43
```js
13fd165 44
  inline: ($) => [$.generic_type, $.generic],
13fd165 45
```
13fd165 46
13fd165 47
with:
13fd165 48
13fd165 49
```js
13fd165 50
  inline: ($) => [$.generic_type],
13fd165 51
```
13fd165 52
13fd165 53
(`$.generic` is now a genuine terminal token — a leaf rule can't meaningfully be "inlined," since there's no substructure to hoist. `$.generic_type` stays inlined so `generics`' children remain flat, matching the existing parser convention.)
13fd165 54
13fd165 55
Replace:
13fd165 56
13fd165 57
```js
13fd165 58
    generics: ($) => seq("(", commaSep1($.generic_type), ")"),
13fd165 59
```
13fd165 60
13fd165 61
with:
13fd165 62
13fd165 63
```js
13fd165 64
    generics: ($) => seq("[", commaSep1($.generic_type), "]"),
13fd165 65
```
13fd165 66
13fd165 67
Replace:
13fd165 68
13fd165 69
```js
13fd165 70
    type: ($) =>
13fd165 71
      choice(
13fd165 72
        seq(
13fd165 73
          $.type_identifier,
13fd165 74
          field(
13fd165 75
            "generics",
13fd165 76
            optional(
13fd165 77
              choice(
13fd165 78
                seq("[", commaSep1($.type), "]"),
13fd165 79
                seq("(", commaSep1($.type), ")"),
13fd165 80
              ),
13fd165 81
            ),
13fd165 82
          ),
13fd165 83
        ),
13fd165 84
        $.generic,
13fd165 85
      ),
13fd165 86
```
13fd165 87
13fd165 88
with:
13fd165 89
13fd165 90
```js
13fd165 91
    type: ($) =>
13fd165 92
      choice(
13fd165 93
        seq(
13fd165 94
          $.type_identifier,
13fd165 95
          field(
13fd165 96
            "generics",
13fd165 97
            optional(seq("[", commaSep1($.type), "]")),
13fd165 98
          ),
13fd165 99
        ),
13fd165 100
        $.generic,
13fd165 101
      ),
13fd165 102
```
13fd165 103
13fd165 104
- [ ] **Step 2: Swap `class`'s field order (generics before implements) and drop `return_type`**
13fd165 105
13fd165 106
Replace:
13fd165 107
13fd165 108
```js
13fd165 109
    class: ($) =>
13fd165 110
      seq(
13fd165 111
        "type",
13fd165 112
        field("name", $.type_identifier),
13fd165 113
        field("implements", optional(seq("(", commaSep1($.type_identifier), ")"))),
13fd165 114
        field("generics", optional($.generics)),
13fd165 115
        "=",
13fd165 116
        $._indent,
13fd165 117
        field("fields", optional(repeat(alias($.class_field, $.field)))),
13fd165 118
        $._dedent,
13fd165 119
      ),
13fd165 120
```
13fd165 121
13fd165 122
with:
13fd165 123
13fd165 124
```js
13fd165 125
    class: ($) =>
13fd165 126
      seq(
13fd165 127
        "type",
13fd165 128
        field("name", $.type_identifier),
13fd165 129
        field("generics", optional($.generics)),
13fd165 130
        field("implements", optional(seq("(", commaSep1($.type_identifier), ")"))),
13fd165 131
        "=",
13fd165 132
        $._indent,
13fd165 133
        field("fields", optional(repeat(alias($.class_field, $.field)))),
13fd165 134
        $._dedent,
13fd165 135
      ),
13fd165 136
```
13fd165 137
13fd165 138
Replace:
13fd165 139
13fd165 140
```js
13fd165 141
    trait_field: ($) =>
13fd165 142
      seq(
13fd165 143
        field("name", $.fn_identifier),
13fd165 144
        field("params", seq("(", optional(commaSep1(choice($.self, $.param))), ")")),
13fd165 145
        field("returns", optional(seq("->", $.return_type))),
13fd165 146
      ),
13fd165 147
```
13fd165 148
13fd165 149
with:
13fd165 150
13fd165 151
```js
13fd165 152
    trait_field: ($) =>
13fd165 153
      seq(
13fd165 154
        field("name", $.fn_identifier),
13fd165 155
        field("params", seq("(", optional(commaSep1(choice($.self, $.param))), ")")),
13fd165 156
        field("returns", optional(seq("->", $.type))),
13fd165 157
      ),
13fd165 158
```
13fd165 159
13fd165 160
Delete this rule entirely (return positions now parse as plain `$.type`):
13fd165 161
13fd165 162
```js
13fd165 163
    return_type: ($) =>
13fd165 164
      seq($.type_identifier, field("generics", optional($.generics))),
13fd165 165
```
13fd165 166
13fd165 167
Replace, in `enum_field`:
13fd165 168
13fd165 169
```js
13fd165 170
    enum_field: ($) =>
13fd165 171
      seq(
13fd165 172
        "|",
13fd165 173
        field("name", $.type_identifier),
13fd165 174
        field("parameters", optional(seq("(", commaSep1(choice($.type_identifier, $.generic)), ")"))),
13fd165 175
      ),
13fd165 176
```
13fd165 177
13fd165 178
with:
13fd165 179
13fd165 180
```js
13fd165 181
    enum_field: ($) =>
13fd165 182
      seq(
13fd165 183
        "|",
13fd165 184
        field("name", $.type_identifier),
13fd165 185
        field("parameters", optional(seq("[", commaSep1(choice($.type_identifier, $.generic)), "]"))),
13fd165 186
      ),
13fd165 187
```
13fd165 188
13fd165 189
Replace, in `fn`:
13fd165 190
13fd165 191
```js
13fd165 192
          field("returns", optional(seq("->", $.return_type))),
13fd165 193
```
13fd165 194
13fd165 195
with:
13fd165 196
13fd165 197
```js
13fd165 198
          field("returns", optional(seq("->", $.type))),
13fd165 199
```
13fd165 200
13fd165 201
- [ ] **Step 3: Replace the hardcoded 4-letter `generic` rule with a single regex token**
13fd165 202
13fd165 203
Replace:
13fd165 204
13fd165 205
```js
13fd165 206
    generic: ($) => choice($.a, $.b, $.c, $.d), // single letter
13fd165 207
    a: (_) => token("a"),
13fd165 208
    b: (_) => token("b"),
13fd165 209
    c: (_) => token("c"),
13fd165 210
    d: (_) => token("d"),
13fd165 211
```
13fd165 212
13fd165 213
with:
13fd165 214
13fd165 215
```js
13fd165 216
    generic: (_) => /[A-Z]/, // any single uppercase letter — reserved, illegal as a type_identifier
13fd165 217
```
13fd165 218
13fd165 219
Replace:
13fd165 220
13fd165 221
```js
13fd165 222
    type_identifier: (_) => /[A-Z][a-zA-Z0-9]*/, // capital case
13fd165 223
```
13fd165 224
13fd165 225
with:
13fd165 226
13fd165 227
```js
13fd165 228
    type_identifier: (_) => /[A-Z][a-zA-Z0-9]+/, // capital case, 2+ chars (single uppercase letters are reserved for `generic`)
13fd165 229
```
13fd165 230
13fd165 231
- [ ] **Step 4: Regenerate the parser**
13fd165 232
13fd165 233
Run: `cd tooling/tree-sitter-plum && npx tree-sitter generate`
13fd165 234
Expected: succeeds with no grammar conflicts reported. If tree-sitter reports a conflict between `generic` and `type_identifier`, stop and report BLOCKED — it would mean the 2-char minimum on `type_identifier` didn't fully disambiguate the two tokens, contradicting this plan's core assumption from the design spec.
13fd165 235
13fd165 236
- [ ] **Step 5: Update `test/corpus/type.txt`**
13fd165 237
13fd165 238
Replace the entire file contents with:
13fd165 239
13fd165 240
```
13fd165 241
================================================================================
13fd165 242
type
13fd165 243
================================================================================
13fd165 244
13fd165 245
type Dog =
13fd165 246
  name: Str
13fd165 247
  age: B
13fd165 248
13fd165 249
type Cat(Stringable) =
13fd165 250
  name: Str
13fd165 251
  age: Int
13fd165 252
13fd165 253
init<Cat>(name: Str) -> Cat =
13fd165 254
  Cat(name: name, age: 0)
13fd165 255
13fd165 256
withName<Cat>(name: Str) -> Cat =
13fd165 257
  Cat(name: name, age: 0)
13fd165 258
13fd165 259
withAge<Cat>(age: Int) -> Cat =
13fd165 260
  Cat(name: "", age: age)
13fd165 261
13fd165 262
toStr<Cat>() -> Str =
13fd165 263
  "Cat({self.name}, {self.age})"
13fd165 264
13fd165 265
--------------------------------------------------------------------------------
13fd165 266
13fd165 267
(source
13fd165 268
  (class
13fd165 269
    (type_identifier)
13fd165 270
    (field
13fd165 271
      (var_identifier)
13fd165 272
      (type
13fd165 273
        (type_identifier)))
13fd165 274
    (field
13fd165 275
      (var_identifier)
13fd165 276
      (type
13fd165 277
        (generic))))
13fd165 278
  (class
13fd165 279
    (type_identifier)
13fd165 280
    (type_identifier)
13fd165 281
    (field
13fd165 282
      (var_identifier)
13fd165 283
      (type
13fd165 284
        (type_identifier)))
13fd165 285
    (field
13fd165 286
      (var_identifier)
13fd165 287
      (type
13fd165 288
        (type_identifier))))
13fd165 289
  (fn
13fd165 290
    (fn_identifier)
13fd165 291
    (type
13fd165 292
      (type_identifier))
13fd165 293
    (param
13fd165 294
      (var_identifier)
13fd165 295
      (type
13fd165 296
        (type_identifier)))
13fd165 297
    (type
13fd165 298
      (type_identifier))
13fd165 299
    (body
13fd165 300
      (expression
13fd165 301
        (primary_expression
13fd165 302
          (class_call
13fd165 303
            (type_identifier)
13fd165 304
            (class_argument_list
13fd165 305
              (var_identifier)
13fd165 306
              (expression
13fd165 307
                (primary_expression
13fd165 308
                  (var_identifier)))
13fd165 309
              (var_identifier)
13fd165 310
              (expression
13fd165 311
                (primary_expression
13fd165 312
                  (integer)))))))))
13fd165 313
  (fn
13fd165 314
    (fn_identifier)
13fd165 315
    (type
13fd165 316
      (type_identifier))
13fd165 317
    (param
13fd165 318
      (var_identifier)
13fd165 319
      (type
13fd165 320
        (type_identifier)))
13fd165 321
    (type
13fd165 322
      (type_identifier))
13fd165 323
    (body
13fd165 324
      (expression
13fd165 325
        (primary_expression
13fd165 326
          (class_call
13fd165 327
            (type_identifier)
13fd165 328
            (class_argument_list
13fd165 329
              (var_identifier)
13fd165 330
              (expression
13fd165 331
                (primary_expression
13fd165 332
                  (var_identifier)))
13fd165 333
              (var_identifier)
13fd165 334
              (expression
13fd165 335
                (primary_expression
13fd165 336
                  (integer)))))))))
13fd165 337
  (fn
13fd165 338
    (fn_identifier)
13fd165 339
    (type
13fd165 340
      (type_identifier))
13fd165 341
    (param
13fd165 342
      (var_identifier)
13fd165 343
      (type
13fd165 344
        (type_identifier)))
13fd165 345
    (type
13fd165 346
      (type_identifier))
13fd165 347
    (body
13fd165 348
      (expression
13fd165 349
        (primary_expression
13fd165 350
          (class_call
13fd165 351
            (type_identifier)
13fd165 352
            (class_argument_list
13fd165 353
              (var_identifier)
13fd165 354
              (expression
13fd165 355
                (primary_expression
13fd165 356
                  (string
13fd165 357
                    (string_start)
13fd165 358
                    (string_end))))
13fd165 359
              (var_identifier)
13fd165 360
              (expression
13fd165 361
                (primary_expression
13fd165 362
                  (var_identifier)))))))))
13fd165 363
  (fn
13fd165 364
    (fn_identifier)
13fd165 365
    (type
13fd165 366
      (type_identifier))
13fd165 367
    (type
13fd165 368
      (type_identifier))
13fd165 369
    (body
13fd165 370
      (expression
13fd165 371
        (primary_expression
13fd165 372
          (string
13fd165 373
            (string_start)
13fd165 374
            (string_content)
13fd165 375
            (interpolation
13fd165 376
              (primary_expression
13fd165 377
                (attribute
13fd165 378
                  (primary_expression
13fd165 379
                    (self))
13fd165 380
                  (fn_identifier))))
13fd165 381
            (string_content)
13fd165 382
            (interpolation
13fd165 383
              (primary_expression
13fd165 384
                (attribute
13fd165 385
                  (primary_expression
13fd165 386
                    (self))
13fd165 387
                  (fn_identifier))))
13fd165 388
            (string_content)
13fd165 389
            (string_end)))))))
13fd165 390
```
13fd165 391
13fd165 392
- [ ] **Step 6: Update `test/corpus/trait.txt`**
13fd165 393
13fd165 394
This file has no generics and only plain `-> Str`/`-> Int` return types, so the only change is `return_type` → `type` in the expected tree. Replace the expected-tree section (everything after the `---` divider) with:
13fd165 395
13fd165 396
```
13fd165 397
(source
13fd165 398
  (trait
13fd165 399
    (type_identifier)
13fd165 400
    (field
13fd165 401
      (fn_identifier)
13fd165 402
      (type
13fd165 403
        (type_identifier))))
13fd165 404
  (trait
13fd165 405
    (type_identifier)
13fd165 406
    (field
13fd165 407
      (fn_identifier)
13fd165 408
      (type
13fd165 409
        (type_identifier)))
13fd165 410
    (field
13fd165 411
      (fn_identifier)
13fd165 412
      (type
13fd165 413
        (type_identifier)))
13fd165 414
    (field
13fd165 415
      (fn_identifier))
13fd165 416
    (field
13fd165 417
      (fn_identifier)
13fd165 418
      (param
13fd165 419
        (var_identifier)
13fd165 420
        (type
13fd165 421
          (type_identifier)))
13fd165 422
      (type
13fd165 423
        (type_identifier)))))
13fd165 424
```
13fd165 425
13fd165 426
(The source section above the divider is unchanged — no generics appear in this file's source.)
13fd165 427
13fd165 428
- [ ] **Step 7: Update `test/corpus/enum.txt`**
13fd165 429
13fd165 430
Replace the entire file contents with:
13fd165 431
13fd165 432
```
13fd165 433
================================================================================
13fd165 434
enum
13fd165 435
================================================================================
13fd165 436
13fd165 437
enum Bool =
13fd165 438
  | True
13fd165 439
  | False
13fd165 440
13fd165 441
toStr<Bool>() -> Str =
13fd165 442
  "Bool"
13fd165 443
13fd165 444
--------------------------------------------------------------------------------
13fd165 445
13fd165 446
(source
13fd165 447
  (enum
13fd165 448
    (type_identifier)
13fd165 449
    (field
13fd165 450
      (type_identifier))
13fd165 451
    (field
13fd165 452
      (type_identifier)))
13fd165 453
  (fn
13fd165 454
    (fn_identifier)
13fd165 455
    (type
13fd165 456
      (type_identifier))
13fd165 457
    (type
13fd165 458
      (type_identifier))
13fd165 459
    (body
13fd165 460
      (expression
13fd165 461
        (primary_expression
13fd165 462
          (string
13fd165 463
            (string_start)
13fd165 464
            (string_content)
13fd165 465
            (string_end)))))))
13fd165 466
13fd165 467
================================================================================
13fd165 468
enum - generic variant fields
13fd165 469
================================================================================
13fd165 470
13fd165 471
enum Option =
13fd165 472
  | Some[T]
13fd165 473
  | None
13fd165 474
13fd165 475
--------------------------------------------------------------------------------
13fd165 476
13fd165 477
(source
13fd165 478
  (enum
13fd165 479
    (type_identifier)
13fd165 480
    (field
13fd165 481
      (type_identifier)
13fd165 482
      (generic))
13fd165 483
    (field
13fd165 484
      (type_identifier))))
13fd165 485
```
13fd165 486
13fd165 487
- [ ] **Step 8: Update `test/corpus/function.txt`**
13fd165 488
13fd165 489
First, apply this mechanical substitution across the whole file — every remaining `(return_type` line (the ones NOT part of the "function - generics" test touched in the next step) becomes `(type`, with indentation untouched:
13fd165 490
13fd165 491
Run:
13fd165 492
```bash
13fd165 493
cd tooling/tree-sitter-plum/test/corpus
13fd165 494
sed -i '' 's/(return_type/(type/' function.txt
13fd165 495
```
13fd165 496
13fd165 497
Then hand-fix the two tests whose *source* text (not just the expected tree) also changes. Find the block starting `function - generics` and replace its source + expected tree:
13fd165 498
13fd165 499
```
13fd165 500
================================================================================
13fd165 501
function - generics
13fd165 502
================================================================================
13fd165 503
13fd165 504
add(param: T, param2: List[U]) -> List[U] =
13fd165 505
  todo
13fd165 506
13fd165 507
--------------------------------------------------------------------------------
13fd165 508
13fd165 509
(source
13fd165 510
  (fn
13fd165 511
    (fn_identifier)
13fd165 512
    (param
13fd165 513
      (var_identifier)
13fd165 514
      (type
13fd165 515
        (generic)))
13fd165 516
    (param
13fd165 517
      (var_identifier)
13fd165 518
      (type
13fd165 519
        (type_identifier)
13fd165 520
        (type
13fd165 521
          (generic))))
13fd165 522
    (type
13fd165 523
      (type_identifier)
13fd165 524
      (type
13fd165 525
        (generic)))
13fd165 526
    (body
13fd165 527
      (todo))))
13fd165 528
```
13fd165 529
13fd165 530
Then find the block starting `function - method with explicit self param` and replace its source + expected tree:
13fd165 531
13fd165 532
```
13fd165 533
================================================================================
13fd165 534
function - method with explicit self param
13fd165 535
================================================================================
13fd165 536
13fd165 537
remove<List>(self, v: T) =
13fd165 538
  todo
13fd165 539
13fd165 540
--------------------------------------------------------------------------------
13fd165 541
13fd165 542
(source
13fd165 543
  (fn
13fd165 544
    (fn_identifier)
13fd165 545
    (type
13fd165 546
      (type_identifier))
13fd165 547
    (self)
13fd165 548
    (param
13fd165 549
      (var_identifier)
13fd165 550
      (type
13fd165 551
        (generic)))
13fd165 552
    (body
13fd165 553
      (todo))))
13fd165 554
```
13fd165 555
13fd165 556
- [ ] **Step 9: Run the corpus tests**
13fd165 557
13fd165 558
Run: `cd tooling/tree-sitter-plum && npx tree-sitter test 2>&1 | tail -100`
13fd165 559
Expected: all tests PASS. If any test other than `type`, `trait`, `enum`, `function` fails, inspect its diff — it likely means a `return_type` occurrence was missed elsewhere, or the sed substitution touched something unintended.
13fd165 560
13fd165 561
- [ ] **Step 10: Commit**
13fd165 562
13fd165 563
```bash
13fd165 564
git add tooling/tree-sitter-plum/grammar.js tooling/tree-sitter-plum/src tooling/tree-sitter-plum/test/corpus/type.txt tooling/tree-sitter-plum/test/corpus/trait.txt tooling/tree-sitter-plum/test/corpus/enum.txt tooling/tree-sitter-plum/test/corpus/function.txt
13fd165 565
git commit -m "feat(grammar): migrate generics to bracket syntax with uppercase letters"
13fd165 566
```
13fd165 567
13fd165 568
(`tooling/tree-sitter-plum/src` covers the regenerated `parser.c`/`grammar.json`/etc. produced by Step 4 — include whatever `tree-sitter generate` wrote there.)
13fd165 569
13fd165 570
---
13fd165 571
13fd165 572
### Task 2: `plum-core` — AST and parser updates
13fd165 573
13fd165 574
**Files:**
13fd165 575
- Modify: `plum-core/src/ast.rs`
13fd165 576
- Modify: `plum-core/src/parser.rs`
13fd165 577
13fd165 578
**Interfaces:**
13fd165 579
- Consumes: the regenerated grammar from Task 1 (node kinds `"generic"`, `"generics"`, `"type"` — no more `"return_type"`, `"a"`/`"b"`/`"c"`/`"d"`).
13fd165 580
- Produces: `TraitMethod.returns: Option<Type>`, `Fn.returns: Option<Type>` (was `Option<ReturnType>`); `parse_class`, `parse_generics_field`, `parse_enum_variant`, `parse_trait_method`, `parse_fn` updated to match the new grammar shape; `parse_return_type` removed entirely.
13fd165 581
13fd165 582
- [ ] **Step 1: Remove `ReturnType` from `ast.rs`, retype `returns` fields**
13fd165 583
13fd165 584
In `plum-core/src/ast.rs`, replace:
13fd165 585
13fd165 586
```rust
13fd165 587
#[derive(Debug, Clone, PartialEq)]
13fd165 588
pub struct TraitMethod {
13fd165 589
    pub name: String,
13fd165 590
    pub params: Vec<Param>,
13fd165 591
    pub returns: Option<ReturnType>,
13fd165 592
}
13fd165 593
```
13fd165 594
13fd165 595
with:
13fd165 596
13fd165 597
```rust
13fd165 598
#[derive(Debug, Clone, PartialEq)]
13fd165 599
pub struct TraitMethod {
13fd165 600
    pub name: String,
13fd165 601
    pub params: Vec<Param>,
13fd165 602
    pub returns: Option<Type>,
13fd165 603
}
13fd165 604
```
13fd165 605
13fd165 606
Replace:
13fd165 607
13fd165 608
```rust
13fd165 609
#[derive(Debug, Clone, PartialEq)]
13fd165 610
pub struct Fn {
13fd165 611
    pub name: String,
13fd165 612
    /// Type parameter for method dispatch, e.g. `<Cat>` in `toStr<Cat>()`
13fd165 613
    pub type_param: Option<String>,
13fd165 614
    pub params: Vec<Param>,
13fd165 615
    pub returns: Option<ReturnType>,
13fd165 616
    pub body: FnBody,
13fd165 617
}
13fd165 618
```
13fd165 619
13fd165 620
with:
13fd165 621
13fd165 622
```rust
13fd165 623
#[derive(Debug, Clone, PartialEq)]
13fd165 624
pub struct Fn {
13fd165 625
    pub name: String,
13fd165 626
    /// Type parameter for method dispatch, e.g. `<Cat>` in `toStr<Cat>()`
13fd165 627
    pub type_param: Option<String>,
13fd165 628
    pub params: Vec<Param>,
13fd165 629
    pub returns: Option<Type>,
13fd165 630
    pub body: FnBody,
13fd165 631
}
13fd165 632
```
13fd165 633
13fd165 634
Delete this struct entirely (its shape is now identical to `Type`, which every `returns` field uses instead):
13fd165 635
13fd165 636
```rust
13fd165 637
#[derive(Debug, Clone, PartialEq)]
13fd165 638
pub struct ReturnType {
13fd165 639
    pub name: String,
13fd165 640
    pub generics: Vec<GenericParam>,
13fd165 641
}
13fd165 642
```
13fd165 643
13fd165 644
- [ ] **Step 2: Update `parse_class`'s implements-list derivation for the new field order**
13fd165 645
13fd165 646
In `plum-core/src/parser.rs`, replace:
13fd165 647
13fd165 648
```rust
13fd165 649
    fn parse_class(&self, node: Node) -> Class {
13fd165 650
        // class: "type" type_identifier ("(" type_identifier,* ")")? generics? "=" body
13fd165 651
        // Named children in order: type_identifier (name), type_identifier* (implements), field*
13fd165 652
        let mut cursor = node.walk();
13fd165 653
        let named: Vec<Node> = node.named_children(&mut cursor).collect();
13fd165 654
13fd165 655
        let name = named.first().map(|n| self.text(*n)).unwrap_or_default();
13fd165 656
13fd165 657
        // implements = type_identifiers that appear before any `field` node
13fd165 658
        let implements: Vec<String> = named[1..]
13fd165 659
            .iter()
13fd165 660
            .take_while(|n| n.kind() == "type_identifier")
13fd165 661
            .map(|n| self.text(*n))
13fd165 662
            .collect();
13fd165 663
13fd165 664
        let generics = self.parse_generics_field(node);
13fd165 665
13fd165 666
        let fields: Vec<Field> = named
13fd165 667
            .iter()
13fd165 668
            .filter(|n| n.kind() == "field")
13fd165 669
            .map(|n| self.parse_field(*n))
13fd165 670
            .collect();
13fd165 671
13fd165 672
        Class { name, implements, generics, fields }
13fd165 673
    }
13fd165 674
```
13fd165 675
13fd165 676
with:
13fd165 677
13fd165 678
```rust
13fd165 679
    fn parse_class(&self, node: Node) -> Class {
13fd165 680
        // class: "type" type_identifier generics? ("(" type_identifier,* ")")? "=" body
13fd165 681
        // Named children in order: type_identifier (name), generics? (declaration), type_identifier* (implements), field*
13fd165 682
        let mut cursor = node.walk();
13fd165 683
        let named: Vec<Node> = node.named_children(&mut cursor).collect();
13fd165 684
13fd165 685
        let name = named.first().map(|n| self.text(*n)).unwrap_or_default();
13fd165 686
13fd165 687
        // Skip the optional `generics` declaration node before looking for implements.
13fd165 688
        let after_generics = if named.get(1).map(|n| n.kind()) == Some("generics") { 2 } else { 1 };
13fd165 689
13fd165 690
        // implements = type_identifiers that appear before any `field` node
13fd165 691
        let implements: Vec<String> = named[after_generics..]
13fd165 692
            .iter()
13fd165 693
            .take_while(|n| n.kind() == "type_identifier")
13fd165 694
            .map(|n| self.text(*n))
13fd165 695
            .collect();
13fd165 696
13fd165 697
        let generics = self.parse_generics_field(node);
13fd165 698
13fd165 699
        let fields: Vec<Field> = named
13fd165 700
            .iter()
13fd165 701
            .filter(|n| n.kind() == "field")
13fd165 702
            .map(|n| self.parse_field(*n))
13fd165 703
            .collect();
13fd165 704
13fd165 705
        Class { name, implements, generics, fields }
13fd165 706
    }
13fd165 707
```
13fd165 708
13fd165 709
- [ ] **Step 3: Update `parse_generics_field` and `parse_enum_variant` to match on `"generic"` instead of `"a"|"b"|"c"|"d"`**
13fd165 710
13fd165 711
Replace:
13fd165 712
13fd165 713
```rust
13fd165 714
    fn parse_generics_field(&self, node: Node) -> Vec<GenericParam> {
13fd165 715
        // generics: "(" generic_type,* ")"  where  generic_type: generic (":" sep1(type_identifier, "+"))?
13fd165 716
        //
13fd165 717
        // Both `generic_type` and `generic` are `inline`d in the grammar, so the
13fd165 718
        // `generics` node has NO `generic_type` children — its named children are
13fd165 719
        // the single-letter generic nodes (`a`/`b`/`c`/`d`) each optionally
13fd165 720
        // followed by their bound `type_identifier` nodes, all flattened together.
13fd165 721
        // Reconstruct each `GenericParam` by starting a new one at every generic
13fd165 722
        // letter and attaching any following `type_identifier`s as its bounds
13fd165 723
        // until the next generic letter.
13fd165 724
        let Some(generics_node) = self.children_of_kind(node, "generics").into_iter().next() else {
13fd165 725
            return Vec::new();
13fd165 726
        };
13fd165 727
        let mut cursor = generics_node.walk();
13fd165 728
        let mut params: Vec<GenericParam> = Vec::new();
13fd165 729
        for child in generics_node.named_children(&mut cursor) {
13fd165 730
            match child.kind() {
13fd165 731
                "a" | "b" | "c" | "d" => {
13fd165 732
                    params.push(GenericParam { name: self.text(child), bounds: Vec::new() });
13fd165 733
                }
13fd165 734
                "type_identifier" => {
13fd165 735
                    if let Some(last) = params.last_mut() {
13fd165 736
                        last.bounds.push(self.text(child));
13fd165 737
                    }
13fd165 738
                }
13fd165 739
                _ => {}
13fd165 740
            }
13fd165 741
        }
13fd165 742
        params
13fd165 743
    }
13fd165 744
```
13fd165 745
13fd165 746
with:
13fd165 747
13fd165 748
```rust
13fd165 749
    fn parse_generics_field(&self, node: Node) -> Vec<GenericParam> {
13fd165 750
        // generics: "[" generic_type,* "]"  where  generic_type: generic (":" sep1(type_identifier, "+"))?
13fd165 751
        //
13fd165 752
        // `generic_type` is `inline`d in the grammar, so the `generics` node has NO
13fd165 753
        // `generic_type` children — its named children are the single-uppercase-letter
13fd165 754
        // `generic` nodes, each optionally followed by their bound `type_identifier`
13fd165 755
        // nodes, all flattened together. Reconstruct each `GenericParam` by starting a
13fd165 756
        // new one at every `generic` node and attaching any following
13fd165 757
        // `type_identifier`s as its bounds until the next `generic` node.
13fd165 758
        let Some(generics_node) = self.children_of_kind(node, "generics").into_iter().next() else {
13fd165 759
            return Vec::new();
13fd165 760
        };
13fd165 761
        let mut cursor = generics_node.walk();
13fd165 762
        let mut params: Vec<GenericParam> = Vec::new();
13fd165 763
        for child in generics_node.named_children(&mut cursor) {
13fd165 764
            match child.kind() {
13fd165 765
                "generic" => {
13fd165 766
                    params.push(GenericParam { name: self.text(child), bounds: Vec::new() });
13fd165 767
                }
13fd165 768
                "type_identifier" => {
13fd165 769
                    if let Some(last) = params.last_mut() {
13fd165 770
                        last.bounds.push(self.text(child));
13fd165 771
                    }
13fd165 772
                }
13fd165 773
                _ => {}
13fd165 774
            }
13fd165 775
        }
13fd165 776
        params
13fd165 777
    }
13fd165 778
```
13fd165 779
13fd165 780
Replace:
13fd165 781
13fd165 782
```rust
13fd165 783
    fn parse_enum_variant(&self, node: Node) -> EnumVariant {
13fd165 784
        // enum_field (aliased to field): "|" type_identifier ("(" (type_identifier | generic),* ")")?
13fd165 785
        // named children: type_identifier (name), then each field type inside "()" — a
13fd165 786
        // `type_identifier` (concrete, e.g. `Int`) or an inlined generic letter node
13fd165 787
        // (`a`/`b`/`c`/`d`, since `generic` is inlined in the grammar).
13fd165 788
        let name = node.named_child(0).map(|n| self.text(n)).unwrap_or_default();
13fd165 789
        let fields: Vec<String> = (1..node.named_child_count())
13fd165 790
            .filter_map(|i| node.named_child(i as u32))
13fd165 791
            .filter(|n| matches!(n.kind(), "type_identifier" | "a" | "b" | "c" | "d"))
13fd165 792
            .map(|n| self.text(n))
13fd165 793
            .collect();
13fd165 794
        EnumVariant { name, fields }
13fd165 795
    }
13fd165 796
```
13fd165 797
13fd165 798
with:
13fd165 799
13fd165 800
```rust
13fd165 801
    fn parse_enum_variant(&self, node: Node) -> EnumVariant {
13fd165 802
        // enum_field (aliased to field): "|" type_identifier ("[" (type_identifier | generic),* "]")?
13fd165 803
        // named children: type_identifier (name), then each field type inside "[]" — a
13fd165 804
        // `type_identifier` (concrete, e.g. `Int`) or a `generic` node (single uppercase letter).
13fd165 805
        let name = node.named_child(0).map(|n| self.text(n)).unwrap_or_default();
13fd165 806
        let fields: Vec<String> = (1..node.named_child_count())
13fd165 807
            .filter_map(|i| node.named_child(i as u32))
13fd165 808
            .filter(|n| matches!(n.kind(), "type_identifier" | "generic"))
13fd165 809
            .map(|n| self.text(n))
13fd165 810
            .collect();
13fd165 811
        EnumVariant { name, fields }
13fd165 812
    }
13fd165 813
```
13fd165 814
13fd165 815
- [ ] **Step 4: Update `parse_trait_method` and `parse_fn` to read `returns` as a `type` node by field name; remove `parse_return_type`**
13fd165 816
13fd165 817
Replace:
13fd165 818
13fd165 819
```rust
13fd165 820
    fn parse_trait_method(&self, node: Node) -> TraitMethod {
13fd165 821
        // trait_field (aliased to field): fn_identifier "(" params ")" ("->" return_type)?
13fd165 822
        let name = node.named_child(0).map(|n| self.text(n)).unwrap_or_default();
13fd165 823
        let params = self.collect_params_from(node);
13fd165 824
        let returns = node
13fd165 825
            .named_children(&mut node.walk())
13fd165 826
            .find(|n| n.kind() == "return_type")
13fd165 827
            .map(|n| self.parse_return_type(n));
13fd165 828
        TraitMethod { name, params, returns }
13fd165 829
    }
13fd165 830
```
13fd165 831
13fd165 832
with:
13fd165 833
13fd165 834
```rust
13fd165 835
    fn parse_trait_method(&self, node: Node) -> TraitMethod {
13fd165 836
        // trait_field (aliased to field): fn_identifier "(" params ")" ("->" type)?
13fd165 837
        let name = node.named_child(0).map(|n| self.text(n)).unwrap_or_default();
13fd165 838
        let params = self.collect_params_from(node);
13fd165 839
        let returns = node.child_by_field_name("returns").map(|n| self.parse_type(n));
13fd165 840
        TraitMethod { name, params, returns }
13fd165 841
    }
13fd165 842
```
13fd165 843
13fd165 844
In `parse_fn`, replace:
13fd165 845
13fd165 846
```rust
13fd165 847
        let returns = named
13fd165 848
            .iter()
13fd165 849
            .find(|n| n.kind() == "return_type")
13fd165 850
            .map(|n| self.parse_return_type(*n));
13fd165 851
13fd165 852
        // body is the last named child — it is either a `body` node (block)
13fd165 853
        // or an expression node when the body is a single expression.
13fd165 854
        let body = named.last().and_then(|last| {
13fd165 855
            match last.kind() {
13fd165 856
                // Skip non-body trailing nodes
13fd165 857
                "fn_identifier" | "type" | "param" | "self" | "return_type" => None,
13fd165 858
                "body" => Some(FnBody::Block(self.parse_block(*last))),
13fd165 859
                _ => {
13fd165 860
                    let unwrapped = self.unwrap_expr_node(*last);
13fd165 861
                    Some(FnBody::Expr(self.parse_expression(unwrapped)))
13fd165 862
                }
13fd165 863
            }
13fd165 864
        }).unwrap_or(FnBody::Block(Block { stmts: vec![] }));
13fd165 865
```
13fd165 866
13fd165 867
with:
13fd165 868
13fd165 869
```rust
13fd165 870
        let returns = node.child_by_field_name("returns").map(|n| self.parse_type(n));
13fd165 871
13fd165 872
        // body is the last named child — it is either a `body` node (block)
13fd165 873
        // or an expression node when the body is a single expression. The
13fd165 874
        // `<Cat>` receiver annotation and the `returns` type both have kind
13fd165 875
        // "type" now (return_type no longer exists as a separate node kind),
13fd165 876
        // but that's fine: neither can ever be the LAST named child when a
13fd165 877
        // body is present, since `body`/the trailing expression always comes
13fd165 878
        // after them in the grammar — so this match doesn't need to
13fd165 879
        // distinguish the two "type" cases from each other, only from `body`.
13fd165 880
        let body = named.last().and_then(|last| {
13fd165 881
            match last.kind() {
13fd165 882
                // Skip non-body trailing nodes
13fd165 883
                "fn_identifier" | "type" | "param" | "self" => None,
13fd165 884
                "body" => Some(FnBody::Block(self.parse_block(*last))),
13fd165 885
                _ => {
13fd165 886
                    let unwrapped = self.unwrap_expr_node(*last);
13fd165 887
                    Some(FnBody::Expr(self.parse_expression(unwrapped)))
13fd165 888
                }
13fd165 889
            }
13fd165 890
        }).unwrap_or(FnBody::Block(Block { stmts: vec![] }));
13fd165 891
```
13fd165 892
13fd165 893
Delete `parse_return_type` entirely:
13fd165 894
13fd165 895
```rust
13fd165 896
    fn parse_return_type(&self, node: Node) -> ReturnType {
13fd165 897
        // return_type: type_identifier generics?
13fd165 898
        // named_child(0) = type_identifier
13fd165 899
        let name = node.named_child(0).map(|n| self.text(n)).unwrap_or_default();
13fd165 900
        let generics = self.parse_generics_field(node);
13fd165 901
        ReturnType { name, generics }
13fd165 902
    }
13fd165 903
```
13fd165 904
13fd165 905
- [ ] **Step 5: Run `plum-core`'s tests**
13fd165 906
13fd165 907
Run: `cargo test -p plum-core 2>&1 | tail -60`
13fd165 908
Expected: PASS. (`plum-core`'s own parser/formatter tests don't currently exercise generics syntax — see the design spec's survey — so this mainly confirms the crate still compiles and existing non-generic tests are unaffected.)
13fd165 909
13fd165 910
- [ ] **Step 6: Commit**
13fd165 911
13fd165 912
```bash
13fd165 913
git add plum-core/src/ast.rs plum-core/src/parser.rs
13fd165 914
git commit -m "feat(plum-core): parse bracket generics, drop ReturnType in favor of Type"
13fd165 915
```
13fd165 916
13fd165 917
---
13fd165 918
13fd165 919
### Task 3: `plum-checker` and `plum-wasm-codegen` — uppercase convention and `ReturnType` cleanup
13fd165 920
13fd165 921
**Files:**
13fd165 922
- Modify: `plum-checker/src/monomorphize.rs`
13fd165 923
- Modify: `plum-checker/src/lib.rs`
13fd165 924
- Modify: `plum-wasm-codegen/src/lib.rs`
13fd165 925
13fd165 926
**Interfaces:**
13fd165 927
- Consumes: `Fn.returns: Option<Type>` / `TraitMethod.returns: Option<Type>` from Task 2.
13fd165 928
- Produces: `is_generic_param_name` now recognizes single ASCII uppercase letters; every `ast::ReturnType` construction/reference becomes `ast::Type`.
13fd165 929
13fd165 930
- [ ] **Step 1: Flip `is_generic_param_name`'s case check**
13fd165 931
13fd165 932
In `plum-checker/src/monomorphize.rs`, replace:
13fd165 933
13fd165 934
```rust
13fd165 935
/// A single lowercase letter (`a`, `b`, `c`, `d`, ...) is the grammar's only legal
13fd165 936
/// spelling for a generic type parameter — this is how we recognize one, since
13fd165 937
/// `ast::Fn` and `ast::Enum` (unlike `ast::Class`/`ast::Trait`) carry no explicit
13fd165 938
/// generics declaration list.
13fd165 939
pub fn is_generic_param_name(name: &str) -> bool {
13fd165 940
    let mut chars = name.chars();
13fd165 941
    match (chars.next(), chars.next()) {
13fd165 942
        (Some(c), None) => c.is_ascii_lowercase(),
13fd165 943
        _ => false,
13fd165 944
    }
13fd165 945
}
13fd165 946
```
13fd165 947
13fd165 948
with:
13fd165 949
13fd165 950
```rust
13fd165 951
/// A single uppercase letter (`T`, `U`, `K`, ...) is the grammar's only legal
13fd165 952
/// spelling for a generic type parameter — this is how we recognize one, since
13fd165 953
/// `ast::Fn` and `ast::Enum` (unlike `ast::Class`/`ast::Trait`) carry no explicit
13fd165 954
/// generics declaration list.
13fd165 955
pub fn is_generic_param_name(name: &str) -> bool {
13fd165 956
    let mut chars = name.chars();
13fd165 957
    match (chars.next(), chars.next()) {
13fd165 958
        (Some(c), None) => c.is_ascii_uppercase(),
13fd165 959
        _ => false,
13fd165 960
    }
13fd165 961
}
13fd165 962
```
13fd165 963
13fd165 964
Also update the two doc comments in this file that describe the old lowercase convention:
13fd165 965
13fd165 966
Replace:
13fd165 967
13fd165 968
```rust
13fd165 969
/// The generic parameter names implicitly introduced by a `Fn` — every distinct
13fd165 970
/// single-lowercase-letter type name appearing in its params or return type, in
13fd165 971
/// first-appearance order.
13fd165 972
```
13fd165 973
13fd165 974
with:
13fd165 975
13fd165 976
```rust
13fd165 977
/// The generic parameter names implicitly introduced by a `Fn` — every distinct
13fd165 978
/// single-uppercase-letter type name appearing in its params or return type, in
13fd165 979
/// first-appearance order.
13fd165 980
```
13fd165 981
13fd165 982
Replace:
13fd165 983
13fd165 984
```rust
13fd165 985
/// The generic parameter names implicitly introduced by an `Enum` — every distinct
13fd165 986
/// single-lowercase-letter variant field type name, in first-appearance order.
13fd165 987
```
13fd165 988
13fd165 989
with:
13fd165 990
13fd165 991
```rust
13fd165 992
/// The generic parameter names implicitly introduced by an `Enum` — every distinct
13fd165 993
/// single-uppercase-letter variant field type name, in first-appearance order.
13fd165 994
```
13fd165 995
13fd165 996
- [ ] **Step 2: Replace `ast::ReturnType` construction sites with `ast::Type`**
13fd165 997
13fd165 998
In `plum-checker/src/monomorphize.rs`, replace:
13fd165 999
13fd165 1000
```rust
13fd165 1001
        returns: f.returns.as_ref().map(|r| {
13fd165 1002
            let substituted = substitute_type(&ast::Type { name: r.name.clone(), generics: vec![] }, subst);
13fd165 1003
            ast::ReturnType { name: substituted.name, generics: vec![] }
13fd165 1004
        }),
13fd165 1005
```
13fd165 1006
13fd165 1007
with:
13fd165 1008
13fd165 1009
```rust
13fd165 1010
        returns: f.returns.as_ref().map(|r| substitute_type(r, subst)),
13fd165 1011
```
13fd165 1012
13fd165 1013
Replace:
13fd165 1014
13fd165 1015
```rust
13fd165 1016
        if needs {
13fd165 1017
            f.returns = Some(ast::ReturnType { name: t.to_string(), generics: vec![] });
13fd165 1018
        }
13fd165 1019
```
13fd165 1020
13fd165 1021
with:
13fd165 1022
13fd165 1023
```rust
13fd165 1024
        if needs {
13fd165 1025
            f.returns = Some(ast::Type { name: t.to_string(), generics: vec![] });
13fd165 1026
        }
13fd165 1027
```
13fd165 1028
13fd165 1029
- [ ] **Step 3: Simplify the two `ast::Type` reconstructions in `plum-checker/src/lib.rs`**
13fd165 1030
13fd165 1031
`f.returns`/`r` is already `&ast::Type` after Task 2 — reconstructing a fresh `ast::Type` from its own fields is now redundant. Replace:
13fd165 1032
13fd165 1033
```rust
13fd165 1034
                let ret = f.returns.as_ref()
13fd165 1035
                    .map(|r| plum_type_from_ast(&ast::Type { name: r.name.clone(), generics: vec![] }))
13fd165 1036
                    .unwrap_or(PlumType::TUnit);
13fd165 1037
```
13fd165 1038
13fd165 1039
with:
13fd165 1040
13fd165 1041
```rust
13fd165 1042
                let ret = f.returns.as_ref()
13fd165 1043
                    .map(plum_type_from_ast)
13fd165 1044
                    .unwrap_or(PlumType::TUnit);
13fd165 1045
```
13fd165 1046
13fd165 1047
Replace:
13fd165 1048
13fd165 1049
```rust
13fd165 1050
    let declared_ret = f.returns.as_ref()
13fd165 1051
        .map(|r| {
13fd165 1052
            let ast_ty = ast::Type { name: r.name.clone(), generics: vec![] };
13fd165 1053
            plum_type_from_ast(&ast_ty)
13fd165 1054
        })
13fd165 1055
        .unwrap_or(PlumType::TUnit);
13fd165 1056
```
13fd165 1057
13fd165 1058
with:
13fd165 1059
13fd165 1060
```rust
13fd165 1061
    let declared_ret = f.returns.as_ref()
13fd165 1062
        .map(plum_type_from_ast)
13fd165 1063
        .unwrap_or(PlumType::TUnit);
13fd165 1064
```
13fd165 1065
13fd165 1066
- [ ] **Step 4: Retype `ret_type_to_wasm` in `plum-wasm-codegen`**
13fd165 1067
13fd165 1068
In `plum-wasm-codegen/src/lib.rs`, replace:
13fd165 1069
13fd165 1070
```rust
13fd165 1071
fn ret_type_to_wasm(ret: Option<&ast::ReturnType>) -> Option<ValType> {
13fd165 1072
    ret.and_then(|r| ast_type_to_wasm(&r.name))
13fd165 1073
}
13fd165 1074
```
13fd165 1075
13fd165 1076
with:
13fd165 1077
13fd165 1078
```rust
13fd165 1079
fn ret_type_to_wasm(ret: Option<&ast::Type>) -> Option<ValType> {
13fd165 1080
    ret.and_then(|r| ast_type_to_wasm(&r.name))
13fd165 1081
}
13fd165 1082
```
13fd165 1083
13fd165 1084
(Its two call sites, `f.returns.as_ref()` at both usages, already produce `Option<&ast::Type>` after Task 2 — no call-site changes needed.)
13fd165 1085
13fd165 1086
- [ ] **Step 5: Build the workspace to confirm the Rust-level refactor compiles**
13fd165 1087
13fd165 1088
Run: `cargo build --workspace 2>&1 | tail -80`
13fd165 1089
Expected: builds clean, no type errors. (Tests are expected to start failing at this point — old-syntax `.plum` source strings embedded in test fixtures no longer parse under the Task-1 grammar. That's addressed in Tasks 4-5.)
13fd165 1090
13fd165 1091
- [ ] **Step 6: Commit**
13fd165 1092
13fd165 1093
```bash
13fd165 1094
git add plum-checker/src/monomorphize.rs plum-checker/src/lib.rs plum-wasm-codegen/src/lib.rs
13fd165 1095
git commit -m "feat(plum-checker,plum-wasm-codegen): recognize uppercase generic params, use Type instead of ReturnType"
13fd165 1096
```
13fd165 1097
13fd165 1098
---
13fd165 1099
13fd165 1100
### Task 4: Update `plum-checker` test fixtures
13fd165 1101
13fd165 1102
**Files:**
13fd165 1103
- Modify: `plum-checker/tests/checker_tests.rs`
13fd165 1104
- Modify: `plum-checker/tests/monomorphize_tests.rs`
13fd165 1105
13fd165 1106
**Interfaces:**
13fd165 1107
- Consumes: the Task 1-3 grammar/parser/checker changes.
13fd165 1108
- Produces: every embedded `.plum` source string in these two files rewritten to the new syntax, with assertions unchanged (this migration doesn't change behavior, so every currently-passing test must still pass with the same expected values).
13fd165 1109
13fd165 1110
- [ ] **Step 1: Run the checker test suite to see which tests fail on old syntax**
13fd165 1111
13fd165 1112
Run: `cargo test -p plum-checker 2>&1 | tail -150`
13fd165 1113
Expected: FAIL — a batch of tests fail because their embedded source strings use old syntax (`type Box(a) =`, `| Some(a)`, `-> a`, etc.) that no longer parses (single lowercase letters are no longer a valid `generic` or `type_identifier` token per Task 1).
13fd165 1114
13fd165 1115
- [ ] **Step 2: Rewrite every failing test's embedded source string using this mechanical rule**
13fd165 1116
13fd165 1117
For each failing test in `checker_tests.rs` and `monomorphize_tests.rs`, find every place a bare single lowercase letter (`a`, `b`, `c`, `d`) appears in a **type position** — i.e. immediately after `:` in a field/param declaration, as a bare function return type, inside a class's generic-declaration parens, or inside an enum variant's payload parens — and rewrite it using this fixed per-letter mapping, consistently within each individual test (do not reuse letters across unrelated tests):
13fd165 1118
13fd165 1119
- `a` → `T`
13fd165 1120
- `b` → `U`
13fd165 1121
- `c` → `V`
13fd165 1122
- `d` → `W`
13fd165 1123
13fd165 1124
And convert the enclosing syntax per Task 1's grammar: `type Foo(a) =` → `type Foo[T] =`, `type Foo(a, b) =` → `type Foo[T, U] =`, `Foo(a)` (type-argument usage, e.g. `Option(a)`) → `Foo[T]`, `| Some(a)` (enum variant payload) → `| Some[T]`, bare `-> a` (return type) → `-> T`.
13fd165 1125
13fd165 1126
Do **not** touch: variable/parameter *names* that happen to be single lowercase letters (e.g. `add(a: Int, b: Int) -> Int`, `bothTrue(a: Bool, b: Bool)`) — these are `var_identifier`s, unaffected by this migration, and must stay exactly as they are. Only letters appearing in **type position** (after the `:` or as a bare type name) are generics and need rewriting.
13fd165 1127
13fd165 1128
As a concrete worked example, in `checker_tests.rs` around line 322-323:
13fd165 1129
13fd165 1130
```rust
13fd165 1131
type Box(a) =
13fd165 1132
  value: a
13fd165 1133
```
13fd165 1134
13fd165 1135
becomes:
13fd165 1136
13fd165 1137
```rust
13fd165 1138
type Box[T] =
13fd165 1139
  value: T
13fd165 1140
```
13fd165 1141
13fd165 1142
and around line 356:
13fd165 1143
13fd165 1144
```rust
13fd165 1145
pair(first: a, second: b) -> Bool =
13fd165 1146
```
13fd165 1147
13fd165 1148
becomes:
13fd165 1149
13fd165 1150
```rust
13fd165 1151
pair(first: T, second: U) -> Bool =
13fd165 1152
```
13fd165 1153
13fd165 1154
(here `first`/`second` are param *names*, left untouched; `a`/`b` are the param *types*, rewritten.)
13fd165 1155
13fd165 1156
- [ ] **Step 3: Re-run until the checker suite passes**
13fd165 1157
13fd165 1158
Run: `cargo test -p plum-checker 2>&1 | tail -150`
13fd165 1159
Expected: iterate Step 2 against each remaining failure until this command shows all tests PASS, with the exact same assertions/expected values as before this migration (only source syntax changed, not behavior).
13fd165 1160
13fd165 1161
- [ ] **Step 4: Commit**
13fd165 1162
13fd165 1163
```bash
13fd165 1164
git add plum-checker/tests/checker_tests.rs plum-checker/tests/monomorphize_tests.rs
13fd165 1165
git commit -m "test(plum-checker): migrate test fixtures to bracket generics syntax"
13fd165 1166
```
13fd165 1167
13fd165 1168
---
13fd165 1169
13fd165 1170
### Task 5: Update `plum-wasm-codegen` test fixtures
13fd165 1171
13fd165 1172
**Files:**
13fd165 1173
- Modify: `plum-wasm-codegen/tests/codegen_tests.rs`
13fd165 1174
13fd165 1175
**Interfaces:**
13fd165 1176
- Consumes: the Task 1-3 grammar/parser/checker changes.
13fd165 1177
- Produces: every embedded `.plum` source string in this file rewritten to the new syntax; same assertions, same expected `run_main`/`run_main_str` results as before.
13fd165 1178
13fd165 1179
- [ ] **Step 1: Run the codegen test suite to see which tests fail on old syntax**
13fd165 1180
13fd165 1181
Run: `cargo test -p plum-wasm-codegen 2>&1 | tail -150`
13fd165 1182
Expected: FAIL — the same class of failures as Task 4, for `type Box(a) =`, `identity(value: a) -> a =`, and similar old-syntax fixtures (survey found occurrences around lines 856, 877, 891, 909, 1010, 1177, plus additional `Option(a)`/generic usages throughout).
13fd165 1183
13fd165 1184
- [ ] **Step 2: Rewrite every failing test's embedded source string using the same mechanical rule as Task 4**
13fd165 1185
13fd165 1186
Apply the identical rewrite rule from Task 4 Step 2 (bare single lowercase letters in type position → uppercase per the `a→T, b→U, c→V, d→W` mapping, consistently per-test; enclosing parens → brackets per Task 1's grammar; variable/parameter names left untouched).
13fd165 1187
13fd165 1188
As a concrete worked example, the `identity` pattern around lines 877/909/1177:
13fd165 1189
13fd165 1190
```rust
13fd165 1191
type Box(a) =
13fd165 1192
  value: a
13fd165 1193
13fd165 1194
identity(value: a) -> a =
13fd165 1195
  value
13fd165 1196
```
13fd165 1197
13fd165 1198
becomes:
13fd165 1199
13fd165 1200
```rust
13fd165 1201
type Box[T] =
13fd165 1202
  value: T
13fd165 1203
13fd165 1204
identity(value: T) -> T =
13fd165 1205
  value
13fd165 1206
```
13fd165 1207
13fd165 1208
- [ ] **Step 3: Re-run until the codegen suite passes**
13fd165 1209
13fd165 1210
Run: `cargo test -p plum-wasm-codegen 2>&1 | tail -150`
13fd165 1211
Expected: iterate Step 2 against each remaining failure until this command shows all tests PASS with unchanged expected values (e.g. `run_main` result assertions).
13fd165 1212
13fd165 1213
- [ ] **Step 4: Run the full workspace suite**
13fd165 1214
13fd165 1215
Run: `cargo test --workspace 2>&1 | tail -100`
13fd165 1216
Expected: all tests PASS across every crate (`tooling/tree-sitter-plum`'s corpus tests were already verified in Task 1 via `tree-sitter test`, which is a separate command from `cargo test`).
13fd165 1217
13fd165 1218
- [ ] **Step 5: Commit**
13fd165 1219
13fd165 1220
```bash
13fd165 1221
git add plum-wasm-codegen/tests/codegen_tests.rs
13fd165 1222
git commit -m "test(plum-wasm-codegen): migrate test fixtures to bracket generics syntax"
13fd165 1223
```
13fd165 1224
13fd165 1225
---
13fd165 1226
13fd165 1227
### Task 6: Rewrite the stdlib (`libs/std`)
13fd165 1228
13fd165 1229
**Files:**
13fd165 1230
- Modify: `libs/std/list.plum`
13fd165 1231
- Modify: `libs/std/map.plum`
13fd165 1232
- Modify: `libs/std/option.plum`
13fd165 1233
- Modify: `libs/std/result.plum`
13fd165 1234
13fd165 1235
**Interfaces:**
13fd165 1236
- Consumes: the new grammar (Task 1) — these files must parse under it.
13fd165 1237
- Produces: stdlib source using bracket/uppercase generics throughout, matching the `Node[T]` shape already present in `list.plum`.
13fd165 1238
13fd165 1239
- [ ] **Step 1: Rewrite `libs/std/option.plum`**
13fd165 1240
13fd165 1241
Replace the entire file contents with:
13fd165 1242
13fd165 1243
```
13fd165 1244
module std
13fd165 1245
13fd165 1246
enum Option =
13fd165 1247
  | Some[T]
13fd165 1248
  | None
13fd165 1249
```
13fd165 1250
13fd165 1251
- [ ] **Step 2: Rewrite `libs/std/result.plum`**
13fd165 1252
13fd165 1253
Replace the entire file contents with:
13fd165 1254
13fd165 1255
```
13fd165 1256
module std
13fd165 1257
13fd165 1258
enum Result =
13fd165 1259
  | Ok[T]
13fd165 1260
  | Err[E]
13fd165 1261
13fd165 1262
# checks whether the result is an Ok value
13fd165 1263
isOk<Result>(self) -> Bool =
13fd165 1264
  match self
13fd165 1265
    Ok(_) =>
13fd165 1266
      True
13fd165 1267
    Err(_) =>
13fd165 1268
      False
13fd165 1269
```
13fd165 1270
13fd165 1271
- [ ] **Step 3: Rewrite `libs/std/map.plum`**
13fd165 1272
13fd165 1273
Replace the entire file contents with:
13fd165 1274
13fd165 1275
```
13fd165 1276
module std
13fd165 1277
13fd165 1278
# A Pair is a grouping of a key with a value
13fd165 1279
type Pair[K, V] =
13fd165 1280
  key: K
13fd165 1281
  val: V
13fd165 1282
13fd165 1283
# A Map is a data structure describing a contiguous section of an array stored separately from the slice variable itself.
13fd165 1284
# A Map is not an array. A slice describes a piece of an array.
13fd165 1285
type Map[K, V] =
13fd165 1286
  items: List[Pair[K, V]]
13fd165 1287
13fd165 1288
init<Map>(self, kvs: ...Pair) -> Map =
13fd165 1289
  Map().add(kvs)
13fd165 1290
13fd165 1291
# adds the specified elements to the start of the list
13fd165 1292
add<Map>(self, kvs: ...Pair) =
13fd165 1293
  self.items.add(kvs)
13fd165 1294
13fd165 1295
# gets a value from the Map using key k
13fd165 1296
get<Map>(self, k: K) -> Option[V] =
13fd165 1297
  for p in self.items
13fd165 1298
    if p.key == k
13fd165 1299
      return Some(p.val)
13fd165 1300
  None
13fd165 1301
13fd165 1302
# puts a value into the Map
13fd165 1303
set<Map>(self, k: K, v: V) =
13fd165 1304
  self.items.add(Pair(key: k, val: v))
13fd165 1305
13fd165 1306
# puts a value into the Map if its not already present
13fd165 1307
putIfAbsent<Map>(self, k: K, v: V) =
13fd165 1308
  todo
13fd165 1309
13fd165 1310
map<Map>(self, cb: fn(Pair[K, V]) -> Pair[X, Y]) -> Map[X, Y] =
13fd165 1311
  self.items.map(cb)
13fd165 1312
```
13fd165 1313
13fd165 1314
- [ ] **Step 4: Rewrite `libs/std/list.plum`**
13fd165 1315
13fd165 1316
Replace the entire file contents with:
13fd165 1317
13fd165 1318
```
13fd165 1319
module std
13fd165 1320
13fd165 1321
import std/option
13fd165 1322
13fd165 1323
# A node stores the data in a list and contains pointers to the previous and next sibling nodes
13fd165 1324
type Node[T] =
13fd165 1325
  value: T
13fd165 1326
  prev: Option[Node]
13fd165 1327
  next: Option[Node]
13fd165 1328
13fd165 1329
# A list is a data structure describing a contiguous section of an array stored separately from the slice variable itself.
13fd165 1330
# It contains the pointers to the start and end nodes (head, tail) and maintains the size as well
13fd165 1331
type List[T: Stringable](Stringable) =
13fd165 1332
  head: Option[Node]
13fd165 1333
  tail: Option[Node]
13fd165 1334
  size: Int
13fd165 1335
13fd165 1336
makeList(values: ...T) -> List =
13fd165 1337
  List(None, None, 0).add(values)
13fd165 1338
13fd165 1339
# gets the element at i'th index of the list
13fd165 1340
get<List>(self, i: Int) -> Option[T] =
13fd165 1341
  current = self.head
13fd165 1342
  index = 0
13fd165 1343
  while current != None
13fd165 1344
    match current
13fd165 1345
      Some(node) =>
13fd165 1346
        if index == i
13fd165 1347
          return Some(node.value)
13fd165 1348
        current = node.next
13fd165 1349
        index = index + 1
13fd165 1350
      None =>
13fd165 1351
        break
13fd165 1352
  None
13fd165 1353
13fd165 1354
# sets the element at i'th index of the list
13fd165 1355
set<List>(self, i: Int, v: T) -> Option[T] =
13fd165 1356
  todo
13fd165 1357
13fd165 1358
# returns the no of elements in the list
13fd165 1359
length<List>(self) -> Int =
13fd165 1360
  self.size
13fd165 1361
13fd165 1362
# adds the specified elements to the start of the list
13fd165 1363
add<List>(self, values: ...T) =
13fd165 1364
  todo
13fd165 1365
13fd165 1366
# removes the element at i'th index of the list
13fd165 1367
removeAt<List>(self, i: Int) =
13fd165 1368
  todo
13fd165 1369
13fd165 1370
# removes the element v from list
13fd165 1371
remove<List>(self, v: T) =
13fd165 1372
  todo
13fd165 1373
13fd165 1374
# removes all objects from this list
13fd165 1375
clear<List>(self) =
13fd165 1376
  todo
13fd165 1377
13fd165 1378
# returns a new list with the elements in reverse order.
13fd165 1379
reverse<List>(self, v: fn(T) -> Bool) -> List =
13fd165 1380
  todo
13fd165 1381
13fd165 1382
# returns a new list with the elements sorted by sorter
13fd165 1383
sort<List>(self, sorter: fn(T) -> Bool) -> List =
13fd165 1384
  todo
13fd165 1385
13fd165 1386
# returns an item and index in the list if the item is is equal to search item
13fd165 1387
find<List>(self, search: T) -> Option[T] =
13fd165 1388
  todo
13fd165 1389
13fd165 1390
# returns the index of an item in the list if present and comparable otherwise None
13fd165 1391
contains<List>(self, v: T) -> Bool =
13fd165 1392
  todo
13fd165 1393
13fd165 1394
# calls f for each elem in the list
13fd165 1395
each<List>(self, cb: fn(T)) -> Unit =
13fd165 1396
  current = self.head
13fd165 1397
  while current != None
13fd165 1398
    match current
13fd165 1399
      Some(node) =>
13fd165 1400
        cb(node.value)
13fd165 1401
        current = node.next
13fd165 1402
      None =>
13fd165 1403
        break
13fd165 1404
13fd165 1405
# returns a list made up of b elements for each elem in the list
13fd165 1406
map<List>(self, cb: fn(T) -> U) -> List[U] =
13fd165 1407
  nl = List()
13fd165 1408
  current = self.head
13fd165 1409
  while current != None
13fd165 1410
    match current
13fd165 1411
      Some(node) =>
13fd165 1412
        item = cb(node.value)
13fd165 1413
        nl.add(item)
13fd165 1414
        current = node.next
13fd165 1415
      None =>
13fd165 1416
        break
13fd165 1417
  nl
13fd165 1418
13fd165 1419
# returns a new list with each element flat-mapped
13fd165 1420
flatMap<List>(self) =
13fd165 1421
  todo
13fd165 1422
13fd165 1423
# returns a new list with the elements that matched the predicate
13fd165 1424
retain<List>(self, predicate: fn(T) -> T) -> List =
13fd165 1425
  todo
13fd165 1426
13fd165 1427
# returns a new list with the elements that matched the predicate removed
13fd165 1428
reject<List>(self, predicate: fn(T) -> T) -> List =
13fd165 1429
  todo
13fd165 1430
13fd165 1431
# returns true if any element in the list satisfies the predicate
13fd165 1432
any<List>(self, predicate: fn(T) -> Bool) -> Bool =
13fd165 1433
  todo
13fd165 1434
13fd165 1435
# returns true if all of the elements in the list satisfies the predicate
13fd165 1436
every<List>(self, predicate: fn(T) -> Bool) -> Bool =
13fd165 1437
  todo
13fd165 1438
13fd165 1439
# returns the accumulated value of all the elements in the list
13fd165 1440
reduce<List>(self, acc: U, cb: fn(T) -> T) -> Option[U] =
13fd165 1441
  todo
13fd165 1442
13fd165 1443
# returns the first element in the list
13fd165 1444
first<List>(self) -> Option[T] =
13fd165 1445
  match self.head
13fd165 1446
    Some(node) =>
13fd165 1447
      Some(node.value)
13fd165 1448
    None =>
13fd165 1449
      None
13fd165 1450
13fd165 1451
# returns the last element in the list
13fd165 1452
last<List>(self) -> Option[T] =
13fd165 1453
  match self.tail
13fd165 1454
    Some(node) =>
13fd165 1455
      Some(node.value)
13fd165 1456
    None =>
13fd165 1457
      None
13fd165 1458
13fd165 1459
# returns a list containing the first n elements of the given list
13fd165 1460
sublist<List>(self, start: Int, end: Int) -> List =
13fd165 1461
  todo
13fd165 1462
13fd165 1463
# returns a list containing the first n elements of the given list
13fd165 1464
take<List>(self, n: Int) -> List =
13fd165 1465
  todo
13fd165 1466
13fd165 1467
# returns a list containing the first n elements of the given list
13fd165 1468
skip<List>(self, n: Int) -> List =
13fd165 1469
  todo
13fd165 1470
13fd165 1471
# returns a list containing the first n elements of the given list
13fd165 1472
drop<List>(self, n: Int) -> List =
13fd165 1473
  todo
13fd165 1474
13fd165 1475
# returns a new list with some of the elements taken randomly
13fd165 1476
sample<List>(self) =
13fd165 1477
  todo
13fd165 1478
13fd165 1479
# returns a new list with all elements shuffled
13fd165 1480
shuffle<List>(self) =
13fd165 1481
  todo
13fd165 1482
13fd165 1483
# returns a new list with all elements grouped by adjacent pairs
13fd165 1484
partition<List>(self) =
13fd165 1485
  todo
13fd165 1486
13fd165 1487
# returns a new list with all elements grouped into chunks
13fd165 1488
chunk<List>(self) =
13fd165 1489
  todo
13fd165 1490
13fd165 1491
# returns a new list with all elements grouped
13fd165 1492
groupBy<List>(self) =
13fd165 1493
  todo
13fd165 1494
13fd165 1495
join<List>(self, sep: Str = ",") -> Str =
13fd165 1496
  res = Buffer()
13fd165 1497
  self.each(|v|
13fd165 1498
    res.write(v.toStr())
13fd165 1499
    res.write(sep)
13fd165 1500
  )
13fd165 1501
  res.toStr()
13fd165 1502
```
13fd165 1503
13fd165 1504
(This is a straight letter-case/bracket rewrite of the file's pre-existing content — no method bodies change, including the still-`todo` ones. `Node[T]`'s already-migrated shape on disk is preserved as-is.)
13fd165 1505
13fd165 1506
- [ ] **Step 5: Confirm these files aren't exercised by any Rust test (so this step can't be verified by `cargo test`)**
13fd165 1507
13fd165 1508
Run: `grep -rn "libs/std" plum-checker/tests plum-wasm-codegen/tests plum-core/tests 2>/dev/null`
13fd165 1509
Expected: no output (or only unrelated matches) — confirming, per the design spec's survey, that no existing Rust test currently loads these files directly, so `cargo test --workspace` passing in Task 5 is unaffected by this task, and there is no automated check for these `.plum` files' correctness beyond `plum-cli` manually loading them (out of scope here — see Global Constraints on the blocked `list-methods` plan, which is what will eventually exercise `list.plum` end-to-end).
13fd165 1510
13fd165 1511
- [ ] **Step 6: Commit**
13fd165 1512
13fd165 1513
```bash
13fd165 1514
git add libs/std/option.plum libs/std/result.plum libs/std/map.plum libs/std/list.plum
13fd165 1515
git commit -m "feat(libs/std): migrate stdlib generics to bracket syntax"
13fd165 1516
```
13fd165 1517
13fd165 1518
---
13fd165 1519
13fd165 1520
### Task 7: Rewrite `examples/types.plum`
13fd165 1521
13fd165 1522
**Files:**
13fd165 1523
- Modify: `examples/types.plum`
13fd165 1524
13fd165 1525
**Interfaces:**
13fd165 1526
- Consumes: the new grammar (Task 1).
13fd165 1527
- Produces: the example file using bracket/uppercase generics for its one generic class and one generic trait; its non-generic declarations (`Point`, `Named(Stringable)`, `Shape`, `Color`, `Option`) are untouched.
13fd165 1528
13fd165 1529
- [ ] **Step 1: Rewrite the file**
13fd165 1530
13fd165 1531
Replace the entire file contents with:
13fd165 1532
13fd165 1533
```
13fd165 1534
type Point =
13fd165 1535
  x: Int
13fd165 1536
  y: Int
13fd165 1537
13fd165 1538
type Named(Stringable) =
13fd165 1539
  name: Str
13fd165 1540
13fd165 1541
type Box[T] =
13fd165 1542
  value: T
13fd165 1543
13fd165 1544
trait Shape =
13fd165 1545
  area() -> Float
13fd165 1546
  perimeter() -> Float
13fd165 1547
13fd165 1548
trait Comparable[T: Ord] =
13fd165 1549
  compareTo(other: T) -> Int
13fd165 1550
13fd165 1551
enum Color =
13fd165 1552
  | Red
13fd165 1553
  | Green
13fd165 1554
  | Blue
13fd165 1555
13fd165 1556
enum Option =
13fd165 1557
  | Some(Int)
13fd165 1558
  | None
13fd165 1559
13fd165 1560
makeIntBox() -> Box =
13fd165 1561
  Box(value: 5)
13fd165 1562
13fd165 1563
makeStrBox() -> Box =
13fd165 1564
  Box(value: "x")
13fd165 1565
```
13fd165 1566
13fd165 1567
- [ ] **Step 2: Confirm this file isn't parsed by any automated test**
13fd165 1568
13fd165 1569
Run: `grep -rn "examples/types" plum-checker/tests plum-wasm-codegen/tests plum-core/tests plum-cli 2>/dev/null`
13fd165 1570
Expected: no output — same reasoning as Task 6 Step 5; this file exists as a hand-written reference example, not something `cargo test` exercises.
13fd165 1571
13fd165 1572
- [ ] **Step 3: Commit**
13fd165 1573
13fd165 1574
```bash
13fd165 1575
git add examples/types.plum
13fd165 1576
git commit -m "feat(examples): migrate types.plum generics to bracket syntax"
13fd165 1577
```
13fd165 1578
13fd165 1579
---
13fd165 1580
13fd165 1581
### Task 8: Final full-workspace verification
13fd165 1582
13fd165 1583
**Files:** none (verification only).
13fd165 1584
13fd165 1585
**Interfaces:**
13fd165 1586
- Consumes: everything from Tasks 1-7.
13fd165 1587
- Produces: confirmation that the migration is complete and the workspace is green end-to-end.
13fd165 1588
13fd165 1589
- [ ] **Step 1: Run the full Rust test suite**
13fd165 1590
13fd165 1591
Run: `cargo test --workspace 2>&1 | tail -100`
13fd165 1592
Expected: all tests PASS.
13fd165 1593
13fd165 1594
- [ ] **Step 2: Run the tree-sitter corpus suite**
13fd165 1595
13fd165 1596
Run: `cd tooling/tree-sitter-plum && npx tree-sitter test 2>&1 | tail -60`
13fd165 1597
Expected: all tests PASS.
13fd165 1598
13fd165 1599
- [ ] **Step 3: Confirm no old-syntax generics remain in tracked `.plum` files**
13fd165 1600
13fd165 1601
Run: `grep -rn '([a-d])\|([a-d],\|(Stringable)([a-d]\|| Some(a)\|| Ok(a)\|| Err(b)' libs/std examples 2>/dev/null`
13fd165 1602
Expected: no output. (This is a narrow sanity grep for the exact old patterns this plan rewrote — not exhaustive old-syntax detection, since `a`/`b`/`c`/`d` as ordinary variable names elsewhere are legitimate and would false-positive on a broader pattern.)
13fd165 1603
13fd165 1604
- [ ] **Step 4: No commit needed**
13fd165 1605
13fd165 1606
This task is verification-only; if Steps 1-3 all pass, the migration is complete and every prior task's commit already captured the corresponding changes.