plum
git clone https://git.pyrossh.dev/plum
A statically typed, imperative programming language inspired by rust, python
plum-examples/types.plum
import std/Option
import std/Bool
import std/Str
import std/Number
enum Point =
| Point(x: Int, y: Int)
enum Named(ToStr) =
| Named(name: Str)
fun toStr(self) -> Str =
self.name
enum Box[T] =
| Box(value: T)
trait Shape =
area() -> Float
perimeter() -> Float
# Named distinctly from the real `Comparable`/`Ord` traits `libs/std/str.plum`
# claims to (but never actually implements — see its own header comment) —
# both are now genuinely reachable in the SAME merged program (`str.plum` is
# an always-implicit prelude, see `plum-core::loader::loadAndMerge`), and
# `checkTraitConformance` matches purely by bare trait name, so reusing
# "Comparable" here would make ITS unrelated demo declaration the thing that
# suddenly enforces (and fails) `Str`'s own long-standing, deliberately
# unenforced claim.
trait DemoComparable[T: Ord] =
compareTo(other: T) -> Int
enum Color =
| Red
| Green
| Blue
# A bare enum variant name can be used directly as a type: `v: Red` means
# "a `Color` value that is specifically the `Red` variant".
fun stringifyColor(v: Red) -> Str =
"Red"
fun makeIntBox() -> Box =
Box(value: 5)
fun makeStrBox() -> Box =
Box(value: "x")
# ---- record-shaped and sum-type enum regression tests ----
enum Cat =
| Cat(name: Str, age: Int)
fun getAge() -> Int =
self.age
enum Dog =
| Dog(name: Str, age: Int)
fun getAge(self) -> Int =
self.age
enum Pair =
| Pair(a: Int, b: Int)
enum Wrapper =
| Wrapper(inner: Pair, tag: Int)
enum LoopBox =
| LoopBox(v: Int)
fun sumLoopBoxes() -> Int =
total := 0
for i := range 5
b := LoopBox(v: i)
total = total + b.v
return total
enum Step(n: Int) =
| ReadMin(10)
| ReadMax(20)
fun toNumber(self) -> Int =
self.n
fun stepToNumber(s: Step) -> Int =
match s
ReadMin => 1
ReadMax => 2
fun unwrapOptionOr(o: Option[Int], default: Int) -> Int =
match o
Some(v) =>
return v
None =>
return default
enum ShapeKind =
| Rect(Int, Int)
| Circle(Int)
fun area(s: ShapeKind) -> Int =
match s
Rect(w, h) =>
return w * h
Circle(r) =>
return r * r
enum Vec2 =
| Vec2(x: Int, y: Int)
enum ShapeWithFields =
| CircleField(radius: Int)
| SquareField(side: Int)
fun numberKind(n: Number) -> Str =
n.kind()
enum OptionBox =
| OptionBox(value: Option[Int])
fun unwrap(default: Int) -> Int =
match self.value
Some(v) =>
return v
None =>
return default
test "class field and method run correctly"
c := Cat(name: "x", age: 7)
assert c.getAge() == 7
test "nested method declaration runs correctly"
d := Dog(name: "x", age: 7)
assert d.getAge() == 7
test "nested class call runs correctly"
w := Wrapper(inner: Pair(a: 11, b: 22), tag: 99)
assert w.inner.b == 22
test "repeated class call in a loop does not alias"
# Regression test: class instances are bump-allocated at *runtime* (via a
# mutable wasm global), not at a compile-time-fixed address — otherwise
# every iteration's `LoopBox(...)` would alias the same memory and this
# would sum to 5*4=20 instead of 0+1+2+3+4=10.
assert sumLoopBoxes() == 10
test "enum discriminant value field access runs correctly for each variant"
assert ReadMin.toNumber() * 100 + ReadMax.toNumber() == 1020
test "enum discriminant value matches by variant name correctly"
assert stepToNumber(ReadMin) * 10 + stepToNumber(ReadMax) == 12
test "payload variant construction compiles and runs"
assert unwrapOptionOr(Some(7), 0) == 7
test "multi field variant construction compiles and runs"
assert area(Rect(3, 4)) == 12
test "single-variant named-payload enum field access works like a class"
# `Vec2` has exactly one variant, so its fields unambiguously describe
# every `Vec2` value — `.x`/`.y` resolve directly, no `match` needed,
# for both named and positional construction.
named := Vec2(x: 1, y: 2)
positional := Vec2(3, 4)
assert named.x == 1
assert named.y == 2
assert positional.x == 3
assert positional.y == 4
test "named-payload field access on a multi-variant enum value works via a checked downcast"
# Unlike `Vec2` above, `ShapeWithFields` has more than one variant — `.field`
# here isn't statically provable to always succeed the way it is on a
# single-variant enum. It's still allowed because `radius`/`side` each
# belong to exactly one variant (no ambiguity) — codegen compiles it as a
# ref.cast down to that one variant's own struct, which would trap at
# runtime if the value were ever the OTHER variant instead.
c := CircleField(radius: 5)
s := SquareField(side: 9)
assert c.radius == 5
assert s.side == 9
test "enum variant used directly as a type checks and runs correctly"
assert stringifyColor(Red) == "Red"
test "a bare Int/Float value flows into a Number-typed param with no wrapper syntax"
assert numberKind(5) == "Int"
assert numberKind(2.5) == "Float"
test "a bare Int/Float value dispatches a method defined only on Number, via fallback"
# `.kind()` isn't defined on `Int`/`Float` themselves — dispatch falls back
# to `Number`, the enum that bare-wraps them, boxing `self` first.
assert {5}.kind() == "Int"
assert {2.5}.kind() == "Float"
test "enum class field construct and destructure runs correctly"
b := OptionBox(value: Some(42))
assert b.unwrap(0) == 42
test "class field mutation and spread update run correctly"
p := Point(x: 1, y: 2)
p.x = 10
assert p.x == 10
assert p.y == 2
p2 := Point(..p, x: 100)
assert p2.x == 100
assert p2.y == 2
# the spread source is untouched by the update it fed
assert p.x == 10
test "single-variant named-payload enum field mutation and spread update run correctly"
v := Vec2(x: 1, y: 2)
v.x = 10
assert v.x == 10
assert v.y == 2
v2 := Vec2(..v, x: 100)
assert v2.x == 100
assert v2.y == 2
assert v.x == 10
test "multi-variant enum field mutation and spread update run correctly"
# Same checked-downcast idiom `.field` reads already use on a
# multi-variant enum's uniquely-owned field name (see the test above
# about `ShapeWithFields`) — traps at runtime if the value is ever the
# OTHER variant, no static proof required.
c := CircleField(radius: 5)
c.radius = 9
assert c.radius == 9
c2 := CircleField(..c, radius: 50)
assert c2.radius == 50
assert c.radius == 9
test "gc type registry produces a well formed type section alongside bump allocator codegen"
# Task 1 Step 5 of the wasm-gc migration plan: the wasm-gc type registry
# emits a well-formed type section — a struct type per class, a
# supertype+subtypes set per enum, and a shared Str array type — even
# though the rest of a compiled module uses a different representation.
# This proves the still-untouched bump-allocator codegen actually runs
# correctly alongside it, not just that it compiles.
c := Cat(name: "x", age: 7)
assert c.getAge() == 7