plum

#treesitter#compiler#wasm

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