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