plum
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
|