plum
git clone https://git.pyrossh.dev/plum
A statically typed, imperative programming language inspired by rust, python
401e713
— Peter John
2026-11-01T20:52:26+05:30
T8: migrate inline payload variants to record members; legacy syntax errors on
- plum-core/src/loader.rs +31 -0
- plum-core/tests/loader_test.rs +29 -0
- plum-examples/constants.plum +2 -4
- plum-examples/control_flow.plum +3 -3
- plum-examples/digit_names.plum +3 -1
- plum-examples/dop_visitor.plum +11 -9
- plum-examples/error_propagation.plum +3 -4
- plum-examples/functions.plum +3 -3
- plum-examples/match.plum +44 -26
- plum-examples/oop_visitor.plum +4 -3
- plum-examples/pattern_if.plum +8 -9
- plum-examples/shapes.plum +15 -6
- plum-examples/types.plum +8 -6
- plum-examples/values.plum +1 -2
- plum-wasm-codegen/tests/codegen_tests.rs +22 -34
- plum-wasm-codegen/tests/examples_test.rs +1 -1
plum-core/src/loader.rs
CHANGED
|
@@ -253,6 +253,37 @@ fn mergeItems(
|
|
|
253
253
|
names: &mut HashMap<String, PathBuf>,
|
|
254
254
|
prims: &mut Vec<PrimitiveDecl>,
|
|
255
255
|
) -> Result<(), String> {
|
|
256
|
+
// Removed forms (inline variant payloads, lone self-named variant).
|
|
257
|
+
// TODO(T9): remove Json exemption
|
|
258
|
+
if !file.to_string_lossy().ends_with("plum-std/Json.plum") {
|
|
259
|
+
// A payload-less self-named single variant (`enum Array[T] = | Array`)
|
|
260
|
+
// is an opaque builtin handle and stays legal.
|
|
261
|
+
let opaque: Vec<String> = source
|
|
262
|
+
.items
|
|
263
|
+
.iter()
|
|
264
|
+
.filter_map(|i| match i {
|
|
265
|
+
Item::Enum(e)
|
|
266
|
+
if e.variants.len() == 1
|
|
267
|
+
&& e.variants[0].name == e.name
|
|
268
|
+
&& e.variants[0].fields.is_empty()
|
|
269
|
+
&& e.variants[0].values.is_empty() =>
|
|
270
|
+
{
|
|
271
|
+
Some(format!(
|
|
272
|
+
"enum {0} has a single variant named {0} — write record {0}(...)",
|
|
273
|
+
e.name
|
|
274
|
+
))
|
|
275
|
+
}
|
|
276
|
+
_ => None,
|
|
277
|
+
})
|
|
278
|
+
.collect();
|
|
279
|
+
let legacy: Vec<String> = crate::unions::legacyErrors(source)
|
|
280
|
+
.into_iter()
|
|
281
|
+
.filter(|m| !opaque.contains(m))
|
|
282
|
+
.collect();
|
|
283
|
+
if !legacy.is_empty() {
|
|
284
|
+
return Err(legacy.join("\n"));
|
|
285
|
+
}
|
|
286
|
+
}
|
|
256
287
|
// Parse-time literal errors (out-of-range integers) of every merged file.
|
|
257
288
|
MERGED_LIT_ERRORS.with(|e| e.borrow_mut().extend(source.formErrors.iter().cloned()));
|
|
258
289
|
// Namespaces (a `primitive` whose name is no built-in) travel with the
|
plum-core/tests/loader_test.rs
CHANGED
|
@@ -378,3 +378,32 @@ fn outOfRangeLiteralInEntryOrImportSurvivesTheMerge() {
|
|
|
378
378
|
let merged = loadAndMerge(&entry, &lib_path).expect("loadAndMerge failed");
|
|
379
379
|
assert!(merged.formErrors.iter().any(|e| e.2.contains("out of range")), "{:?}", merged.formErrors);
|
|
380
380
|
}
|
|
381
|
+
|
|
382
|
+
#[test]
|
|
383
|
+
fn legacyInlinePayloadIsAnError() {
|
|
384
|
+
let dir = TempDir::new().unwrap();
|
|
385
|
+
let lib_path = dir.path().join("libs");
|
|
386
|
+
let entry = writeLibFile(
|
|
387
|
+
&lib_path,
|
|
388
|
+
"main",
|
|
389
|
+
"module fixtures\n\nenum Shape =\n | Circle(r: Unit)\n | Dot\n",
|
|
390
|
+
);
|
|
391
|
+
let err = loadAndMerge(&entry, &lib_path).unwrap_err();
|
|
392
|
+
assert_eq!(
|
|
393
|
+
err,
|
|
394
|
+
"inline variant payloads are removed: declare record Circle(...) and list Circle in enum Shape"
|
|
395
|
+
);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
#[test]
|
|
399
|
+
fn legacySingleSelfVariantIsAnError() {
|
|
400
|
+
let dir = TempDir::new().unwrap();
|
|
401
|
+
let lib_path = dir.path().join("libs");
|
|
402
|
+
let entry = writeLibFile(
|
|
403
|
+
&lib_path,
|
|
404
|
+
"main",
|
|
405
|
+
"module fixtures\n\nenum User =\n | User(name: Unit)\n",
|
|
406
|
+
);
|
|
407
|
+
let err = loadAndMerge(&entry, &lib_path).unwrap_err();
|
|
408
|
+
assert_eq!(err, "enum User has a single variant named User — write record User(...)");
|
|
409
|
+
}
|
plum-examples/constants.plum
CHANGED
|
@@ -20,8 +20,7 @@ primitive Grid =
|
|
|
20
20
|
fun cells() -> Int =
|
|
21
21
|
W * H
|
|
22
22
|
|
|
23
|
-
enum Tile =
|
|
24
|
-
|
|
23
|
+
record Tile(x: Int) =
|
|
25
24
|
|
|
26
25
|
W = 100
|
|
27
26
|
|
|
@@ -30,8 +29,7 @@ enum Tile =
|
|
|
30
29
|
fun origin() -> Tile =
|
|
31
30
|
Tile(x: 0)
|
|
32
31
|
|
|
33
|
-
|
|
32
|
+
record Wrap[T](v: T) =
|
|
34
|
-
| Wrap(v: T)
|
|
35
33
|
|
|
36
34
|
fun area(self) -> Int = Grid.W * Grid.H
|
|
37
35
|
|
plum-examples/control_flow.plum
CHANGED
|
@@ -61,9 +61,9 @@ fun describeMagnitude(n: Int) -> Int =
|
|
|
61
61
|
return 100
|
|
62
62
|
x => x * 2
|
|
63
63
|
|
|
64
|
-
enum TailOption =
|
|
64
|
+
enum TailOption = TailSome | TailNone
|
|
65
|
+
|
|
65
|
-
|
|
66
|
+
record TailSome(v: Int)
|
|
66
|
-
| TailNone
|
|
67
67
|
|
|
68
68
|
fun unwrapTailOr(o: TailOption, default: Int) -> Int =
|
|
69
69
|
match o
|
plum-examples/digit_names.plum
CHANGED
|
@@ -7,10 +7,12 @@ import std/Int
|
|
|
7
7
|
# `MAX`-style consts keep working.
|
|
8
8
|
|
|
9
9
|
enum V2 =
|
|
10
|
-
| A1
|
|
10
|
+
| A1
|
|
11
11
|
| B2
|
|
12
12
|
K1 = 40
|
|
13
13
|
|
|
14
|
+
record A1(n: Int)
|
|
15
|
+
|
|
14
16
|
error S1 = E1 | E2
|
|
15
17
|
|
|
16
18
|
fun fail(n: Int) -> S1!Int =
|
plum-examples/dop_visitor.plum
CHANGED
|
@@ -41,15 +41,17 @@ import std/Int
|
|
|
41
41
|
import std/Float
|
|
42
42
|
import std/Str
|
|
43
43
|
|
|
44
|
-
enum Rating =
|
|
44
|
+
enum Rating = Good | Bad
|
|
45
|
+
|
|
45
|
-
|
|
46
|
+
record Good(name: Str)
|
|
46
|
-
|
|
47
|
+
record Bad(name: Str)
|
|
47
|
-
|
|
48
|
+
|
|
48
|
-
enum Book =
|
|
49
|
+
enum Book = FantasyBook | ScifiBook | ChildrensTaleBook | NonFictionBook
|
|
50
|
+
|
|
49
|
-
|
|
51
|
+
record FantasyBook(title: Str, pages: Int, hasMythicalCreatures: Bool)
|
|
50
|
-
|
|
52
|
+
record ScifiBook(title: Str, pages: Int, theme: Str)
|
|
51
|
-
|
|
53
|
+
record ChildrensTaleBook(title: Str, pages: Int, moralLesson: Str)
|
|
52
|
-
|
|
54
|
+
record NonFictionBook(title: Str, pages: Int, rating1: Rating, rating2: Rating)
|
|
53
55
|
|
|
54
56
|
fun collectInterestingInfo(book: Book) -> Str =
|
|
55
57
|
match book
|
plum-examples/error_propagation.plum
CHANGED
|
@@ -97,10 +97,9 @@ fun describeAge(s: Str) -> Str =
|
|
|
97
97
|
"age {n}"
|
|
98
98
|
|
|
99
99
|
# A plain list of cases is an ordinary enum.
|
|
100
|
-
enum Failure =
|
|
100
|
+
enum Failure = Timeout | Refused | BadRequest
|
|
101
|
-
|
|
101
|
+
|
|
102
|
-
| Refused
|
|
103
|
-
|
|
102
|
+
record BadRequest(v: Str)
|
|
104
103
|
|
|
105
104
|
fun isRetryable(f: Failure) -> Bool =
|
|
106
105
|
match f
|
plum-examples/functions.plum
CHANGED
|
@@ -65,9 +65,9 @@ record GenericBox2[T](value: T) =
|
|
|
65
65
|
fun doubled(value: Int) -> Int =
|
|
66
66
|
identity(value) + identity(value)
|
|
67
67
|
|
|
68
|
-
enum Parcel[T] =
|
|
68
|
+
enum Parcel[T] = Delivered[T] | Lost
|
|
69
|
+
|
|
69
|
-
|
|
70
|
+
record Delivered[T](v: T)
|
|
70
|
-
| Lost
|
|
71
71
|
|
|
72
72
|
fun contentsOr(p: Parcel[Int], default: Int) -> Int =
|
|
73
73
|
match p
|
plum-examples/match.plum
CHANGED
|
@@ -32,9 +32,9 @@ fun describeColor(c: Color) -> Str =
|
|
|
32
32
|
|
|
33
33
|
# A dedicated, non-generic "maybe an Int", as opposed to the generic
|
|
34
34
|
# generic `Reading[T]` (used elsewhere in this file).
|
|
35
|
-
enum IntOpt =
|
|
35
|
+
enum IntOpt = Present | Absent
|
|
36
|
+
|
|
36
|
-
|
|
37
|
+
record Present(v: Int)
|
|
37
|
-
| Absent
|
|
38
38
|
|
|
39
39
|
fun describeOption(opt: IntOpt) -> Int =
|
|
40
40
|
match opt
|
|
@@ -46,33 +46,38 @@ fun main() -> Int =
|
|
|
46
46
|
|
|
47
47
|
# ---- pattern matching regression tests ----
|
|
48
48
|
|
|
49
|
-
enum Nested =
|
|
49
|
+
enum Nested = Wrap | Empty
|
|
50
|
+
|
|
50
|
-
|
|
51
|
+
record Wrap(v: IntOpt)
|
|
51
|
-
| Empty
|
|
52
52
|
|
|
53
53
|
fun unwrapNested(n: Nested) -> Int =
|
|
54
54
|
match n
|
|
55
55
|
Wrap(Present(v)) => v
|
|
56
56
|
Wrap(Absent) => -1
|
|
57
|
+
Wrap(_) => -2
|
|
57
58
|
Empty => 0
|
|
58
59
|
|
|
59
|
-
enum Reading[T] =
|
|
60
|
+
enum Reading[T] = Sample[T] | NoSignal
|
|
61
|
+
|
|
60
|
-
|
|
62
|
+
record Sample[T](v: T)
|
|
63
|
+
|
|
64
|
+
enum GenericBox[T] = GFull[T] | GEmpty
|
|
65
|
+
|
|
61
|
-
|
|
66
|
+
record GFull[T](v: T)
|
|
62
67
|
|
|
63
|
-
|
|
68
|
+
fun sampleOf(n: Int) -> Reading[Int] =
|
|
64
|
-
|
|
69
|
+
Sample(n)
|
|
65
|
-
| GEmpty
|
|
66
70
|
|
|
67
71
|
fun unwrapGenericBox(b: GenericBox[Reading[Int]]) -> Int =
|
|
68
72
|
match b
|
|
69
73
|
GFull(Sample(v)) => v
|
|
70
74
|
GFull(NoSignal) => -1
|
|
75
|
+
GFull(_) => -2
|
|
71
76
|
GEmpty => 0
|
|
72
77
|
|
|
73
|
-
enum RecOption =
|
|
78
|
+
enum RecOption = RSome | RNone
|
|
79
|
+
|
|
74
|
-
|
|
80
|
+
record RSome(v: RecOption)
|
|
75
|
-
| RNone
|
|
76
81
|
|
|
77
82
|
fun unwrapTwice(o: RecOption) -> Int =
|
|
78
83
|
match o
|
|
@@ -144,13 +149,17 @@ record Point(x: Int, y: Int) =
|
|
|
144
149
|
Point(_, _) => "elsewhere"
|
|
145
150
|
|
|
146
151
|
enum Shape =
|
|
147
|
-
| Circle
|
|
152
|
+
| Circle
|
|
148
|
-
| Square
|
|
153
|
+
| Square
|
|
149
154
|
|
|
150
155
|
fun measure(self) -> Int =
|
|
151
156
|
match self
|
|
152
157
|
Circle(Point(x, y)) => x + y
|
|
153
158
|
Square(Point(x, y)) => x * y
|
|
159
|
+
_ => 0
|
|
160
|
+
|
|
161
|
+
record Circle(v: Point)
|
|
162
|
+
record Square(v: Point)
|
|
154
163
|
|
|
155
164
|
# Named payload fields (`Ring(radius: Int)`) alongside the
|
|
156
165
|
# unnamed-positional-payload form (`Circle(Point)` above) — lets a
|
|
@@ -158,14 +167,23 @@ enum Shape =
|
|
|
158
167
|
# anonymous positional types. Pattern matching is unaffected either way
|
|
159
168
|
# (still positional, by declaration order — `Rect(w, h)`, not `Rect(w:, h:)`).
|
|
160
169
|
enum Figure =
|
|
161
|
-
| Ring
|
|
170
|
+
| Ring
|
|
162
|
-
| Rect
|
|
171
|
+
| Rect
|
|
163
172
|
|
|
164
173
|
fun area(self) -> Int =
|
|
165
174
|
match self
|
|
166
175
|
Ring(radius) => radius * radius
|
|
167
176
|
Rect(w, h) => w * h
|
|
168
177
|
|
|
178
|
+
record Ring(radius: Int)
|
|
179
|
+
record Rect(w: Int, h: Int)
|
|
180
|
+
|
|
181
|
+
fun measureOf(s: Shape) -> Int =
|
|
182
|
+
s.measure()
|
|
183
|
+
|
|
184
|
+
fun areaOf(f: Figure) -> Int =
|
|
185
|
+
f.area()
|
|
186
|
+
|
|
169
187
|
test "match example computes correctly"
|
|
170
188
|
assert main() == 5
|
|
171
189
|
|
|
@@ -176,7 +194,7 @@ test "nested constructor pattern mismatch falls through to next case runs correc
|
|
|
176
194
|
assert unwrapNested(Wrap(Absent)) == -1
|
|
177
195
|
|
|
178
196
|
test "nested constructor pattern against a specialized generic enum runs correctly"
|
|
179
|
-
assert unwrapGenericBox(GFull(
|
|
197
|
+
assert unwrapGenericBox(GFull(sampleOf(7))) == 7
|
|
180
198
|
|
|
181
199
|
test "doubly nested constructor pattern runs correctly"
|
|
182
200
|
assert unwrapTwice(RSome(RSome(RNone))) == 1
|
|
@@ -220,16 +238,16 @@ test "match on a plain class supports literal/wildcard sub-patterns and case fal
|
|
|
220
238
|
assert Point(x: 5, y: 5).classify() == "elsewhere"
|
|
221
239
|
|
|
222
240
|
test "a plain class nested inside an enum variant pattern destructures correctly"
|
|
223
|
-
assert Circle(Point(x: 3, y: 4))
|
|
241
|
+
assert measureOf(Circle(Point(x: 3, y: 4))) == 7
|
|
224
|
-
assert Square(Point(x: 3, y: 4))
|
|
242
|
+
assert measureOf(Square(Point(x: 3, y: 4))) == 12
|
|
225
243
|
|
|
226
244
|
test "named-payload enum variant constructs via named args and destructures by position"
|
|
227
|
-
assert Ring(radius: 5)
|
|
245
|
+
assert areaOf(Ring(radius: 5)) == 25
|
|
228
|
-
assert Rect(w: 3, h: 4)
|
|
246
|
+
assert areaOf(Rect(w: 3, h: 4)) == 12
|
|
229
247
|
|
|
230
248
|
test "named-payload enum variant still supports positional construction too"
|
|
231
|
-
assert Ring(5)
|
|
249
|
+
assert areaOf(Ring(5)) == 25
|
|
232
|
-
assert Rect(3, 4)
|
|
250
|
+
assert areaOf(Rect(3, 4)) == 12
|
|
233
251
|
|
|
234
252
|
test "a pattern name reused with different types in one fn"
|
|
235
253
|
r := Sample(1)
|
plum-examples/oop_visitor.plum
CHANGED
|
@@ -33,9 +33,10 @@ import std/Int
|
|
|
33
33
|
import std/Float
|
|
34
34
|
import std/Str
|
|
35
35
|
|
|
36
|
-
enum Rating =
|
|
36
|
+
enum Rating = Good | Bad
|
|
37
|
+
|
|
37
|
-
|
|
38
|
+
record Good(name: Str)
|
|
38
|
-
|
|
39
|
+
record Bad(name: Str)
|
|
39
40
|
|
|
40
41
|
record FantasyBook(title: Str, pages: Int, hasMythicalCreatures: Bool) =
|
|
41
42
|
|
plum-examples/pattern_if.plum
CHANGED
|
@@ -13,19 +13,18 @@ import std/Str
|
|
|
13
13
|
#
|
|
14
14
|
# A plain name over an optional (`x := opt`) keeps its meaning: bind when present.
|
|
15
15
|
|
|
16
|
-
enum Shape =
|
|
16
|
+
enum Shape = Circle | Rect | Dot
|
|
17
|
+
|
|
17
|
-
|
|
18
|
+
record Circle(radius: Float)
|
|
18
|
-
|
|
19
|
+
record Rect(w: Float, h: Float)
|
|
19
|
-
| Dot
|
|
20
20
|
|
|
21
21
|
struct Vec2(x: Int, y: Int)
|
|
22
22
|
|
|
23
|
-
enum Job =
|
|
23
|
+
enum Job = Idle | Busy
|
|
24
|
-
|
|
24
|
+
|
|
25
|
-
|
|
25
|
+
record Busy(left: Int)
|
|
26
26
|
|
|
27
|
-
enum Soldier =
|
|
28
|
-
|
|
27
|
+
record Soldier(hp: Int, shape: Shape)
|
|
29
28
|
|
|
30
29
|
fun area(s: Shape) -> Float =
|
|
31
30
|
if Circle(r) := s
|
plum-examples/shapes.plum
CHANGED
|
@@ -5,8 +5,8 @@ import std/Float
|
|
|
5
5
|
# A closed set of shapes as one enum: mixed kinds live in a single type and
|
|
6
6
|
# `match` picks the behavior per variant.
|
|
7
7
|
enum Shape =
|
|
8
|
-
| Circle
|
|
8
|
+
| Circle
|
|
9
|
-
| Rect
|
|
9
|
+
| Rect
|
|
10
10
|
|
|
11
11
|
fun area(self) -> Float =
|
|
12
12
|
match self
|
|
@@ -18,10 +18,19 @@ enum Shape =
|
|
|
18
18
|
Circle(r) => "circle"
|
|
19
19
|
Rect(w, h) => "rect"
|
|
20
20
|
|
|
21
|
+
record Circle(r: Float)
|
|
22
|
+
record Rect(w: Float, h: Float)
|
|
23
|
+
|
|
24
|
+
fun areaOf(s: Shape) -> Float =
|
|
25
|
+
s.area()
|
|
26
|
+
|
|
27
|
+
fun nameOf(s: Shape) -> Str =
|
|
28
|
+
s.name()
|
|
29
|
+
|
|
21
30
|
test "area dispatches on the variant"
|
|
22
|
-
assert Circle(r: 1.5)
|
|
31
|
+
assert areaOf(Circle(r: 1.5)) == 6.75
|
|
23
|
-
assert Rect(w: 2.0, h: 3.0)
|
|
32
|
+
assert areaOf(Rect(w: 2.0, h: 3.0)) == 6.0
|
|
24
33
|
|
|
25
34
|
test "name dispatches on the variant"
|
|
26
|
-
assert Circle(r: 1.0)
|
|
35
|
+
assert nameOf(Circle(r: 1.0)) == "circle"
|
|
27
|
-
assert Rect(w: 1.0, h: 1.0)
|
|
36
|
+
assert nameOf(Rect(w: 1.0, h: 1.0)) == "rect"
|
plum-examples/types.plum
CHANGED
|
@@ -70,9 +70,10 @@ fun stepToNumber(s: Step) -> Int =
|
|
|
70
70
|
fun unwrapOrDefault(o: Int?, default: Int) -> Int =
|
|
71
71
|
o ?: default
|
|
72
72
|
|
|
73
|
-
enum ShapeKind =
|
|
73
|
+
enum ShapeKind = Rect | Circle
|
|
74
|
+
|
|
74
|
-
|
|
75
|
+
record Rect(v0: Int, v1: Int)
|
|
75
|
-
|
|
76
|
+
record Circle(v: Int)
|
|
76
77
|
|
|
77
78
|
fun area(s: ShapeKind) -> Int =
|
|
78
79
|
match s
|
|
@@ -83,9 +84,10 @@ fun area(s: ShapeKind) -> Int =
|
|
|
83
84
|
|
|
84
85
|
record Vec2(x: Int, y: Int)
|
|
85
86
|
|
|
86
|
-
enum ShapeWithFields =
|
|
87
|
+
enum ShapeWithFields = CircleField | SquareField
|
|
88
|
+
|
|
87
|
-
|
|
89
|
+
record CircleField(radius: Int)
|
|
88
|
-
|
|
90
|
+
record SquareField(side: Int)
|
|
89
91
|
|
|
90
92
|
record MaybeBox(value: Int?) =
|
|
91
93
|
|
plum-examples/values.plum
CHANGED
|
@@ -36,8 +36,7 @@ struct Color(r: Int, g: Int, b: Int, a: Int)
|
|
|
36
36
|
|
|
37
37
|
struct Cell(pos: Vec2, solid: Bool, tint: Color)
|
|
38
38
|
|
|
39
|
-
enum Player =
|
|
40
|
-
|
|
39
|
+
record Player(pos: Vec2, hp: Int)
|
|
41
40
|
|
|
42
41
|
fun applyTo(v: Vec2, cb: fn(Vec2) -> Vec2) -> Vec2 =
|
|
43
42
|
cb(v)
|
plum-wasm-codegen/tests/codegen_tests.rs
CHANGED
|
@@ -230,8 +230,7 @@ fn floatArithmeticAndNegationCompile() {
|
|
|
230
230
|
#[test]
|
|
231
231
|
fn classFieldAndMethodCompile() {
|
|
232
232
|
let src = "\
|
|
233
|
-
enum Cat =
|
|
234
|
-
|
|
233
|
+
record Cat(name: Str, age: Int) =
|
|
235
234
|
|
|
236
235
|
fun getAge() -> Int =
|
|
237
236
|
self.age
|
|
@@ -246,11 +245,9 @@ fun makeCat() -> Int =
|
|
|
246
245
|
#[test]
|
|
247
246
|
fn nestedClassCallCompiles() {
|
|
248
247
|
let src = "\
|
|
249
|
-
enum Pair =
|
|
250
|
-
|
|
248
|
+
record Pair(a: Int, b: Int)
|
|
251
249
|
|
|
252
|
-
enum Wrapper =
|
|
253
|
-
|
|
250
|
+
record Wrapper(inner: Pair, tag: Int)
|
|
254
251
|
|
|
255
252
|
fun make() -> Int =
|
|
256
253
|
w = Wrapper(inner: Pair(a: 1, b: 2), tag: 9)
|
|
@@ -1113,14 +1110,17 @@ import std/Float
|
|
|
1113
1110
|
import std/List
|
|
1114
1111
|
|
|
1115
1112
|
enum Shape =
|
|
1116
|
-
| Circle
|
|
1113
|
+
| Circle
|
|
1117
|
-
| Square
|
|
1114
|
+
| Square
|
|
1118
1115
|
|
|
1119
1116
|
fun area(self) -> Float =
|
|
1120
1117
|
match self
|
|
1121
1118
|
Circle(r) => r * r * 3.0
|
|
1122
1119
|
Square(s) => s * s
|
|
1123
1120
|
|
|
1121
|
+
record Circle(r: Float)
|
|
1122
|
+
record Square(s: Float)
|
|
1123
|
+
|
|
1124
1124
|
fun total(xs: List[Shape]) -> Float =
|
|
1125
1125
|
sum := 0.0
|
|
1126
1126
|
for x := range xs
|
|
@@ -1611,8 +1611,7 @@ fun main() -> Int =
|
|
|
1611
1611
|
#[test]
|
|
1612
1612
|
fn fn_typed_field_call() {
|
|
1613
1613
|
let src = format!("{}\
|
|
1614
|
-
enum Ops =
|
|
1615
|
-
|
|
1614
|
+
record Ops(inc: fn(Int) -> Int, base: Int)
|
|
1616
1615
|
|
|
1617
1616
|
fun main() -> Int =
|
|
1618
1617
|
o := Ops(inc: |n| n + 1, base: 40)
|
|
@@ -1624,8 +1623,7 @@ fun main() -> Int =
|
|
|
1624
1623
|
#[test]
|
|
1625
1624
|
fn method_beats_field_precedence() {
|
|
1626
1625
|
let src = format!("{}\
|
|
1627
|
-
enum Combo =
|
|
1628
|
-
|
|
1626
|
+
record Combo(add: fn(Int, Int) -> Int) =
|
|
1629
1627
|
|
|
1630
1628
|
fun add(self, a: Int, b: Int) -> Int =
|
|
1631
1629
|
a * b
|
|
@@ -1652,8 +1650,7 @@ fun main() -> Int =
|
|
|
1652
1650
|
#[test]
|
|
1653
1651
|
fn record_of_fns_passed_explicitly() {
|
|
1654
1652
|
let src = format!("{}{}\
|
|
1655
|
-
enum Num[T] =
|
|
1656
|
-
|
|
1653
|
+
record Num[T](add: fn(T, T) -> T, zero: T)
|
|
1657
1654
|
|
|
1658
1655
|
fun sumWith(xs: List[T], num: Num[T]) -> T =
|
|
1659
1656
|
reduceList(xs, num.zero, |a, b| num.add(a, b))
|
|
@@ -1672,8 +1669,7 @@ fun main() -> Int =
|
|
|
1672
1669
|
}
|
|
1673
1670
|
|
|
1674
1671
|
const NULLABLE_SRC: &str = "\
|
|
1675
|
-
enum Person =
|
|
1676
|
-
|
|
1672
|
+
record Person(name: Str, age: Int)
|
|
1677
1673
|
|
|
1678
1674
|
fun findNullable(n: Int) -> Person? =
|
|
1679
1675
|
if n < 0
|
|
@@ -1734,8 +1730,7 @@ fun main() -> Int =
|
|
|
1734
1730
|
#[test]
|
|
1735
1731
|
fn nullable_turns_into_an_error_with_elvis() {
|
|
1736
1732
|
let src = format!("{}{}", STD_HEAD, "\
|
|
1737
|
-
enum Person =
|
|
1738
|
-
|
|
1733
|
+
record Person(name: Str, age: Int)
|
|
1739
1734
|
|
|
1740
1735
|
fun findIt(n: Int) -> Person? =
|
|
1741
1736
|
if n < 0
|
|
@@ -1897,8 +1892,7 @@ fn fn_field_nested_call_arg() {
|
|
|
1897
1892
|
let src = format!("{}\
|
|
1898
1893
|
import std/Array
|
|
1899
1894
|
|
|
1900
|
-
enum Keyed[T] =
|
|
1901
|
-
|
|
1895
|
+
record Keyed[T](items: List[T], key: fn(T) -> Int) =
|
|
1902
1896
|
|
|
1903
1897
|
fun first(self) -> Int =
|
|
1904
1898
|
self.key(self.items.data.get(0))
|
|
@@ -1947,7 +1941,7 @@ fn local_value_subfield_assign() {
|
|
|
1947
1941
|
#[test]
|
|
1948
1942
|
fn heap_field_value_subfield_assign() {
|
|
1949
1943
|
let src = format!(
|
|
1950
|
-
"{VEC2}
|
|
1944
|
+
"{VEC2}record Player(pos: Vec2, hp: Int)\n\nstruct Rect(pos: Vec2, size: Vec2)\n\nrecord Body(bounds: Rect, id: Int)\n\nfun main() -> Int =\n p := Player(pos: Vec2(x: 1.0, y: 2.0), hp: 5)\n p.pos.x = 8.0\n b := Body(bounds: Rect(pos: Vec2(x: 1.0, y: 2.0), size: Vec2(x: 3.0, y: 4.0)), id: 1)\n b.bounds.size.y = 6.0\n if p.pos.x == 8.0 && p.pos.y == 2.0 && p.hp == 5 && b.bounds.size.y == 6.0 && b.bounds.size.x == 3.0 && b.bounds.pos.y == 2.0\n return 1\n return 0\n"
|
|
1951
1945
|
);
|
|
1952
1946
|
assert_eq!(runMerged::<i64>("heap_field_value_subfield_assign", &src), 1);
|
|
1953
1947
|
}
|
|
@@ -1957,7 +1951,7 @@ fn value_nested_pattern_mixed() {
|
|
|
1957
1951
|
// Whole-value binding next to a nested destructure, and a heap container
|
|
1958
1952
|
// (class-like enum) holding a value-enum field.
|
|
1959
1953
|
let src = format!(
|
|
1960
|
-
"{VEC2}struct Rect(pos: Vec2, size: Vec2)\n\
|
|
1954
|
+
"{VEC2}struct Rect(pos: Vec2, size: Vec2)\n\nrecord Player(pos: Vec2, hp: Float)\n\nfun f(r: Rect) -> Float =\n match r\n Rect(p, Vec2(w, h)) => p.x + p.y * 10.0 + w * 100.0 + h * 1000.0\n\nfun g(p: Player) -> Float =\n match p\n Player(Vec2(x, y), hp) => x + y * 10.0 + hp * 100.0\n\nfun main() -> Int =\n r := Rect(pos: Vec2(x: 1.0, y: 2.0), size: Vec2(x: 3.0, y: 4.0))\n p := Player(pos: Vec2(x: 1.0, y: 2.0), hp: 3.0)\n if f(r) == 4321.0 && g(p) == 321.0\n return 1\n return 0\n"
|
|
1961
1955
|
);
|
|
1962
1956
|
assert_eq!(runMerged::<i64>("value_nested_pattern_mixed", &src), 1);
|
|
1963
1957
|
}
|
|
@@ -2192,8 +2186,7 @@ fun main() -> Int =
|
|
|
2192
2186
|
fn fun_star_method_no_closure() {
|
|
2193
2187
|
// A `fun*` method is expanded at the use site: no closure, no call_indirect, `self` bound once.
|
|
2194
2188
|
let src = format!("{}\
|
|
2195
|
-
enum Bag =
|
|
2196
|
-
|
|
2189
|
+
record Bag(items: List[Int]) =
|
|
2197
2190
|
|
|
2198
2191
|
fun* doubled(self) *Int =
|
|
2199
2192
|
for x := range self.items
|
|
@@ -2595,8 +2588,7 @@ fun main() -> Int =
|
|
|
2595
2588
|
#[test]
|
|
2596
2589
|
fn error_union_in_class_field() {
|
|
2597
2590
|
let src = "\
|
|
2598
|
-
enum Box =
|
|
2599
|
-
|
|
2591
|
+
record Box(v: !Int)
|
|
2600
2592
|
|
|
2601
2593
|
fun f(n: Int) -> !Int =
|
|
2602
2594
|
if n < 0
|
|
@@ -2614,8 +2606,7 @@ fun main() -> Int =
|
|
|
2614
2606
|
#[test]
|
|
2615
2607
|
fn safe_nav_on_error_union() {
|
|
2616
2608
|
let src = "\
|
|
2617
|
-
enum User =
|
|
2618
|
-
|
|
2609
|
+
record User(age: Int)
|
|
2619
2610
|
|
|
2620
2611
|
fun load(n: Int) -> !User =
|
|
2621
2612
|
if n < 0
|
|
@@ -2638,8 +2629,7 @@ import std/Str
|
|
|
2638
2629
|
import std/Int
|
|
2639
2630
|
import std/Float
|
|
2640
2631
|
|
|
2641
|
-
enum Item =
|
|
2642
|
-
|
|
2632
|
+
record Item(v: Int)
|
|
2643
2633
|
|
|
2644
2634
|
fun find(n: Int) -> Item? =
|
|
2645
2635
|
if n < 0
|
|
@@ -2704,8 +2694,7 @@ import std/Str
|
|
|
2704
2694
|
import std/Int
|
|
2705
2695
|
import std/Float
|
|
2706
2696
|
|
|
2707
|
-
enum Person =
|
|
2708
|
-
|
|
2697
|
+
record Person(name: Str, age: Int)
|
|
2709
2698
|
|
|
2710
2699
|
fun nameOf(p: Person?) -> Str? =
|
|
2711
2700
|
p?.name
|
|
@@ -2809,8 +2798,7 @@ import std/Str
|
|
|
2809
2798
|
import std/Int
|
|
2810
2799
|
import std/Float
|
|
2811
2800
|
|
|
2812
|
-
enum Item =
|
|
2813
|
-
|
|
2801
|
+
record Item(v: Int)
|
|
2814
2802
|
|
|
2815
2803
|
fun mi(n: Int) -> Int? =
|
|
2816
2804
|
if n > 0
|
plum-wasm-codegen/tests/examples_test.rs
CHANGED
|
@@ -249,7 +249,7 @@ fn const_table_1000_entries() {
|
|
|
249
249
|
src.push_str(&format!(" E{} = {}\n", i, i * 3 + 1));
|
|
250
250
|
}
|
|
251
251
|
src.push('\n');
|
|
252
|
-
src.push_str("
|
|
252
|
+
src.push_str("record Holder(n: Int) =\n\n fun at(self) -> Int = Table.E499 + self.n\n\nprimitive More =\n");
|
|
253
253
|
for i in 500..1000 {
|
|
254
254
|
src.push_str(&format!(" E{} = {}\n", i, i * 3 + 1));
|
|
255
255
|
}
|