plum
git clone https://git.pyrossh.dev/plum
A statically typed, imperative programming language inspired by rust, python
73b5e55
— Peter John
2026-09-04T20:39:27+05:30
feat(plum): make Bool an ordinary import-gated enum, no hardcoded prelude
- examples/basics.plum +1 -0
- examples/closures.plum +1 -0
- examples/control_flow.plum +1 -0
- examples/dop_visitor.plum +1 -0
- examples/functions.plum +1 -0
- examples/io.plum +1 -0
- examples/match.plum +1 -0
- examples/methods.plum +1 -0
- examples/oop_visitor.plum +1 -0
- examples/strings.plum +2 -0
- examples/testing.plum +1 -0
- examples/types.plum +1 -0
- libs/std/Array.plum +1 -0
- libs/std/Buffer.plum +1 -0
- libs/std/Byte.plum +1 -0
- libs/std/ByteSlice.plum +1 -0
- libs/std/Err.plum +1 -0
- libs/std/Float.plum +1 -0
- libs/std/Int.plum +1 -0
- libs/std/Json.plum +1 -0
- libs/std/List.plum +1 -0
- libs/std/Map.plum +1 -0
- libs/std/Option.plum +1 -0
- libs/std/Result.plum +1 -0
- libs/std/Str.plum +1 -0
- libs/std/Time.plum +1 -0
- libs/std/Uuid.plum +1 -0
- libs/std/encoding/Base64.plum +1 -0
- libs/std/http.plum +1 -0
- libs/std/os.plum +1 -0
- libs/std/testing.plum +1 -0
- plum-checker/src/lib.rs +22 -22
- plum-checker/tests/checker_tests.rs +17 -1
- plum-checker/tests/examples_test.rs +12 -18
- plum-wasm-codegen/src/lib.rs +5 -21
- plum-wasm-codegen/tests/codegen_tests.rs +16 -3
examples/basics.plum
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
module basics
|
|
2
|
+
import std/Bool
|
|
2
3
|
|
|
3
4
|
MAX_RETRIES = 3
|
|
4
5
|
GOLDEN_RATIO = 1.61803
|
examples/closures.plum
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import std/Str
|
|
2
|
+
import std/Bool
|
|
2
3
|
|
|
3
4
|
fun each(cb: fn(Int) -> Int) -> Int =
|
|
4
5
|
cb(5)
|
examples/control_flow.plum
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import std/Str
|
|
2
|
+
import std/Bool
|
|
2
3
|
|
|
3
4
|
fun loopSum(limit: Int) -> Int =
|
|
4
5
|
total := 0
|
examples/dop_visitor.plum
CHANGED
|
@@ -37,6 +37,7 @@
|
|
|
37
37
|
# for anything that needs to treat the shapes polymorphically.
|
|
38
38
|
|
|
39
39
|
import std/os
|
|
40
|
+
import std/Bool
|
|
40
41
|
|
|
41
42
|
enum Rating =
|
|
42
43
|
| Good(name: Str)
|
examples/functions.plum
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import std/Str
|
|
2
|
+
import std/Bool
|
|
2
3
|
|
|
3
4
|
fun addInts(a: Int, b: Int) -> Int =
|
|
4
5
|
a + b
|
examples/io.plum
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import std/Str
|
|
2
|
+
import std/Bool
|
|
2
3
|
|
|
3
4
|
# `printLn` mirrors `libs/std/os.plum`'s declaration; redeclared locally (rather
|
|
4
5
|
# than `import std/os`) so this stays a self-contained example for the
|
examples/match.plum
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import std/Option
|
|
2
|
+
import std/Bool
|
|
2
3
|
|
|
3
4
|
enum Color =
|
|
4
5
|
| Red
|
examples/methods.plum
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import std/Str
|
|
2
|
+
import std/Bool
|
|
2
3
|
|
|
3
4
|
type Cat =
|
|
4
5
|
name: Str
|
examples/oop_visitor.plum
CHANGED
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
# which is a real cost specific to this language.
|
|
31
31
|
|
|
32
32
|
import std/os
|
|
33
|
+
import std/Bool
|
|
33
34
|
|
|
34
35
|
enum Rating =
|
|
35
36
|
| Good(name: Str)
|
examples/strings.plum
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import std/Bool
|
|
2
|
+
|
|
1
3
|
fun greet(name: Str) -> Str =
|
|
2
4
|
"Hello, {name}!"
|
|
3
5
|
|
examples/testing.plum
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import std/Str
|
|
2
|
+
import std/Bool
|
|
2
3
|
|
|
3
4
|
fun add(a: Int, b: Int) -> Int =
|
|
4
5
|
a + b
|
examples/types.plum
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import std/Option
|
|
2
|
+
import std/Bool
|
|
2
3
|
|
|
3
4
|
type Point =
|
|
4
5
|
x: Int
|
libs/std/Array.plum
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
module std
|
|
2
|
+
import std/Bool
|
|
2
3
|
|
|
3
4
|
# A fixed-length, O(1)-indexable array of `T`, backed directly by a wasm-gc
|
|
4
5
|
# `array<anyref>` — every `Array[T]` specialization (`Array$Int`, `Array$Str`,
|
libs/std/Buffer.plum
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
module std
|
|
2
2
|
import std/ByteSlice
|
|
3
3
|
import std/Str
|
|
4
|
+
import std/Bool
|
|
4
5
|
|
|
5
6
|
# A Buffer is a growable, mutable sequence of bytes for efficiently building
|
|
6
7
|
# up a `Str` piece by piece — modeled on Go's `bytes.Buffer`. Backed by a
|
libs/std/Byte.plum
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
module std
|
|
2
2
|
import std/Int
|
|
3
|
+
import std/Bool
|
|
3
4
|
|
|
4
5
|
# Byte is an unsigned 8-bit value (0-255) — Plum's counterpart to Go's
|
|
5
6
|
# `byte`/`uint8`. `Byte(x)` converts an `Int` (wrapping, not trapping, if `x`
|
libs/std/ByteSlice.plum
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
module std
|
|
2
2
|
import std/Byte
|
|
3
3
|
import std/Str
|
|
4
|
+
import std/Bool
|
|
4
5
|
|
|
5
6
|
# ByteSlice is `[]Byte` — Plum's counterpart to Go's byte slice: a
|
|
6
7
|
# fixed-length, mutable sequence of raw bytes backed directly by a wasm-gc
|
libs/std/Err.plum
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
module std
|
|
2
|
+
import std/Bool
|
|
2
3
|
|
|
3
4
|
# This is used to represent an error value across the language
|
|
4
5
|
trait Err =
|
libs/std/Float.plum
CHANGED
|
@@ -2,6 +2,7 @@ module std
|
|
|
2
2
|
import std/Int
|
|
3
3
|
import std/Result
|
|
4
4
|
import std/Str
|
|
5
|
+
import std/Bool
|
|
5
6
|
|
|
6
7
|
E = 2.718281828459045f # Euler's number, the base of natural logarithms, e, https://oeis.org/A001113
|
|
7
8
|
LN10 = 2.302585092994046f # The natural logarithm of 10, https://oeis.org/A002392
|
libs/std/Int.plum
CHANGED
|
@@ -2,6 +2,7 @@ module std
|
|
|
2
2
|
import std/Float
|
|
3
3
|
import std/Result
|
|
4
4
|
import std/Str
|
|
5
|
+
import std/Bool
|
|
5
6
|
|
|
6
7
|
MIN_VALUE = -0x8000_0000_0000_0000 # Lowest value of Int
|
|
7
8
|
MAX_VALUE = 0x7FFF_FFFF_FFFF_FFFF # Highest value of Int
|
libs/std/Json.plum
CHANGED
|
@@ -9,6 +9,7 @@ import std/Float
|
|
|
9
9
|
import std/Str
|
|
10
10
|
import std/Buffer
|
|
11
11
|
import std/Err
|
|
12
|
+
import std/Bool
|
|
12
13
|
|
|
13
14
|
# A parsed JSON value. Numbers are always stored as `Float` (JSON doesn't
|
|
14
15
|
# distinguish integers from floats the way Plum does), and `JsonList`/
|
libs/std/List.plum
CHANGED
|
@@ -3,6 +3,7 @@ module std
|
|
|
3
3
|
import std/Option
|
|
4
4
|
import std/Buffer
|
|
5
5
|
import std/Int
|
|
6
|
+
import std/Bool
|
|
6
7
|
|
|
7
8
|
# A node stores the data in a list and contains pointers to the previous and next sibling nodes
|
|
8
9
|
type Node[T] =
|
libs/std/Map.plum
CHANGED
|
@@ -2,6 +2,7 @@ module std
|
|
|
2
2
|
import std/List
|
|
3
3
|
import std/Option
|
|
4
4
|
import std/Array
|
|
5
|
+
import std/Bool
|
|
5
6
|
|
|
6
7
|
# Any type usable as a `Map` key needs a `hash`, so keys landing in different
|
|
7
8
|
# buckets (`hash(k) % BUCKET_COUNT`) can be told apart in O(1) without a full
|
libs/std/Option.plum
CHANGED
|
@@ -2,6 +2,7 @@ module std
|
|
|
2
2
|
|
|
3
3
|
import std/Result
|
|
4
4
|
import std/Str
|
|
5
|
+
import std/Bool
|
|
5
6
|
|
|
6
7
|
# Option[T] represents a value that may or may not be present — Plum's
|
|
7
8
|
# counterpart to Rust's `Option`/Go's "zero value or ok bool" idiom.
|
libs/std/Result.plum
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
module std
|
|
2
2
|
import std/Option
|
|
3
3
|
import std/Str
|
|
4
|
+
import std/Bool
|
|
4
5
|
|
|
5
6
|
# Result[T, E] represents either success (`Ok`, carrying a `T`) or failure
|
|
6
7
|
# (`Err`, carrying an `E`) — used throughout std for fallible operations
|
libs/std/Str.plum
CHANGED
|
@@ -2,6 +2,7 @@ module std
|
|
|
2
2
|
|
|
3
3
|
import std/List
|
|
4
4
|
import std/Buffer
|
|
5
|
+
import std/Bool
|
|
5
6
|
|
|
6
7
|
# Any type that can be converted to a str needs to implement this trait
|
|
7
8
|
trait ToStr =
|
libs/std/Time.plum
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
module std
|
|
2
2
|
import std/Int
|
|
3
3
|
import std/Str
|
|
4
|
+
import std/Bool
|
|
4
5
|
|
|
5
6
|
# A raw, host-provided wall-clock reading: milliseconds since the Unix epoch.
|
|
6
7
|
extern fun rawNowMillis() -> Int
|
libs/std/Uuid.plum
CHANGED
|
@@ -2,6 +2,7 @@ module std
|
|
|
2
2
|
import std/Int
|
|
3
3
|
import std/Result
|
|
4
4
|
import std/Str
|
|
5
|
+
import std/Bool
|
|
5
6
|
|
|
6
7
|
# A UUID, stored as its canonical 36-character
|
|
7
8
|
# `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx` text form.
|
libs/std/encoding/Base64.plum
CHANGED
|
@@ -2,6 +2,7 @@ module std
|
|
|
2
2
|
import std/Str
|
|
3
3
|
import std/Buffer
|
|
4
4
|
import std/Option
|
|
5
|
+
import std/Bool
|
|
5
6
|
|
|
6
7
|
# The Base64 package contains support for doing Base64 binary-to-text encodings.
|
|
7
8
|
#
|
libs/std/http.plum
CHANGED
|
@@ -7,6 +7,7 @@ import std/Map
|
|
|
7
7
|
import std/Option
|
|
8
8
|
import std/Result
|
|
9
9
|
import std/Str
|
|
10
|
+
import std/Bool
|
|
10
11
|
|
|
11
12
|
# An HTTP response: status code, response headers, and the raw response
|
|
12
13
|
# body as a `Str` (a byte array, so binary bodies round-trip intact).
|
libs/std/os.plum
CHANGED
|
@@ -3,6 +3,7 @@ import std/List
|
|
|
3
3
|
import std/Result
|
|
4
4
|
import std/Str
|
|
5
5
|
import std/Uuid
|
|
6
|
+
import std/Bool
|
|
6
7
|
|
|
7
8
|
# NOTE: the original file modeled `stdin`/`stdout`/`stderr` as bare top-level
|
|
8
9
|
# variable bindings and a `File(...)` constructor, but this language has no
|
libs/std/testing.plum
CHANGED
|
@@ -3,6 +3,7 @@ module std
|
|
|
3
3
|
import std/Option
|
|
4
4
|
import std/Result
|
|
5
5
|
import std/List
|
|
6
|
+
import std/Bool
|
|
6
7
|
|
|
7
8
|
# Small jest/rspec-style assertion helpers for use inside `test` blocks, e.g.
|
|
8
9
|
#
|
plum-checker/src/lib.rs
CHANGED
|
@@ -63,6 +63,13 @@ pub fn unify(t1: &PlumType, t2: &PlumType) -> Result<(), String> {
|
|
|
63
63
|
// the same real type spelled two different ways depending on how the
|
|
64
64
|
// checker reached it, so they must unify with each other too.
|
|
65
65
|
(PlumType::TStr, PlumType::TNamed(n)) | (PlumType::TNamed(n), PlumType::TStr) if n == "Str" => Ok(()),
|
|
66
|
+
// Same reasoning as `Str` above — `Bool` is an ordinary enum now
|
|
67
|
+
// (`enum Bool = | True | False` in `libs/std/Bool.plum`), so a bare
|
|
68
|
+
// `True`/`False`/`Ok(...)`-wrapped-variant reference can infer as
|
|
69
|
+
// `TNamed("Bool")` (via `EnumVariantInfo`/`bareVariantWrapType`)
|
|
70
|
+
// while a `Bool` type ANNOTATION resolves via `plumTypeFromName` to
|
|
71
|
+
// the dedicated `TBool` variant instead.
|
|
72
|
+
(PlumType::TBool, PlumType::TNamed(n)) | (PlumType::TNamed(n), PlumType::TBool) if n == "Bool" => Ok(()),
|
|
66
73
|
(PlumType::TByte, PlumType::TByte) => Ok(()),
|
|
67
74
|
(PlumType::TByteSlice, PlumType::TByteSlice) => Ok(()),
|
|
68
75
|
(PlumType::TUnit, PlumType::TUnit) => Ok(()),
|
|
@@ -158,10 +165,6 @@ pub fn buildGlobalTables(source: &ast::Source) -> (TypeEnv, ClassEnv, MethodEnv,
|
|
|
158
165
|
let mut methods: MethodEnv = BTreeMap::new();
|
|
159
166
|
let mut enum_variants: EnumVariants = BTreeMap::new();
|
|
160
167
|
let mut enum_params: EnumParams = BTreeMap::new();
|
|
161
|
-
// `Bool`'s variants are built in (see `inferExpr`'s TypeName handling) rather
|
|
162
|
-
// than requiring every source file to redeclare `enum Bool = | True | False`.
|
|
163
|
-
enum_variants.insert("True".to_string(), EnumVariantInfo { enum_name: "Bool".to_string(), tag: 1, field_types: vec![], field_names: vec![], values: vec![] });
|
|
164
|
-
enum_variants.insert("False".to_string(), EnumVariantInfo { enum_name: "Bool".to_string(), tag: 0, field_types: vec![], field_names: vec![], values: vec![] });
|
|
165
168
|
|
|
166
169
|
// First pass: register class fields, THEN enum variants, so a bare variant
|
|
167
170
|
// (`| FantasyBook`, no `[...]` payload) that names an already-declared
|
|
@@ -179,15 +182,6 @@ pub fn buildGlobalTables(source: &ast::Source) -> (TypeEnv, ClassEnv, MethodEnv,
|
|
|
179
182
|
}
|
|
180
183
|
for item in &source.items {
|
|
181
184
|
match item {
|
|
182
|
-
// `Bool` may be re-"declared" (`enum Bool = | True | False`) purely to
|
|
183
|
-
// give it a nesting site for methods (`and`/`or`/`parse`/...) — the
|
|
184
|
-
// language currently has no other way to attach a method to a builtin
|
|
185
|
-
// type (see `libs/std/int.plum`/`float.plum`'s own `type Int =`/`type
|
|
186
|
-
// Float =` for the same pattern). Re-registering its variants here
|
|
187
|
-
// would silently overwrite the hardcoded tags above with whatever
|
|
188
|
-
// order this declaration happens to list them in, flipping every
|
|
189
|
-
// `True`/`False` tag used throughout the rest of the codegen. Skip.
|
|
190
|
-
ast::Item::Enum(e) if e.name == "Bool" => {}
|
|
191
185
|
ast::Item::Enum(e) => {
|
|
192
186
|
let shared_field_types: Vec<PlumType> = e.params.iter()
|
|
193
187
|
.map(|p| plumTypeFromAst(&p.ty))
|
|
@@ -1015,15 +1009,21 @@ pub fn inferExpr(expr: &ast::Expr, env: &TypeEnv, ctx: &CheckCtx) -> Result<Plum
|
|
|
1015
1009
|
// const (`PI`, `MAX_VALUE`, ...), since there's no separate "constant"
|
|
1016
1010
|
// token. Prefer a matching const's real type over the enum-variant/
|
|
1017
1011
|
// unmodeled-type-name fallbacks below.
|
|
1018
|
-
ast::Expr::TypeName(n) => match
|
|
1012
|
+
ast::Expr::TypeName(n) => match lookup(env, n) {
|
|
1019
|
-
"True" | "False" => Ok(PlumType::TBool),
|
|
1020
|
-
_ => match lookup(env, n) {
|
|
1021
|
-
|
|
1013
|
+
Ok(ty) if !matches!(ty, PlumType::TVar(_)) => Ok(ty),
|
|
1022
|
-
|
|
1014
|
+
_ => match ctx.enum_variants.get(n) {
|
|
1015
|
+
// `Bool` normalizes to the dedicated `TBool` variant (not
|
|
1016
|
+
// `TNamed("Bool")`) — `True`/`False` only resolve here at all
|
|
1017
|
+
// once `enum Bool = | True | False` (`libs/std/Bool.plum`) is
|
|
1018
|
+
// actually imported, but every OTHER part of the checker AND
|
|
1019
|
+
// `plum-wasm-codegen` (which reuses `inferExpr` via
|
|
1020
|
+
// `inferLocalType`) still branches on the literal `TBool`
|
|
1021
|
+
// variant, not a generic `TNamed` name — normalizing here
|
|
1022
|
+
// keeps that working without patching every such site.
|
|
1023
|
+
Some(info) if info.enum_name == "Bool" => Ok(PlumType::TBool),
|
|
1023
|
-
|
|
1024
|
+
Some(info) => Ok(PlumType::TNamed(info.enum_name.clone())),
|
|
1024
|
-
|
|
1025
|
+
// Unmodeled/builtin type name: allow, codegen will catch.
|
|
1025
|
-
|
|
1026
|
+
None => Ok(PlumType::TNamed(n.to_string())),
|
|
1026
|
-
},
|
|
1027
1027
|
},
|
|
1028
1028
|
},
|
|
1029
1029
|
ast::Expr::Paren(inner) => inferExpr(inner, env, ctx),
|
plum-checker/tests/checker_tests.rs
CHANGED
|
@@ -114,7 +114,7 @@ fn typeMismatchInBinaryOpIsError() {
|
|
|
114
114
|
|
|
115
115
|
#[test]
|
|
116
116
|
fn boolLiteralTrueFalseAreBool() {
|
|
117
|
-
let src = "
|
|
117
|
+
let src = "enum Bool =\n | True\n | False\n\nfun isTrue() -> Bool =\n True\n";
|
|
118
118
|
let source = parse(src);
|
|
119
119
|
assert!(checkSource(&source).is_ok(), "expected Ok, got {:?}", checkSource(&source).err());
|
|
120
120
|
}
|
|
@@ -490,6 +490,10 @@ fun makeStrBox() -> Box =
|
|
|
490
490
|
#[test]
|
|
491
491
|
fn genericFunctionCalledWithDifferentConcreteTypesPerSiteTypeChecks() {
|
|
492
492
|
let src = "\
|
|
493
|
+
enum Bool =
|
|
494
|
+
| True
|
|
495
|
+
| False
|
|
496
|
+
|
|
493
497
|
fun wrap(value: T) -> Bool =
|
|
494
498
|
True
|
|
495
499
|
|
|
@@ -507,6 +511,10 @@ fun useStr() -> Bool =
|
|
|
507
511
|
#[test]
|
|
508
512
|
fn genericFunctionWithTwoIndependentTypeParamsTypeChecks() {
|
|
509
513
|
let src = "\
|
|
514
|
+
enum Bool =
|
|
515
|
+
| True
|
|
516
|
+
| False
|
|
517
|
+
|
|
510
518
|
fun pair(first: T, second: U) -> Bool =
|
|
511
519
|
True
|
|
512
520
|
|
|
@@ -730,6 +738,10 @@ fun use() -> Int =
|
|
|
730
738
|
#[test]
|
|
731
739
|
fn closureLiteralInfersAsAFunctionType() {
|
|
732
740
|
let src = "\
|
|
741
|
+
enum Bool =
|
|
742
|
+
| True
|
|
743
|
+
| False
|
|
744
|
+
|
|
733
745
|
fun useClosure() -> Bool =
|
|
734
746
|
cb = |v|
|
|
735
747
|
True
|
|
@@ -798,6 +810,10 @@ type Cat =
|
|
|
798
810
|
#[test]
|
|
799
811
|
fn closurePassedToFnValueTypedParamTypeChecks() {
|
|
800
812
|
let src = "\
|
|
813
|
+
enum Bool =
|
|
814
|
+
| True
|
|
815
|
+
| False
|
|
816
|
+
|
|
801
817
|
fun each(cb: fn(Int) -> Bool) -> Bool =
|
|
802
818
|
cb(5)
|
|
803
819
|
|
plum-checker/tests/examples_test.rs
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
#![allow(non_snake_case)]
|
|
2
2
|
use plum_checker::checkSource;
|
|
3
|
-
use plum_core::AstParser;
|
|
4
3
|
|
|
5
4
|
fn examplesDir() -> std::path::PathBuf {
|
|
6
5
|
std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("../examples")
|
|
7
6
|
}
|
|
8
7
|
|
|
8
|
+
fn libPath() -> std::path::PathBuf {
|
|
9
|
+
std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("../libs")
|
|
10
|
+
}
|
|
11
|
+
|
|
9
12
|
fn exampleFiles() -> Vec<std::path::PathBuf> {
|
|
10
13
|
let mut files: Vec<_> = std::fs::read_dir(examplesDir())
|
|
11
14
|
.expect("examples/ directory should exist")
|
|
@@ -18,26 +21,17 @@ fn exampleFiles() -> Vec<std::path::PathBuf> {
|
|
|
18
21
|
files
|
|
19
22
|
}
|
|
20
23
|
|
|
21
|
-
/// Every example must
|
|
24
|
+
/// Every example must load (resolving its own `import`s against `../libs`,
|
|
25
|
+
/// the real compilation path — several examples now genuinely `import` real
|
|
26
|
+
/// stdlib types like `std/Bool`/`std/Str`, so a bare single-file parse would
|
|
27
|
+
/// leave those names undeclared) and pass `checkSource`. These files exist
|
|
22
|
-
///
|
|
28
|
+
/// specifically to prove each piece of the currently-supported grammar
|
|
23
|
-
///
|
|
29
|
+
/// surface actually works end to end, not just in isolated unit tests.
|
|
24
30
|
#[test]
|
|
25
31
|
fn everyExampleParsesAndTypeChecks() {
|
|
26
|
-
let mut parser = tree_sitter::Parser::new();
|
|
27
|
-
parser.set_language(&tree_sitter_plum::LANGUAGE.into()).unwrap();
|
|
28
|
-
|
|
29
32
|
for path in exampleFiles() {
|
|
33
|
+
let source = plum_core::loadAndMerge(&path, &libPath())
|
|
30
|
-
|
|
34
|
+
.unwrap_or_else(|e| panic!("failed to load {}: {}", path.display(), e));
|
|
31
|
-
let tree = parser.parse(&src, None).unwrap_or_else(|| panic!("failed to parse {}", path.display()));
|
|
32
|
-
assert!(
|
|
33
|
-
!tree.root_node().has_error(),
|
|
34
|
-
"{} has a parse error:\n{}",
|
|
35
|
-
path.display(),
|
|
36
|
-
tree.root_node().to_sexp()
|
|
37
|
-
);
|
|
38
|
-
|
|
39
|
-
let ap = AstParser::new(&src);
|
|
40
|
-
let source = ap.parseSource(tree.root_node());
|
|
41
35
|
let result = checkSource(&source);
|
|
42
36
|
assert!(
|
|
43
37
|
result.is_ok(),
|
plum-wasm-codegen/src/lib.rs
CHANGED
|
@@ -742,29 +742,13 @@ fn buildGcTypeRegistry(
|
|
|
742
742
|
}
|
|
743
743
|
}
|
|
744
744
|
|
|
745
|
-
// Bool is
|
|
745
|
+
// `Bool` is an ordinary enum now (`type Bool = | True | False` in
|
|
746
|
-
// `ast::Item::Enum` of its own (see `docs/superpowers/plans/2026-07-25-wasm-gc-migration.md`'s
|
|
747
|
-
//
|
|
746
|
+
// `libs/std/Bool.plum`) — registered exactly like any other enum
|
|
748
|
-
//
|
|
747
|
+
// declared in `source.items`, present only if actually imported.
|
|
749
|
-
let mut enum_decls: Vec<(String, Vec<String>)> =
|
|
748
|
+
let mut enum_decls: Vec<(String, Vec<String>)> = Vec::new();
|
|
750
|
-
vec![("Bool".to_string(), vec!["False".to_string(), "True".to_string()])];
|
|
751
749
|
for item in &source.items {
|
|
752
|
-
// A source file may re-"declare" `enum Bool = | True | False` purely to
|
|
753
|
-
// give it a nesting site for methods (no other way exists to attach a
|
|
754
|
-
// method to a builtin type) — see the identical skip, with the full
|
|
755
|
-
// rationale, in `plum-checker`'s `buildGlobalTables`. Registering it
|
|
756
|
-
// again here would give Bool a SECOND, orphaned GC struct (the first,
|
|
757
|
-
// hardcoded one is still referenced by every OTHER already-registered
|
|
758
|
-
// slot/type by index) and — worse — since slot assignment for a
|
|
759
|
-
// specialized generic enum with a `Bool` field (e.g. `Result[Bool,
|
|
760
|
-
// Str]`) resolves "Bool" by NAME at the point it's compiled, later
|
|
761
|
-
// duplicate registrations can leave that field pointing at whichever
|
|
762
|
-
// Bool slot was assigned last, an index that isn't guaranteed to
|
|
763
|
-
// satisfy wasm-gc's "supertypes before subtypes" ordering rule.
|
|
764
750
|
if let ast::Item::Enum(e) = item {
|
|
765
|
-
if e.name != "Bool" {
|
|
766
|
-
|
|
751
|
+
enum_decls.push((e.name.clone(), e.variants.iter().map(|v| v.name.clone()).collect()));
|
|
767
|
-
}
|
|
768
752
|
}
|
|
769
753
|
}
|
|
770
754
|
for (enum_name, variant_names) in &enum_decls {
|
plum-wasm-codegen/tests/codegen_tests.rs
CHANGED
|
@@ -47,6 +47,10 @@ fn outputValidates() {
|
|
|
47
47
|
#[test]
|
|
48
48
|
fn factorialCompiles() {
|
|
49
49
|
let src = "\
|
|
50
|
+
enum Bool =
|
|
51
|
+
| True
|
|
52
|
+
| False
|
|
53
|
+
|
|
50
54
|
fun factorial(x: Int) -> Int =
|
|
51
55
|
if x < 2
|
|
52
56
|
return 1
|
|
@@ -92,6 +96,10 @@ fn readStrResult(store: &mut wasmtime::Store<()>, val: &wasmtime::Val) -> String
|
|
|
92
96
|
#[test]
|
|
93
97
|
fn testBlocksCompileAndRun() {
|
|
94
98
|
let src = "\
|
|
99
|
+
enum Bool =
|
|
100
|
+
| True
|
|
101
|
+
| False
|
|
102
|
+
|
|
95
103
|
fun add(a: Int, b: Int) -> Int =
|
|
96
104
|
a + b
|
|
97
105
|
|
|
@@ -166,8 +174,9 @@ fn assertValid(src: &str) -> Vec<u8> {
|
|
|
166
174
|
|
|
167
175
|
#[test]
|
|
168
176
|
fn boolLiteralsCompile() {
|
|
177
|
+
let boolDecl = "enum Bool =\n | True\n | False\n\n";
|
|
169
|
-
assertValid("fun main() -> Bool =\n True\n");
|
|
178
|
+
assertValid(&format!("{}fun main() -> Bool =\n True\n", boolDecl));
|
|
170
|
-
assertValid("fun main() -> Bool =\n False\n");
|
|
179
|
+
assertValid(&format!("{}fun main() -> Bool =\n False\n", boolDecl));
|
|
171
180
|
}
|
|
172
181
|
|
|
173
182
|
#[test]
|
|
@@ -252,7 +261,7 @@ fn matchInlineCaseBodyCompiles() {
|
|
|
252
261
|
|
|
253
262
|
#[test]
|
|
254
263
|
fn matchBoolVariantPatternCompiles() {
|
|
255
|
-
let src = "
|
|
264
|
+
let src = "enum Bool =\n | True\n | False\n\nfun main(a: Bool) -> Int =\n match a\n True =>\n return 1\n False =>\n return 0\n";
|
|
256
265
|
assertValid(src);
|
|
257
266
|
}
|
|
258
267
|
|
|
@@ -272,6 +281,10 @@ fn matchStringPatternIsAClearError() {
|
|
|
272
281
|
#[test]
|
|
273
282
|
fn assertTrapsOnFalse() {
|
|
274
283
|
let src_trap = "\
|
|
284
|
+
enum Bool =
|
|
285
|
+
| True
|
|
286
|
+
| False
|
|
287
|
+
|
|
275
288
|
fun check(n: Int) -> Int =
|
|
276
289
|
assert n > 0
|
|
277
290
|
n
|