plum

#treesitter#compiler#wasm

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

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


3910d54 — Peter John 2026-11-01T23:19:05+05:30
test: restore coverage for 8 behaviors found uncovered after dedupe
plum-checker/tests/checker_tests.rs CHANGED
@@ -600,3 +600,12 @@ fn rejects_bad_optionals_and_error_unions() {
600
600
  r("fun g() -> error{Neg}!Int =\n \"x\"\n", &["expected error{Neg}!Int, found Str"]),
601
601
  ]);
602
602
  }
603
+
604
+ // A bare generic name as a return annotation inside the type's own methods means the same
605
+ // instantiation. (Codegen does not support it yet: `-> Box` fails with a GC registry error.)
606
+ #[test]
607
+ fn bare_generic_type_allowed_inside_its_own_methods() {
608
+ let src = "record Box[T](value: T) =\n\n fun same(self) -> Box =\n self\n";
609
+ let r = checkSource(&parseClean(src));
610
+ assert!(r.is_ok(), "got {:?}", r.err());
611
+ }
plum-examples/constants.plum CHANGED
@@ -2,6 +2,7 @@ module constants
2
2
  import std/Int
3
3
  import std/Float
4
4
  import std/Str
5
+ import std/List
5
6
 
6
7
  # Constants and static functions live inside a type's body or a `primitive`
7
8
  # namespace — there are no top-level constants. `NAME = expr` declares an
@@ -39,6 +40,16 @@ struct Vec2(x: Float, y: Float) =
39
40
 
40
41
  fun scaled(n: Float) -> Float = n * Grid.CELL_SCALE
41
42
 
43
+ primitive Shelf =
44
+ SIDES = 4
45
+ NAMES = List("a", "b", "c")
46
+
47
+ fun sidesTwice() -> Int =
48
+ twice(SIDES)
49
+
50
+ fun twice(n: Int) -> Int =
51
+ n * 2
52
+
42
53
  test "consts are usable in fns, methods, generics and derived consts"
43
54
  assert Grid.H == 16
44
55
  assert Grid.cells() == 128
@@ -55,3 +66,7 @@ test "the same const name can live on two types"
55
66
  test "a struct can hold a constant instance of itself"
56
67
  assert Vec2.ZERO.x == 0.0
57
68
  assert Tile.origin().x == 0
69
+
70
+ test "a primitive holds a list constant and a static fn reading a sibling const"
71
+ assert Shelf.NAMES.length() == 3
72
+ assert Shelf.sidesTwice() == 8
plum-examples/error_propagation.plum CHANGED
@@ -2,6 +2,7 @@ import std/Int
2
2
  import std/Float
3
3
  import std/Str
4
4
  import std/List
5
+ import std/Map
5
6
 
6
7
  # `!T` is "a `T` or an error". An error is a value written `error.Name` (for
7
8
  # example `return error.NegativeNum`): it needs no declaration and carries no
@@ -120,6 +121,26 @@ fun errorText(s: Str) -> Str =
120
121
  return e.name()
121
122
  "none"
122
123
 
124
+ fun checkNonNeg(n: Int) -> error{Neg}!Unit =
125
+ if n < 0
126
+ return error.Neg
127
+
128
+ fun passCheck(n: Int) -> error{Neg}!Unit =
129
+ try checkNonNeg(n)
130
+
131
+ fun runChecked(n: Int) -> error{Neg}!Int =
132
+ try checkNonNeg(n)
133
+ n
134
+
135
+ fun negOrSeven(n: Int) -> error{Neg}!Int =
136
+ if n < 0
137
+ return error.Neg
138
+ n
139
+
140
+ fun lookup(m: Map[Int, error{Neg}!Int]) -> Int =
141
+ v := m.get(1) catch negOrSeven(-1)
142
+ v catch 0
143
+
123
144
  test "try operator unwraps the value and propagates it through two calls"
124
145
  assert sumTwo("2", "3")! == 5
125
146
 
@@ -189,3 +210,20 @@ test "a list of error unions nests one level per access"
189
210
  assert {second catch 0} == 0
190
211
  fallback := {results.get(9) catch parsePositive("5")}
191
212
  assert {fallback catch 0} == 5
213
+
214
+ test "a Unit error union propagates with try and is handled with catch"
215
+ assert {runChecked(4) catch 0} == 4
216
+ assert {runChecked(-1) catch 5} == 5
217
+ hits := 0
218
+ passCheck(1) catch e
219
+ hits = hits + 1
220
+ passCheck(-1) catch e
221
+ hits = hits + 10
222
+ assert hits == 10
223
+
224
+ test "a Map can hold error unions as values"
225
+ m := Map[Int, error{Neg}!Int]()
226
+ m.set(1, negOrSeven(7))
227
+ assert lookup(m) == 7
228
+ m.set(1, negOrSeven(-2))
229
+ assert lookup(m) == 0
plum-examples/functions.plum CHANGED
@@ -48,6 +48,12 @@ fun double(n: Int) -> Int =
48
48
  fun combineVariadic(prefix: Int, rest: ...Int) -> Int =
49
49
  prefix
50
50
 
51
+ fun weighted(nums: ...Int) -> Int =
52
+ total := 0
53
+ for i, v := range nums
54
+ total = total + i * v
55
+ total
56
+
51
57
  test "factorial runs correctly"
52
58
  assert factorial(5) == 120
53
59
 
@@ -65,3 +71,7 @@ test "sum all variadic int runs correctly"
65
71
 
66
72
  test "sum all variadic int with zero args runs correctly"
67
73
  assert sumAll() == 0
74
+
75
+ test "for over a variadic binds index and element"
76
+ assert weighted() == 0
77
+ assert weighted(5, 10, 20) == 50
plum-examples/match.plum CHANGED
@@ -264,6 +264,20 @@ fun bookInfo(book: Book) -> Str =
264
264
  Good(name1), Good(name2) => "Non-fiction book \"{title}\" praised by {name1} and {name2}"
265
265
  _, _ => "Non-fiction book \"{title}\" has mixed reviews"
266
266
 
267
+ record Full(n: Int)
268
+
269
+ enum Slot =
270
+ | Full
271
+ | Vacant
272
+
273
+ fun isVacant(s: Slot) -> Bool =
274
+ s == Vacant
275
+
276
+ fun slotKind(s: Slot) -> Str =
277
+ match s
278
+ Vacant => "vacant"
279
+ other => "full"
280
+
267
281
  test "nested constructor pattern matches and binds runs correctly"
268
282
  assert unwrapNested(Wrap(Present(5))) == 5
269
283
 
@@ -402,3 +416,9 @@ test "nested constructor patterns and a nested multi-subject match"
402
416
  mixed := NonFictionBook(title: "Shaky Claims", pages: 80, rating1: Good(name: "Alice"), rating2: Bad(name: "Carol"))
403
417
  assert bookInfo(good) == "Non-fiction book \"Hidden Facts\" praised by Alice and Bob"
404
418
  assert bookInfo(mixed) == "Non-fiction book \"Shaky Claims\" has mixed reviews"
419
+
420
+ test "a bare tag equals a member-enum value and a binding arm catches the rest"
421
+ assert isVacant(Vacant)
422
+ assert !isVacant(Full(n: 3))
423
+ assert slotKind(Vacant) == "vacant"
424
+ assert slotKind(Full(n: 1)) == "full"
plum-examples/structs_records.plum CHANGED
@@ -111,6 +111,8 @@ fun methodsTotal() -> Int =
111
111
  w := Wrapper(inner: c, tag: 1)
112
112
  c.getAge() + c.birthday() + w.innerAge()
113
113
 
114
+ record Slab(bounds: Rect, id: Int)
115
+
114
116
  test "struct assignment copies"
115
117
  a := Vec2(x: 1.0, y: 2.0)
116
118
  b := a
@@ -239,3 +241,15 @@ test "records built in a loop do not alias"
239
241
  # compile-time-fixed address; otherwise every iteration's `LoopBox(...)`
240
242
  # would alias the same memory and this would sum to 20 instead of 10.
241
243
  assert sumLoopBoxes() == 10
244
+
245
+ test "struct sub-fields assign in place through a record field"
246
+ p := Player(pos: Vec2(x: 1.0, y: 2.0), hp: 5)
247
+ p.pos.x = 8.0
248
+ assert p.pos.x == 8.0
249
+ assert p.pos.y == 2.0
250
+ assert p.hp == 5
251
+ s := Slab(bounds: Rect(pos: Vec2(x: 1.0, y: 2.0), size: Vec2(x: 3.0, y: 4.0)), id: 1)
252
+ s.bounds.size.y = 6.0
253
+ assert s.bounds.size.y == 6.0
254
+ assert s.bounds.size.x == 3.0
255
+ assert s.bounds.pos.y == 2.0