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
b7071c9 1
import std/Option
73b5e55 2
import std/Bool
29313cb 3
import std/Str
ca5fd6f 4
import std/Number
73e4ff1 5
5f2f962 6
enum Point =
5f2f962 7
  | Point(x: Int, y: Int)
0e1801a 8
5f2f962 9
enum Named(ToStr) =
5f2f962 10
  | Named(name: Str)
0e1801a 11
73e4ff1 12
  fun toStr(self) -> Str =
73e4ff1 13
    self.name
73e4ff1 14
5f2f962 15
enum Box[T] =
5f2f962 16
  | Box(value: T)
0e1801a 17
0e1801a 18
trait Shape =
0e1801a 19
  area() -> Float
0e1801a 20
  perimeter() -> Float
0e1801a 21
73e4ff1 22
# Named distinctly from the real `Comparable`/`Ord` traits `libs/std/str.plum`
73e4ff1 23
# claims to (but never actually implements — see its own header comment) —
73e4ff1 24
# both are now genuinely reachable in the SAME merged program (`str.plum` is
73e4ff1 25
# an always-implicit prelude, see `plum-core::loader::loadAndMerge`), and
73e4ff1 26
# `checkTraitConformance` matches purely by bare trait name, so reusing
73e4ff1 27
# "Comparable" here would make ITS unrelated demo declaration the thing that
73e4ff1 28
# suddenly enforces (and fails) `Str`'s own long-standing, deliberately
73e4ff1 29
# unenforced claim.
73e4ff1 30
trait DemoComparable[T: Ord] =
0bb6882 31
  compareTo(other: T) -> Int
0e1801a 32
0e1801a 33
enum Color =
0e1801a 34
  | Red
0e1801a 35
  | Green
0e1801a 36
  | Blue
0e1801a 37
b1c3ec8 38
# A bare enum variant name can be used directly as a type: `v: Red` means
b1c3ec8 39
# "a `Color` value that is specifically the `Red` variant".
b1c3ec8 40
fun stringifyColor(v: Red) -> Str =
b1c3ec8 41
  "Red"
b1c3ec8 42
0fe3528 43
fun makeIntBox() -> Box =
2e28ecc 44
  Box(value: 5)
2e28ecc 45
0fe3528 46
fun makeStrBox() -> Box =
2e28ecc 47
  Box(value: "x")
a43f3af 48
5f2f962 49
# ---- record-shaped and sum-type enum regression tests ----
a43f3af 50
5f2f962 51
enum Cat =
5f2f962 52
  | Cat(name: Str, age: Int)
a43f3af 53
a43f3af 54
  fun getAge() -> Int =
a43f3af 55
    self.age
a43f3af 56
5f2f962 57
enum Dog =
5f2f962 58
  | Dog(name: Str, age: Int)
a43f3af 59
a43f3af 60
  fun getAge(self) -> Int =
a43f3af 61
    self.age
a43f3af 62
5f2f962 63
enum Pair =
5f2f962 64
  | Pair(a: Int, b: Int)
a43f3af 65
5f2f962 66
enum Wrapper =
5f2f962 67
  | Wrapper(inner: Pair, tag: Int)
a43f3af 68
5f2f962 69
enum LoopBox =
5f2f962 70
  | LoopBox(v: Int)
a43f3af 71
a43f3af 72
fun sumLoopBoxes() -> Int =
a43f3af 73
  total := 0
a43f3af 74
  for i := range 5
a43f3af 75
    b := LoopBox(v: i)
a43f3af 76
    total = total + b.v
a43f3af 77
  return total
a43f3af 78
a43f3af 79
enum Step(n: Int) =
a43f3af 80
  | ReadMin(10)
a43f3af 81
  | ReadMax(20)
a43f3af 82
a43f3af 83
  fun toNumber(self) -> Int =
a43f3af 84
    self.n
a43f3af 85
a43f3af 86
fun stepToNumber(s: Step) -> Int =
a43f3af 87
  match s
a43f3af 88
    ReadMin => 1
a43f3af 89
    ReadMax => 2
a43f3af 90
73e4ff1 91
fun unwrapOptionOr(o: Option[Int], default: Int) -> Int =
a43f3af 92
  match o
a43f3af 93
    Some(v) =>
a43f3af 94
      return v
a43f3af 95
    None =>
a43f3af 96
      return default
a43f3af 97
a43f3af 98
enum ShapeKind =
287b97c 99
  | Rect(Int, Int)
287b97c 100
  | Circle(Int)
a43f3af 101
a43f3af 102
fun area(s: ShapeKind) -> Int =
a43f3af 103
  match s
a43f3af 104
    Rect(w, h) =>
a43f3af 105
      return w * h
a43f3af 106
    Circle(r) =>
a43f3af 107
      return r * r
a43f3af 108
b1c3ec8 109
enum Vec2 =
b1c3ec8 110
  | Vec2(x: Int, y: Int)
b1c3ec8 111
b1c3ec8 112
enum ShapeWithFields =
b1c3ec8 113
  | CircleField(radius: Int)
b1c3ec8 114
  | SquareField(side: Int)
b1c3ec8 115
ca5fd6f 116
fun numberKind(n: Number) -> Str =
ca5fd6f 117
  n.kind()
ca5fd6f 118
5f2f962 119
enum OptionBox =
5f2f962 120
  | OptionBox(value: Option[Int])
a43f3af 121
a43f3af 122
  fun unwrap(default: Int) -> Int =
a43f3af 123
    match self.value
a43f3af 124
      Some(v) =>
a43f3af 125
        return v
a43f3af 126
      None =>
a43f3af 127
        return default
a43f3af 128
8de13fd 129
test "class field and method run correctly"
8de13fd 130
  c := Cat(name: "x", age: 7)
8de13fd 131
  assert c.getAge() == 7
8de13fd 132
8de13fd 133
test "nested method declaration runs correctly"
8de13fd 134
  d := Dog(name: "x", age: 7)
8de13fd 135
  assert d.getAge() == 7
8de13fd 136
8de13fd 137
test "nested class call runs correctly"
8de13fd 138
  w := Wrapper(inner: Pair(a: 11, b: 22), tag: 99)
8de13fd 139
  assert w.inner.b == 22
8de13fd 140
8de13fd 141
test "repeated class call in a loop does not alias"
8de13fd 142
  # Regression test: class instances are bump-allocated at *runtime* (via a
8de13fd 143
  # mutable wasm global), not at a compile-time-fixed address — otherwise
8de13fd 144
  # every iteration's `LoopBox(...)` would alias the same memory and this
8de13fd 145
  # would sum to 5*4=20 instead of 0+1+2+3+4=10.
8de13fd 146
  assert sumLoopBoxes() == 10
8de13fd 147
8de13fd 148
test "enum discriminant value field access runs correctly for each variant"
8de13fd 149
  assert ReadMin.toNumber() * 100 + ReadMax.toNumber() == 1020
8de13fd 150
8de13fd 151
test "enum discriminant value matches by variant name correctly"
8de13fd 152
  assert stepToNumber(ReadMin) * 10 + stepToNumber(ReadMax) == 12
8de13fd 153
8de13fd 154
test "payload variant construction compiles and runs"
8de13fd 155
  assert unwrapOptionOr(Some(7), 0) == 7
8de13fd 156
8de13fd 157
test "multi field variant construction compiles and runs"
8de13fd 158
  assert area(Rect(3, 4)) == 12
8de13fd 159
b1c3ec8 160
test "single-variant named-payload enum field access works like a class"
b1c3ec8 161
  # `Vec2` has exactly one variant, so its fields unambiguously describe
b1c3ec8 162
  # every `Vec2` value — `.x`/`.y` resolve directly, no `match` needed,
b1c3ec8 163
  # for both named and positional construction.
b1c3ec8 164
  named := Vec2(x: 1, y: 2)
b1c3ec8 165
  positional := Vec2(3, 4)
b1c3ec8 166
  assert named.x == 1
b1c3ec8 167
  assert named.y == 2
b1c3ec8 168
  assert positional.x == 3
b1c3ec8 169
  assert positional.y == 4
b1c3ec8 170
b1c3ec8 171
test "named-payload field access on a multi-variant enum value works via a checked downcast"
b1c3ec8 172
  # Unlike `Vec2` above, `ShapeWithFields` has more than one variant — `.field`
b1c3ec8 173
  # here isn't statically provable to always succeed the way it is on a
b1c3ec8 174
  # single-variant enum. It's still allowed because `radius`/`side` each
b1c3ec8 175
  # belong to exactly one variant (no ambiguity) — codegen compiles it as a
b1c3ec8 176
  # ref.cast down to that one variant's own struct, which would trap at
b1c3ec8 177
  # runtime if the value were ever the OTHER variant instead.
b1c3ec8 178
  c := CircleField(radius: 5)
b1c3ec8 179
  s := SquareField(side: 9)
b1c3ec8 180
  assert c.radius == 5
b1c3ec8 181
  assert s.side == 9
b1c3ec8 182
b1c3ec8 183
test "enum variant used directly as a type checks and runs correctly"
b1c3ec8 184
  assert stringifyColor(Red) == "Red"
b1c3ec8 185
ca5fd6f 186
test "a bare Int/Float value flows into a Number-typed param with no wrapper syntax"
ca5fd6f 187
  assert numberKind(5) == "Int"
ca5fd6f 188
  assert numberKind(2.5) == "Float"
ca5fd6f 189
ca5fd6f 190
test "a bare Int/Float value dispatches a method defined only on Number, via fallback"
ca5fd6f 191
  # `.kind()` isn't defined on `Int`/`Float` themselves — dispatch falls back
ca5fd6f 192
  # to `Number`, the enum that bare-wraps them, boxing `self` first.
ca5fd6f 193
  assert {5}.kind() == "Int"
ca5fd6f 194
  assert {2.5}.kind() == "Float"
ca5fd6f 195
a43f3af 196
test "enum class field construct and destructure runs correctly"
a43f3af 197
  b := OptionBox(value: Some(42))
a9a0147 198
  assert b.unwrap(0) == 42
a43f3af 199
13507fa 200
test "class field mutation and spread update run correctly"
13507fa 201
  p := Point(x: 1, y: 2)
13507fa 202
  p.x = 10
13507fa 203
  assert p.x == 10
13507fa 204
  assert p.y == 2
13507fa 205
  p2 := Point(..p, x: 100)
13507fa 206
  assert p2.x == 100
13507fa 207
  assert p2.y == 2
13507fa 208
  # the spread source is untouched by the update it fed
13507fa 209
  assert p.x == 10
13507fa 210
13507fa 211
test "single-variant named-payload enum field mutation and spread update run correctly"
13507fa 212
  v := Vec2(x: 1, y: 2)
13507fa 213
  v.x = 10
13507fa 214
  assert v.x == 10
13507fa 215
  assert v.y == 2
13507fa 216
  v2 := Vec2(..v, x: 100)
13507fa 217
  assert v2.x == 100
13507fa 218
  assert v2.y == 2
13507fa 219
  assert v.x == 10
13507fa 220
13507fa 221
test "multi-variant enum field mutation and spread update run correctly"
13507fa 222
  # Same checked-downcast idiom `.field` reads already use on a
13507fa 223
  # multi-variant enum's uniquely-owned field name (see the test above
13507fa 224
  # about `ShapeWithFields`) — traps at runtime if the value is ever the
13507fa 225
  # OTHER variant, no static proof required.
13507fa 226
  c := CircleField(radius: 5)
13507fa 227
  c.radius = 9
13507fa 228
  assert c.radius == 9
13507fa 229
  c2 := CircleField(..c, radius: 50)
13507fa 230
  assert c2.radius == 50
13507fa 231
  assert c.radius == 9
13507fa 232
a43f3af 233
test "gc type registry produces a well formed type section alongside bump allocator codegen"
a43f3af 234
  # Task 1 Step 5 of the wasm-gc migration plan: the wasm-gc type registry
a43f3af 235
  # emits a well-formed type section — a struct type per class, a
a43f3af 236
  # supertype+subtypes set per enum, and a shared Str array type — even
a43f3af 237
  # though the rest of a compiled module uses a different representation.
a43f3af 238
  # This proves the still-untouched bump-allocator codegen actually runs
a43f3af 239
  # correctly alongside it, not just that it compiles.
a43f3af 240
  c := Cat(name: "x", age: 7)
a9a0147 241
  assert c.getAge() == 7