plum
git clone https://git.pyrossh.dev/plum
A statically typed, imperative programming language inspired by rust, python
e91520f
— Peter John
2026-11-01T23:09:45+05:30
test: dedupe codegen Rust tests (152 -> 82); drop unused greetForInterpolationTest
plum-std/Str.plum
CHANGED
|
@@ -635,9 +635,6 @@ fun deburrLatinExtA() -> Str =
|
|
|
635
635
|
# `"{expr}"` interpolation is a compiler/codegen feature (not a Str method
|
|
636
636
|
# above), but Str is what it produces, so its regression coverage lives here.
|
|
637
637
|
|
|
638
|
-
fun greetForInterpolationTest(name: Str) -> Str =
|
|
639
|
-
"Hello, {name}!"
|
|
640
|
-
|
|
641
638
|
fun bigAscii(n: Int, b: Int) -> Str =
|
|
642
639
|
buf := Buffer()
|
|
643
640
|
i := 0
|
plum-wasm-codegen/tests/codegen_tests.rs
CHANGED
|
@@ -26,16 +26,6 @@ fn gcEngine() -> wasmtime::Engine {
|
|
|
26
26
|
wasmtime::Engine::new(&config).expect("engine with GC config should construct")
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
#[test]
|
|
30
|
-
fn compilesToValidWasm() {
|
|
31
|
-
let src = "fun add(a: Int, b: Int) -> Int =\n a + b\n";
|
|
32
|
-
let source = parse(src);
|
|
33
|
-
let bytes = compileSource(&source).expect("compile failed");
|
|
34
|
-
// Valid WASM starts with the magic number
|
|
35
|
-
assert_eq!(&bytes[0..4], b"\0asm");
|
|
36
|
-
assert_eq!(&bytes[4..8], &[1, 0, 0, 0]); // version 1
|
|
37
|
-
}
|
|
38
|
-
|
|
39
29
|
#[test]
|
|
40
30
|
fn outputValidates() {
|
|
41
31
|
let src = "fun add(a: Int, b: Int) -> Int =\n a + b\n";
|
|
@@ -46,31 +36,6 @@ fn outputValidates() {
|
|
|
46
36
|
assert!(result.is_ok(), "wasm validation failed: {:?}", result.err());
|
|
47
37
|
}
|
|
48
38
|
|
|
49
|
-
#[test]
|
|
50
|
-
fn factorialCompiles() {
|
|
51
|
-
let src = "\
|
|
52
|
-
fun factorial(x: Int) -> Int =
|
|
53
|
-
if x < 2
|
|
54
|
-
return 1
|
|
55
|
-
return x * factorial(x - 1)
|
|
56
|
-
";
|
|
57
|
-
let source = parse(src);
|
|
58
|
-
let bytes = compileSource(&source).expect("factorial should compile");
|
|
59
|
-
assert_eq!(&bytes[0..4], b"\0asm");
|
|
60
|
-
let result = wasmparser::validate(&bytes);
|
|
61
|
-
assert!(result.is_ok(), "wasm validation failed: {:?}", result.err());
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
#[test]
|
|
65
|
-
fn give42CompilesAndExports() {
|
|
66
|
-
let src = "fun give42() -> Int =\n 42\n";
|
|
67
|
-
let source = parse(src);
|
|
68
|
-
let bytes = compileSource(&source).expect("compile failed");
|
|
69
|
-
assert_eq!(&bytes[0..4], b"\0asm");
|
|
70
|
-
let result = wasmparser::validate(&bytes);
|
|
71
|
-
assert!(result.is_ok(), "wasm validation failed: {:?}", result.err());
|
|
72
|
-
}
|
|
73
|
-
|
|
74
39
|
/// Reads a `Str` (wasm-gc `array<i8>`) result value's bytes into a Rust
|
|
75
40
|
/// `String` — a test-only counterpart to `plum-cli`'s own `readStrResult`,
|
|
76
41
|
/// since each `test#<name>` export now returns a `Str` report (empty = every
|
|
@@ -271,23 +236,6 @@ fn assertValid(src: &str) -> Vec<u8> {
|
|
|
271
236
|
bytes
|
|
272
237
|
}
|
|
273
238
|
|
|
274
|
-
#[test]
|
|
275
|
-
fn boolLiteralsCompile() {
|
|
276
|
-
let boolDecl = "";
|
|
277
|
-
assertValid(&format!("{}fun main() -> Bool =\n true\n", boolDecl));
|
|
278
|
-
assertValid(&format!("{}fun main() -> Bool =\n false\n", boolDecl));
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
#[test]
|
|
282
|
-
fn stringLiteralCompiles() {
|
|
283
|
-
assertValid("fun main() -> Str =\n \"hello\"\n");
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
#[test]
|
|
287
|
-
fn emptyStringLiteralCompiles() {
|
|
288
|
-
assertValid("fun main() -> Str =\n \"\"\n");
|
|
289
|
-
}
|
|
290
|
-
|
|
291
239
|
#[test]
|
|
292
240
|
fn stringInterpolationOfAFloatIsAClearError() {
|
|
293
241
|
// Float-to-decimal-string formatting is a substantial separate undertaking
|
|
@@ -299,65 +247,6 @@ fn stringInterpolationOfAFloatIsAClearError() {
|
|
|
299
247
|
assert!(err.contains("Float"), "got: {}", err);
|
|
300
248
|
}
|
|
301
249
|
|
|
302
|
-
#[test]
|
|
303
|
-
fn floatArithmeticAndNegationCompile() {
|
|
304
|
-
assertValid("fun main(x: Float) -> Float =\n y = -x\n y + 1.5\n");
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
#[test]
|
|
308
|
-
fn classFieldAndMethodCompile() {
|
|
309
|
-
let src = "\
|
|
310
|
-
record Cat(name: Str, age: Int) =
|
|
311
|
-
|
|
312
|
-
fun getAge() -> Int =
|
|
313
|
-
self.age
|
|
314
|
-
|
|
315
|
-
fun makeCat() -> Int =
|
|
316
|
-
c = Cat(name: \"x\", age: 3)
|
|
317
|
-
c.getAge()
|
|
318
|
-
";
|
|
319
|
-
assertValid(src);
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
#[test]
|
|
323
|
-
fn nestedClassCallCompiles() {
|
|
324
|
-
let src = "\
|
|
325
|
-
record Pair(a: Int, b: Int)
|
|
326
|
-
|
|
327
|
-
record Wrapper(inner: Pair, tag: Int)
|
|
328
|
-
|
|
329
|
-
fun make() -> Int =
|
|
330
|
-
w = Wrapper(inner: Pair(a: 1, b: 2), tag: 9)
|
|
331
|
-
w.tag
|
|
332
|
-
";
|
|
333
|
-
assertValid(src);
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
#[test]
|
|
337
|
-
fn matchWithIntAndWildcardCompiles() {
|
|
338
|
-
let src = "fun main(a: Int) -> Int =\n match a\n 1 =>\n return 10\n _ =>\n return 0\n";
|
|
339
|
-
assertValid(src);
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
#[test]
|
|
343
|
-
fn matchBindingPatternCompiles() {
|
|
344
|
-
let src = "fun main(a: Int) -> Int =\n match a\n x =>\n return x\n";
|
|
345
|
-
assertValid(src);
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
#[test]
|
|
349
|
-
fn matchInlineCaseBodyCompiles() {
|
|
350
|
-
// Case bodies can be a single inline expression, not just an indented block.
|
|
351
|
-
let src = "fun main(a: Int) =\n match a\n 1 => 10\n _ => 0\n";
|
|
352
|
-
assertValid(src);
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
#[test]
|
|
356
|
-
fn matchBoolVariantPatternCompiles() {
|
|
357
|
-
let src = "fun main(a: Bool) -> Int =\n match a\n true =>\n return 1\n false =>\n return 0\n";
|
|
358
|
-
assertValid(src);
|
|
359
|
-
}
|
|
360
|
-
|
|
361
250
|
#[test]
|
|
362
251
|
fn matchStringPatternIsAClearError() {
|
|
363
252
|
let src = "fun main(a: Str) -> Int =\n match a\n \"x\" =>\n 1\n _ =>\n 0\n";
|
|
@@ -429,18 +318,6 @@ fun main() -> Int =
|
|
|
429
318
|
);
|
|
430
319
|
}
|
|
431
320
|
|
|
432
|
-
#[test]
|
|
433
|
-
fn payloadFreeVariantConstructionCompiles() {
|
|
434
|
-
let src = "\
|
|
435
|
-
enum Color =
|
|
436
|
-
| Red
|
|
437
|
-
| Green
|
|
438
|
-
| Blue
|
|
439
|
-
|
|
440
|
-
fun main() -> Int =\n x = Green\n 0\n";
|
|
441
|
-
assertValid(src);
|
|
442
|
-
}
|
|
443
|
-
|
|
444
321
|
#[test]
|
|
445
322
|
fn tailIfWithoutElseIsAClearError() {
|
|
446
323
|
let src = "\
|
|
@@ -1182,84 +1059,8 @@ fn runMerged<R: wasmtime::WasmResults>(name: &str, src: &str) -> R {
|
|
|
1182
1059
|
r
|
|
1183
1060
|
}
|
|
1184
1061
|
|
|
1185
|
-
#[test]
|
|
1186
|
-
fn heterogeneous_list_dispatch() {
|
|
1187
|
-
let src = "\
|
|
1188
|
-
import std/Str
|
|
1189
|
-
import std/Int
|
|
1190
|
-
import std/Float
|
|
1191
|
-
import std/List
|
|
1192
|
-
|
|
1193
|
-
enum Shape =
|
|
1194
|
-
| Circle
|
|
1195
|
-
| Square
|
|
1196
|
-
|
|
1197
|
-
fun area(self) -> Float =
|
|
1198
|
-
match self
|
|
1199
|
-
Circle(r) => r * r * 3.0
|
|
1200
|
-
Square(s) => s * s
|
|
1201
|
-
|
|
1202
|
-
record Circle(r: Float)
|
|
1203
|
-
record Square(s: Float)
|
|
1204
|
-
|
|
1205
|
-
fun total(xs: List[Shape]) -> Float =
|
|
1206
|
-
sum := 0.0
|
|
1207
|
-
for x := range xs
|
|
1208
|
-
sum = sum + x.area()
|
|
1209
|
-
sum
|
|
1210
|
-
|
|
1211
|
-
fun main() -> Float =
|
|
1212
|
-
xs := List[Shape]()
|
|
1213
|
-
xs.add(Circle(r: 1.5))
|
|
1214
|
-
xs.add(Square(s: 1.5))
|
|
1215
|
-
xs.add(Circle(r: 0.5))
|
|
1216
|
-
total(xs)
|
|
1217
|
-
";
|
|
1218
|
-
// 6.75 + 2.25 + 0.75, exact in binary floating point.
|
|
1219
|
-
assert_eq!(runMerged::<f64>("hetero", src), 9.75);
|
|
1220
|
-
}
|
|
1221
|
-
|
|
1222
1062
|
// ---- erased generics (T4) ----
|
|
1223
1063
|
|
|
1224
|
-
#[test]
|
|
1225
|
-
fn boxed_i64_extremes_through_generic() {
|
|
1226
|
-
let src = "\
|
|
1227
|
-
import std/Str
|
|
1228
|
-
import std/Int
|
|
1229
|
-
import std/Float
|
|
1230
|
-
|
|
1231
|
-
fun identity(x: T) -> T =
|
|
1232
|
-
x
|
|
1233
|
-
|
|
1234
|
-
fun main() -> Int =
|
|
1235
|
-
lo := 0 - 9223372036854775807 - 1
|
|
1236
|
-
big := 9007199254740993
|
|
1237
|
-
nz := identity(-0.0)
|
|
1238
|
-
ok := identity(lo) == lo && identity(big) == big && identity(big + 0) - big == 0
|
|
1239
|
-
neg := 1.0 / nz < 0.0
|
|
1240
|
-
if ok && neg && identity(0 - 5) == 0 - 5
|
|
1241
|
-
return 1
|
|
1242
|
-
return 0
|
|
1243
|
-
";
|
|
1244
|
-
assert_eq!(runMerged::<i64>("extremes", src), 1);
|
|
1245
|
-
}
|
|
1246
|
-
|
|
1247
|
-
#[test]
|
|
1248
|
-
fn generic_closure_thunk() {
|
|
1249
|
-
let src = "\
|
|
1250
|
-
import std/Str
|
|
1251
|
-
import std/Int
|
|
1252
|
-
import std/Float
|
|
1253
|
-
import std/List
|
|
1254
|
-
|
|
1255
|
-
fun main() -> Int =
|
|
1256
|
-
xs := mapList(List(1, 2, 3), |n| n * 2)
|
|
1257
|
-
reduceList(xs, 0, |acc, x| acc + x)
|
|
1258
|
-
";
|
|
1259
|
-
let src = format!("{src}\n{LIST_FNS}");
|
|
1260
|
-
assert_eq!(runMerged::<i64>("thunk", &src), 12);
|
|
1261
|
-
}
|
|
1262
|
-
|
|
1263
1064
|
|
|
1264
1065
|
/// Compiles `src` (merged with the real std) and returns every compiled function key/name.
|
|
1265
1066
|
fn compiledKeys(name: &str, src: &str) -> (Vec<u8>, Vec<String>) {
|
|
@@ -1488,31 +1289,6 @@ fun main() -> Int =
|
|
|
1488
1289
|
|
|
1489
1290
|
// ---- built-in Bool (i32; no `import std/Bool`) ----
|
|
1490
1291
|
|
|
1491
|
-
#[test]
|
|
1492
|
-
fn builtin_bool_without_import() {
|
|
1493
|
-
let src = "\
|
|
1494
|
-
import std/Str
|
|
1495
|
-
import std/Int
|
|
1496
|
-
import std/Float
|
|
1497
|
-
|
|
1498
|
-
fun flip(b: Bool) -> Bool =
|
|
1499
|
-
!b
|
|
1500
|
-
|
|
1501
|
-
fun name(b: Bool) -> Str =
|
|
1502
|
-
match b
|
|
1503
|
-
true => \"yes\"
|
|
1504
|
-
false => \"no\"
|
|
1505
|
-
|
|
1506
|
-
fun main() -> Int =
|
|
1507
|
-
x := true
|
|
1508
|
-
if flip(false) && x == true && !false || false
|
|
1509
|
-
if name(x) == \"yes\" && name(false) == \"no\" && \"{x}\" == \"true\"
|
|
1510
|
-
return 1
|
|
1511
|
-
return 0
|
|
1512
|
-
";
|
|
1513
|
-
assert_eq!(runMerged::<i64>("bool_noimport", src), 1);
|
|
1514
|
-
}
|
|
1515
|
-
|
|
1516
1292
|
#[test]
|
|
1517
1293
|
fn builtin_bool_list_round_trip() {
|
|
1518
1294
|
let src = "\
|
|
@@ -1638,57 +1414,6 @@ fn extern_int_overflow_traps() {
|
|
|
1638
1414
|
assert!(externEcho("0 - 2147483649").1.is_err(), "Int < i32::MIN must trap");
|
|
1639
1415
|
}
|
|
1640
1416
|
|
|
1641
|
-
#[test]
|
|
1642
|
-
fn bool_tostr_intrinsic() {
|
|
1643
|
-
let src = "\
|
|
1644
|
-
import std/Int
|
|
1645
|
-
import std/Float
|
|
1646
|
-
import std/Str
|
|
1647
|
-
import std/Bool
|
|
1648
|
-
|
|
1649
|
-
fun main() -> Int =
|
|
1650
|
-
t := true
|
|
1651
|
-
if t.toStr() == \"true\" && false.toStr() == \"false\" && \"{t}\" == \"true\"
|
|
1652
|
-
return 1
|
|
1653
|
-
return 0
|
|
1654
|
-
";
|
|
1655
|
-
assert_eq!(runMerged::<i64>("bool_tostr_intrinsic", src), 1);
|
|
1656
|
-
}
|
|
1657
|
-
|
|
1658
|
-
#[test]
|
|
1659
|
-
fn bool_tostr_lowercase() {
|
|
1660
|
-
let src = "\
|
|
1661
|
-
import std/Int
|
|
1662
|
-
import std/Float
|
|
1663
|
-
import std/Str
|
|
1664
|
-
import std/Bool
|
|
1665
|
-
|
|
1666
|
-
fun main() -> Int =
|
|
1667
|
-
t := true
|
|
1668
|
-
f := t && false
|
|
1669
|
-
if t.toStr() == \"true\" && f.toStr() == \"false\" && \"{t}-{f}\" == \"true-false\"
|
|
1670
|
-
return 1
|
|
1671
|
-
return 0
|
|
1672
|
-
";
|
|
1673
|
-
assert_eq!(runMerged::<i64>("bool_tostr_lowercase", src), 1);
|
|
1674
|
-
}
|
|
1675
|
-
|
|
1676
|
-
#[test]
|
|
1677
|
-
fn bool_interpolation_with_import() {
|
|
1678
|
-
let src = "\
|
|
1679
|
-
import std/Int
|
|
1680
|
-
import std/Float
|
|
1681
|
-
import std/Str
|
|
1682
|
-
import std/Bool
|
|
1683
|
-
|
|
1684
|
-
fun main() -> Int =
|
|
1685
|
-
if \"{true}\" == \"true\" && \"{1 > 2}\" == \"false\"
|
|
1686
|
-
return 1
|
|
1687
|
-
return 0
|
|
1688
|
-
";
|
|
1689
|
-
assert_eq!(runMerged::<i64>("bool_interp_import", src), 1);
|
|
1690
|
-
}
|
|
1691
|
-
|
|
1692
1417
|
#[test]
|
|
1693
1418
|
fn bool_tostr_without_import_is_rejected() {
|
|
1694
1419
|
let src = "\
|
|
@@ -1730,25 +1455,6 @@ fun main() -> Str =
|
|
|
1730
1455
|
);
|
|
1731
1456
|
}
|
|
1732
1457
|
|
|
1733
|
-
#[test]
|
|
1734
|
-
fn parse_bool() {
|
|
1735
|
-
let src = "\
|
|
1736
|
-
import std/Int
|
|
1737
|
-
import std/Float
|
|
1738
|
-
import std/Str
|
|
1739
|
-
import std/Bool
|
|
1740
|
-
|
|
1741
|
-
fun main() -> Int =
|
|
1742
|
-
a := Bool.parse(\"true\") catch false
|
|
1743
|
-
b := Bool.parse(\"false\") catch true
|
|
1744
|
-
c := Bool.parse(\"nope\") catch false
|
|
1745
|
-
if a && !b && !c
|
|
1746
|
-
return 1
|
|
1747
|
-
return 0
|
|
1748
|
-
";
|
|
1749
|
-
assert_eq!(runMerged::<i64>("parse_bool", src), 1);
|
|
1750
|
-
}
|
|
1751
|
-
|
|
1752
1458
|
// ---- fn-typed fields ("record of functions") + closures calling fn params ----
|
|
1753
1459
|
|
|
1754
1460
|
#[test]
|
|
@@ -1850,57 +1556,6 @@ fn nullable_ref_no_alloc() {
|
|
|
1850
1556
|
assert!(!ops.iter().any(|o| o.starts_with("StructNew")), "{ops:?}");
|
|
1851
1557
|
}
|
|
1852
1558
|
|
|
1853
|
-
#[test]
|
|
1854
|
-
fn nullable_runs() {
|
|
1855
|
-
let src = format!("{}{}", STD_HEAD, NULLABLE_SRC);
|
|
1856
|
-
assert_eq!(runMerged::<i64>("nullrun", &src), 5 + 100 + 7);
|
|
1857
|
-
}
|
|
1858
|
-
|
|
1859
|
-
#[test]
|
|
1860
|
-
fn nullable_primitive_keeps_struct_layout() {
|
|
1861
|
-
let src = format!("{}\
|
|
1862
|
-
fun half(n: Int) -> Int? =
|
|
1863
|
-
if n % 2 != 0
|
|
1864
|
-
return null
|
|
1865
|
-
n / 2
|
|
1866
|
-
|
|
1867
|
-
fun main() -> Int =
|
|
1868
|
-
{{half(8) ?: 0}} + {{half(3) ?: 100}}
|
|
1869
|
-
", STD_HEAD);
|
|
1870
|
-
assert_eq!(runMerged::<i64>("nullprim", &src), 104);
|
|
1871
|
-
}
|
|
1872
|
-
|
|
1873
|
-
#[test]
|
|
1874
|
-
fn nullable_turns_into_an_error_with_elvis() {
|
|
1875
|
-
let src = format!("{}{}", STD_HEAD, "\
|
|
1876
|
-
record Person(name: Str, age: Int)
|
|
1877
|
-
|
|
1878
|
-
fun findIt(n: Int) -> Person? =
|
|
1879
|
-
if n < 0
|
|
1880
|
-
return null
|
|
1881
|
-
Person(name: \"p\", age: n)
|
|
1882
|
-
|
|
1883
|
-
fun viaElvis(n: Int) -> error{Absent, Bad}!Person =
|
|
1884
|
-
p := findIt(n) ?: return error.Absent
|
|
1885
|
-
if p.age > 50
|
|
1886
|
-
return error.Bad
|
|
1887
|
-
p
|
|
1888
|
-
|
|
1889
|
-
fun code(n: Int) -> Int =
|
|
1890
|
-
p := viaElvis(n) catch e
|
|
1891
|
-
match e
|
|
1892
|
-
Absent =>
|
|
1893
|
-
return 1000
|
|
1894
|
-
Bad =>
|
|
1895
|
-
return 2000
|
|
1896
|
-
p.age
|
|
1897
|
-
|
|
1898
|
-
fun main() -> Int =
|
|
1899
|
-
code(3) + code(-1) + code(99)
|
|
1900
|
-
");
|
|
1901
|
-
assert_eq!(runMerged::<i64>("nullwide", &src), 3 + 1000 + 2000);
|
|
1902
|
-
}
|
|
1903
|
-
|
|
1904
1559
|
// ---- L2: structs (flattened, copy semantics) ----
|
|
1905
1560
|
|
|
1906
1561
|
const VEC2: &str = "\
|
|
@@ -1934,14 +1589,6 @@ fn funcTypes(bytes: &[u8]) -> Vec<(Vec<wasmparser::ValType>, Vec<wasmparser::Val
|
|
|
1934
1589
|
out
|
|
1935
1590
|
}
|
|
1936
1591
|
|
|
1937
|
-
#[test]
|
|
1938
|
-
fn value_copy_semantics() {
|
|
1939
|
-
let src = format!(
|
|
1940
|
-
"{VEC2}fun main() -> Int =\n a := Vec2(x: 1.0, y: 2.0)\n b := a\n b.x = 9.0\n if a.x == 1.0 && b.x == 9.0 && b.y == 2.0\n return 1\n return 0\n"
|
|
1941
|
-
);
|
|
1942
|
-
assert_eq!(runMerged::<i64>("value_copy_semantics", &src), 1);
|
|
1943
|
-
}
|
|
1944
|
-
|
|
1945
1592
|
#[test]
|
|
1946
1593
|
fn value_multi_value_return() {
|
|
1947
1594
|
use wasmparser::ValType::F64;
|
|
@@ -1956,22 +1603,6 @@ fn value_multi_value_return() {
|
|
|
1956
1603
|
assert_eq!(runMerged::<i64>("value_multi_value_return", &src), 1);
|
|
1957
1604
|
}
|
|
1958
1605
|
|
|
1959
|
-
#[test]
|
|
1960
|
-
fn value_in_list_roundtrip() {
|
|
1961
|
-
let src = format!(
|
|
1962
|
-
"import std/List\n{VEC2}fun main() -> Int =\n l := List[Vec2]()\n l.push(Vec2(x: 1.0, y: 2.0))\n l.push(Vec2(x: 3.0, y: 4.0))\n b := l.get(1)!\n if l.length() == 2 && b.x == 3.0 && b.y == 4.0 && l.get(0)!.y == 2.0\n return 1\n return 0\n"
|
|
1963
|
-
);
|
|
1964
|
-
assert_eq!(runMerged::<i64>("value_in_list_roundtrip", &src), 1);
|
|
1965
|
-
}
|
|
1966
|
-
|
|
1967
|
-
#[test]
|
|
1968
|
-
fn value_eq_fieldwise() {
|
|
1969
|
-
let src = format!(
|
|
1970
|
-
"{VEC2}fun main() -> Int =\n a := Vec2(x: 1.0, y: 2.0)\n if a == Vec2(x: 1.0, y: 2.0) && a != Vec2(x: 1.0, y: 3.0)\n return 1\n return 0\n"
|
|
1971
|
-
);
|
|
1972
|
-
assert_eq!(runMerged::<i64>("value_eq_fieldwise", &src), 1);
|
|
1973
|
-
}
|
|
1974
|
-
|
|
1975
1606
|
#[test]
|
|
1976
1607
|
fn value_nested_flatten() {
|
|
1977
1608
|
use wasmparser::ValType::F64;
|
|
@@ -1986,14 +1617,6 @@ fn value_nested_flatten() {
|
|
|
1986
1617
|
assert_eq!(runMerged::<i64>("value_nested_flatten", &src), 1);
|
|
1987
1618
|
}
|
|
1988
1619
|
|
|
1989
|
-
#[test]
|
|
1990
|
-
fn value_operator_add_mul() {
|
|
1991
|
-
let src = format!(
|
|
1992
|
-
"{VEC2}fun main() -> Int =\n a := Vec2(x: 1.0, y: 2.0)\n b := Vec2(x: 3.0, y: 4.0)\n r := a + b * 2.0\n if r.x == 7.0 && r.y == 10.0\n return 1\n return 0\n"
|
|
1993
|
-
);
|
|
1994
|
-
assert_eq!(runMerged::<i64>("value_operator_add_mul", &src), 1);
|
|
1995
|
-
}
|
|
1996
|
-
|
|
1997
1620
|
#[test]
|
|
1998
1621
|
fn extern_value_multi_value() {
|
|
1999
1622
|
let src = format!(
|
|
@@ -2073,22 +1696,6 @@ fn value_nested_pattern() {
|
|
|
2073
1696
|
assert_eq!(runMerged::<i64>("value_nested_pattern", &src), 1);
|
|
2074
1697
|
}
|
|
2075
1698
|
|
|
2076
|
-
#[test]
|
|
2077
|
-
fn local_value_subfield_assign() {
|
|
2078
|
-
let src = format!(
|
|
2079
|
-
"{VEC2}struct Rect(pos: Vec2, size: Vec2)\n\nfun main() -> Int =\n r := Rect(pos: Vec2(x: 1.0, y: 2.0), size: Vec2(x: 3.0, y: 4.0))\n r.pos.x = 7.0\n r.size.y = 9.0\n if r.pos.x == 7.0 && r.pos.y == 2.0 && r.size.x == 3.0 && r.size.y == 9.0\n return 1\n return 0\n"
|
|
2080
|
-
);
|
|
2081
|
-
assert_eq!(runMerged::<i64>("local_value_subfield_assign", &src), 1);
|
|
2082
|
-
}
|
|
2083
|
-
|
|
2084
|
-
#[test]
|
|
2085
|
-
fn heap_field_value_subfield_assign() {
|
|
2086
|
-
let src = format!(
|
|
2087
|
-
"{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"
|
|
2088
|
-
);
|
|
2089
|
-
assert_eq!(runMerged::<i64>("heap_field_value_subfield_assign", &src), 1);
|
|
2090
|
-
}
|
|
2091
|
-
|
|
2092
1699
|
#[test]
|
|
2093
1700
|
fn value_nested_pattern_mixed() {
|
|
2094
1701
|
// Whole-value binding next to a nested destructure, and a heap container
|
|
@@ -2184,48 +1791,6 @@ fun main() -> Int =
|
|
|
2184
1791
|
assert_eq!(runMerged::<i64>("rangeinttight_run", &src), 45);
|
|
2185
1792
|
}
|
|
2186
1793
|
|
|
2187
|
-
#[test]
|
|
2188
|
-
fn until_to_downTo_bounds() {
|
|
2189
|
-
// Each loop sums its values and counts iterations; empty ranges iterate nothing.
|
|
2190
|
-
let src = format!("{}\
|
|
2191
|
-
fun main() -> Int =
|
|
2192
|
-
a := 0
|
|
2193
|
-
for i := range 2.until(6)
|
|
2194
|
-
a = a + i
|
|
2195
|
-
b := 0
|
|
2196
|
-
for i := range 1.to(4)
|
|
2197
|
-
b = b + i
|
|
2198
|
-
c := 0
|
|
2199
|
-
for i := range 4.downTo(1)
|
|
2200
|
-
c = c * 10 + i
|
|
2201
|
-
n := 0
|
|
2202
|
-
for _ := range 5.until(5)
|
|
2203
|
-
n = n + 1
|
|
2204
|
-
for _ := range 1.downTo(2)
|
|
2205
|
-
n = n + 1
|
|
2206
|
-
for _ := range 0
|
|
2207
|
-
n = n + 1
|
|
2208
|
-
for _ := range 3.to(2)
|
|
2209
|
-
n = n + 1
|
|
2210
|
-
a * 1000000 + b * 100000 + c + n * 100000000
|
|
2211
|
-
", iterHead());
|
|
2212
|
-
assert_eq!(runMerged::<i64>("untilbounds", &src), 14 * 1000000 + 10 * 100000 + 4321);
|
|
2213
|
-
}
|
|
2214
|
-
|
|
2215
|
-
#[test]
|
|
2216
|
-
fn stepBy_on_until() {
|
|
2217
|
-
let src = format!("{}\
|
|
2218
|
-
fun main() -> Int =
|
|
2219
|
-
total := 0
|
|
2220
|
-
for i := range 0.until(10).stepBy(3)
|
|
2221
|
-
total = total + i
|
|
2222
|
-
for i := range 1.to(10).stepBy(4)
|
|
2223
|
-
total = total + 100 * i
|
|
2224
|
-
total
|
|
2225
|
-
", iterHead());
|
|
2226
|
-
assert_eq!(runMerged::<i64>("untilstep", &src), 0 + 3 + 6 + 9 + 100 * (1 + 5 + 9));
|
|
2227
|
-
}
|
|
2228
|
-
|
|
2229
1794
|
#[test]
|
|
2230
1795
|
fn no_closure_in_expansion() {
|
|
2231
1796
|
let src = format!("{}\
|
|
@@ -2260,55 +1825,8 @@ fun main() -> Int =
|
|
|
2260
1825
|
}
|
|
2261
1826
|
|
|
2262
1827
|
#[test]
|
|
2263
|
-
fn nested_adapters_skip_take() {
|
|
2264
|
-
let src = format!("{}\
|
|
2265
|
-
fun main() -> Int =
|
|
2266
|
-
xs := List(5, 6, 7, 8, 9)
|
|
2267
|
-
total := 0
|
|
2268
|
-
for x := range xs.take(4).skip(1)
|
|
2269
|
-
total = total * 10 + x
|
|
2270
|
-
total
|
|
2271
|
-
", iterHead());
|
|
2272
|
-
// skip(1, take(4, xs)) = 6, 7, 8
|
|
2273
|
-
assert_eq!(runMerged::<i64>("nestedskiptake", &src), 678);
|
|
2274
|
-
}
|
|
2275
|
-
|
|
2276
|
-
#[test]
|
|
2277
|
-
fn zip_two_lists() {
|
|
2278
|
-
let src = format!("{}\
|
|
2279
|
-
fun main() -> Int =
|
|
2280
|
-
xs := List(1, 2, 3)
|
|
2281
|
-
ys := List(10, 20, 30, 40)
|
|
2282
|
-
dot := 0
|
|
2283
|
-
for a, b := range xs.zip(ys)
|
|
2284
|
-
dot = dot + a * b
|
|
2285
|
-
dot
|
|
2286
|
-
", iterHead());
|
|
2287
|
-
assert_eq!(runMerged::<i64>("ziplists", &src), 140);
|
|
2288
|
-
}
|
|
2289
|
-
|
|
2290
|
-
#[test]
|
|
2291
|
-
fn chunks_and_stepBy() {
|
|
2292
|
-
let src = format!("{}\
|
|
2293
|
-
fun main() -> Int =
|
|
2294
|
-
xs := List(1, 2, 3, 4, 5, 6, 7)
|
|
2295
|
-
pieces := 0
|
|
2296
|
-
last := 0
|
|
2297
|
-
for c := range xs.chunks(3)
|
|
2298
|
-
pieces = pieces + 1
|
|
2299
|
-
last = c.length()
|
|
2300
|
-
stepped := 0
|
|
2301
|
-
for x := range xs.stepBy(3)
|
|
2302
|
-
stepped = stepped + x
|
|
2303
|
-
pieces * 1000 + last * 100 + stepped
|
|
2304
|
-
", iterHead());
|
|
2305
|
-
// chunks: [1 2 3] [4 5 6] [7] -> 3 pieces, last has 1; stepBy 3: 1 + 4 + 7 = 12
|
|
2306
|
-
assert_eq!(runMerged::<i64>("chunksstep", &src), 3000 + 100 + 12);
|
|
2307
|
-
}
|
|
2308
|
-
|
|
2309
|
-
#[test]
|
|
2310
|
-
fn pack_binder_passes_through_map() {
|
|
1828
|
+
fn pack_binder_passes_through_map() {
|
|
2311
|
-
// map over a two-value source: map's single name `x` holds both values and is passed to yield
|
|
1829
|
+
// map over a two-value source: map's single name `x` holds both values and is passed to yield
|
|
2312
1830
|
let src = format!("{}\
|
|
2313
1831
|
fun* pairs(src: List[Int]) *(Int, Int) =
|
|
2314
1832
|
for i, x := range src
|
|
@@ -2348,21 +1866,6 @@ fun main() -> Int =
|
|
|
2348
1866
|
assert_eq!(runMerged::<i64>("starmethod_run", &src), 12);
|
|
2349
1867
|
}
|
|
2350
1868
|
|
|
2351
|
-
#[test]
|
|
2352
|
-
fn underscore_binder_skips_binding() {
|
|
2353
|
-
let src = format!("{}\
|
|
2354
|
-
fun main() -> Int =
|
|
2355
|
-
xs := List(5, 6, 7)
|
|
2356
|
-
total := 0
|
|
2357
|
-
for _, x := range xs
|
|
2358
|
-
total = total + x
|
|
2359
|
-
for _ := range 4
|
|
2360
|
-
total = total + 1
|
|
2361
|
-
total
|
|
2362
|
-
", iterHead());
|
|
2363
|
-
assert_eq!(runMerged::<i64>("underscore", &src), 18 + 4);
|
|
2364
|
-
}
|
|
2365
|
-
|
|
2366
1869
|
/// The checker's error messages for `src` compiled as a real file ("" when it checks).
|
|
2367
1870
|
fn checkErrorsMerged(name: &str, src: &str) -> String {
|
|
2368
1871
|
let dir = std::env::temp_dir().join(format!("plum_seq_{}_{}", name, std::process::id()));
|
|
@@ -2509,62 +2012,6 @@ fn error_union_multi_value() {
|
|
|
2509
2012
|
assert!(found, "no (i64)->(i64,i32) function type");
|
|
2510
2013
|
}
|
|
2511
2014
|
|
|
2512
|
-
#[test]
|
|
2513
|
-
fn fail_and_question_propagate() {
|
|
2514
|
-
let src = "\
|
|
2515
|
-
fun a(n: Int) -> !Int =
|
|
2516
|
-
if n < 0
|
|
2517
|
-
return error.Negative
|
|
2518
|
-
n * 2
|
|
2519
|
-
|
|
2520
|
-
fun b(n: Int) -> !Int =
|
|
2521
|
-
x := try a(n)
|
|
2522
|
-
x + 1
|
|
2523
|
-
|
|
2524
|
-
fun main() -> Int =
|
|
2525
|
-
p := b(5) catch 100
|
|
2526
|
-
q := b(-5) catch 200
|
|
2527
|
-
p * 1000 + q
|
|
2528
|
-
";
|
|
2529
|
-
assert_eq!(runInt(src), 11 * 1000 + 200);
|
|
2530
|
-
}
|
|
2531
|
-
|
|
2532
|
-
#[test]
|
|
2533
|
-
fn catch_default_runtime() {
|
|
2534
|
-
let src = "\
|
|
2535
|
-
fun f(n: Int) -> !Int =
|
|
2536
|
-
if n == 0
|
|
2537
|
-
return error.Zero
|
|
2538
|
-
n
|
|
2539
|
-
|
|
2540
|
-
fun main() -> Int =
|
|
2541
|
-
{f(7) catch 1} * 10 + {f(0) catch 3}
|
|
2542
|
-
";
|
|
2543
|
-
assert_eq!(runInt(src), 73);
|
|
2544
|
-
}
|
|
2545
|
-
|
|
2546
|
-
#[test]
|
|
2547
|
-
fn catch_return_error_maps() {
|
|
2548
|
-
let src = "\
|
|
2549
|
-
fun inner(n: Int) -> !Int =
|
|
2550
|
-
if n == 0
|
|
2551
|
-
return error.Zero
|
|
2552
|
-
n
|
|
2553
|
-
|
|
2554
|
-
fun outer(n: Int) -> !Int =
|
|
2555
|
-
x := inner(n) catch return error.Mapped
|
|
2556
|
-
x + 1
|
|
2557
|
-
|
|
2558
|
-
fun main() -> Int =
|
|
2559
|
-
a := outer(0) catch e
|
|
2560
|
-
match e
|
|
2561
|
-
Mapped => 40
|
|
2562
|
-
b := outer(1) catch 99
|
|
2563
|
-
a + b
|
|
2564
|
-
";
|
|
2565
|
-
assert_eq!(runInt(src), 42);
|
|
2566
|
-
}
|
|
2567
|
-
|
|
2568
2015
|
#[test]
|
|
2569
2016
|
fn catch_crash_default() {
|
|
2570
2017
|
let src = "\
|
|
@@ -2612,53 +2059,6 @@ fun main() -> Int =
|
|
|
2612
2059
|
assert_eq!(runInt(src), 30 + 2 + 6);
|
|
2613
2060
|
}
|
|
2614
2061
|
|
|
2615
|
-
#[test]
|
|
2616
|
-
fn catch_block_binds_error() {
|
|
2617
|
-
let src = "\
|
|
2618
|
-
fun f(n: Int) -> !Int =
|
|
2619
|
-
if n == 1
|
|
2620
|
-
return error.One
|
|
2621
|
-
if n == 2
|
|
2622
|
-
return error.Two
|
|
2623
|
-
n
|
|
2624
|
-
|
|
2625
|
-
fun main() -> Int =
|
|
2626
|
-
a := f(1) catch e
|
|
2627
|
-
match e
|
|
2628
|
-
One => 10
|
|
2629
|
-
Two => 20
|
|
2630
|
-
b := f(2) catch e
|
|
2631
|
-
match e
|
|
2632
|
-
One => 10
|
|
2633
|
-
Two => 20
|
|
2634
|
-
c := f(3) catch e
|
|
2635
|
-
return 0
|
|
2636
|
-
a * 100 + b * 10 + c
|
|
2637
|
-
";
|
|
2638
|
-
assert_eq!(runInt(src), 10 * 100 + 20 * 10 + 3);
|
|
2639
|
-
}
|
|
2640
|
-
|
|
2641
|
-
#[test]
|
|
2642
|
-
fn e_name_runtime() {
|
|
2643
|
-
let src = "\
|
|
2644
|
-
import std/Str
|
|
2645
|
-
import std/Int
|
|
2646
|
-
import std/Float
|
|
2647
|
-
|
|
2648
|
-
fun f() -> !Int =
|
|
2649
|
-
return error.NotFound
|
|
2650
|
-
|
|
2651
|
-
fun main() -> Int =
|
|
2652
|
-
s := f() catch e
|
|
2653
|
-
e.name()
|
|
2654
|
-
s.length()
|
|
2655
|
-
";
|
|
2656
|
-
// the catch block must yield Int, so name() is used for its length instead
|
|
2657
|
-
let src = src.replace(" s := f() catch e\n e.name()\n s.length()", " n := f() catch e\n e.name().length()\n n");
|
|
2658
|
-
let (r, _) = runWithCrash("ename", &src);
|
|
2659
|
-
assert_eq!(r.unwrap(), 8);
|
|
2660
|
-
}
|
|
2661
|
-
|
|
2662
2062
|
#[test]
|
|
2663
2063
|
fn bang_crashes_with_message() {
|
|
2664
2064
|
let src = "\
|
|
@@ -2683,47 +2083,6 @@ fun main() -> Int =
|
|
|
2683
2083
|
assert_eq!(runWithCrash("bangok", ok).0.unwrap(), 7);
|
|
2684
2084
|
}
|
|
2685
2085
|
|
|
2686
|
-
#[test]
|
|
2687
|
-
fn unit_error_union() {
|
|
2688
|
-
let src = "\
|
|
2689
|
-
fun check(n: Int) -> !Unit =
|
|
2690
|
-
if n < 0
|
|
2691
|
-
return error.Neg
|
|
2692
|
-
|
|
2693
|
-
fun run(n: Int) -> !Int =
|
|
2694
|
-
try check(n)
|
|
2695
|
-
n
|
|
2696
|
-
|
|
2697
|
-
fun main() -> Int =
|
|
2698
|
-
{run(4) catch 0} + {run(-1) catch 5}
|
|
2699
|
-
";
|
|
2700
|
-
assert_eq!(runInt(src), 9);
|
|
2701
|
-
}
|
|
2702
|
-
|
|
2703
|
-
#[test]
|
|
2704
|
-
fn error_union_boxed_in_list() {
|
|
2705
|
-
let src = "\
|
|
2706
|
-
import std/Str
|
|
2707
|
-
import std/Int
|
|
2708
|
-
import std/Float
|
|
2709
|
-
import std/List
|
|
2710
|
-
|
|
2711
|
-
fun f(n: Int) -> !Int =
|
|
2712
|
-
if n < 0
|
|
2713
|
-
return error.Neg
|
|
2714
|
-
n
|
|
2715
|
-
|
|
2716
|
-
fun main() -> Int =
|
|
2717
|
-
xs := List[!Int]()
|
|
2718
|
-
xs.push(f(4))
|
|
2719
|
-
xs.push(f(-1))
|
|
2720
|
-
a := xs.at(0) catch 0
|
|
2721
|
-
b := xs.at(1) catch 100
|
|
2722
|
-
a + b
|
|
2723
|
-
";
|
|
2724
|
-
assert_eq!(runWithCrash("boxlist", src).0.unwrap(), 104);
|
|
2725
|
-
}
|
|
2726
|
-
|
|
2727
2086
|
#[test]
|
|
2728
2087
|
fn error_union_in_class_field() {
|
|
2729
2088
|
let src = "\
|
|
@@ -2761,28 +2120,6 @@ fun main() -> Int =
|
|
|
2761
2120
|
assert_eq!(runInt(src), 31);
|
|
2762
2121
|
}
|
|
2763
2122
|
|
|
2764
|
-
#[test]
|
|
2765
|
-
fn nullable_ref_still_null() {
|
|
2766
|
-
let src = "\
|
|
2767
|
-
import std/Str
|
|
2768
|
-
import std/Int
|
|
2769
|
-
import std/Float
|
|
2770
|
-
|
|
2771
|
-
record Item(v: Int)
|
|
2772
|
-
|
|
2773
|
-
fun find(n: Int) -> Item? =
|
|
2774
|
-
if n < 0
|
|
2775
|
-
return null
|
|
2776
|
-
Item(v: n)
|
|
2777
|
-
|
|
2778
|
-
fun main() -> Int =
|
|
2779
|
-
a := find(3) ?: Item(v: 0)
|
|
2780
|
-
b := find(-3) ?: Item(v: 5)
|
|
2781
|
-
a.v * 10 + b.v
|
|
2782
|
-
";
|
|
2783
|
-
assert_eq!(runWithCrash("nullable", src).0.unwrap(), 35);
|
|
2784
|
-
}
|
|
2785
|
-
|
|
2786
2123
|
#[test]
|
|
2787
2124
|
fn no_alloc_on_error_path() {
|
|
2788
2125
|
let src = "\
|
|
@@ -2826,55 +2163,6 @@ fun main() -> Int =
|
|
|
2826
2163
|
assert!(msgs[0].contains("main.plum:"), "{}", msgs[0]);
|
|
2827
2164
|
}
|
|
2828
2165
|
|
|
2829
|
-
#[test]
|
|
2830
|
-
fn nullable_safe_navigation_keeps_the_reference_layout() {
|
|
2831
|
-
let src = "\
|
|
2832
|
-
import std/Str
|
|
2833
|
-
import std/Int
|
|
2834
|
-
import std/Float
|
|
2835
|
-
|
|
2836
|
-
record Person(name: Str, age: Int)
|
|
2837
|
-
|
|
2838
|
-
fun nameOf(p: Person?) -> Str? =
|
|
2839
|
-
p?.name
|
|
2840
|
-
|
|
2841
|
-
fun ageOf(p: Person?) -> Int? =
|
|
2842
|
-
p?.age
|
|
2843
|
-
|
|
2844
|
-
fun none() -> Person? =
|
|
2845
|
-
return null
|
|
2846
|
-
|
|
2847
|
-
fun main() -> Int =
|
|
2848
|
-
a := nameOf(Person(name: \"abc\", age: 4)) ?: \"\"
|
|
2849
|
-
b := nameOf(none()) ?: \"zz\"
|
|
2850
|
-
c := ageOf(Person(name: \"x\", age: 40)) ?: 0
|
|
2851
|
-
d := ageOf(none()) ?: 7
|
|
2852
|
-
a.length() * 1000 + b.length() * 100 + c + d
|
|
2853
|
-
";
|
|
2854
|
-
assert_eq!(runWithCrash("nullnav", src).0.unwrap(), 3000 + 200 + 40 + 7);
|
|
2855
|
-
}
|
|
2856
|
-
|
|
2857
|
-
#[test]
|
|
2858
|
-
fn expression_bodied_unit_error_union() {
|
|
2859
|
-
let src = "\
|
|
2860
|
-
fun check(n: Int) -> error{Neg}!Unit =
|
|
2861
|
-
if n < 0
|
|
2862
|
-
return error.Neg
|
|
2863
|
-
|
|
2864
|
-
fun passthrough(n: Int) -> error{Neg}!Unit =
|
|
2865
|
-
try check(n)
|
|
2866
|
-
|
|
2867
|
-
fun main() -> Int =
|
|
2868
|
-
a := 0
|
|
2869
|
-
passthrough(1) catch e
|
|
2870
|
-
a = a + 1
|
|
2871
|
-
passthrough(-1) catch e
|
|
2872
|
-
a = a + 10
|
|
2873
|
-
a
|
|
2874
|
-
";
|
|
2875
|
-
assert_eq!(runInt(src), 10);
|
|
2876
|
-
}
|
|
2877
|
-
|
|
2878
2166
|
#[test]
|
|
2879
2167
|
fn open_error_set_name_through_a_parameter() {
|
|
2880
2168
|
let src = "\
|
|
@@ -2930,76 +2218,6 @@ fun main() -> Int =
|
|
|
2930
2218
|
assert!(msgs[0].contains("main.plum:"), "{}", msgs[0]);
|
|
2931
2219
|
}
|
|
2932
2220
|
|
|
2933
|
-
#[test]
|
|
2934
|
-
fn null_comparisons_and_bindings_run() {
|
|
2935
|
-
let src = "\
|
|
2936
|
-
import std/Str
|
|
2937
|
-
import std/Int
|
|
2938
|
-
import std/Float
|
|
2939
|
-
|
|
2940
|
-
record Item(v: Int)
|
|
2941
|
-
|
|
2942
|
-
fun mi(n: Int) -> Int? =
|
|
2943
|
-
if n > 0
|
|
2944
|
-
return n
|
|
2945
|
-
null
|
|
2946
|
-
|
|
2947
|
-
fun mr(n: Int) -> Item? =
|
|
2948
|
-
if n > 0
|
|
2949
|
-
return Item(v: n)
|
|
2950
|
-
null
|
|
2951
|
-
|
|
2952
|
-
fun nested(n: Int) -> error{Bad}!Int? =
|
|
2953
|
-
if n < 0
|
|
2954
|
-
return error.Bad
|
|
2955
|
-
if n == 0
|
|
2956
|
-
return null
|
|
2957
|
-
n
|
|
2958
|
-
|
|
2959
|
-
fun main() -> Int =
|
|
2960
|
-
score := 0
|
|
2961
|
-
if mi(1) != null
|
|
2962
|
-
score = score + 1
|
|
2963
|
-
if null == mi(0)
|
|
2964
|
-
score = score + 10
|
|
2965
|
-
if mr(0) == null
|
|
2966
|
-
score = score + 100
|
|
2967
|
-
if x := mi(5)
|
|
2968
|
-
score = score + x * 1000
|
|
2969
|
-
if it := mr(2)
|
|
2970
|
-
score = score + it.v * 10000
|
|
2971
|
-
k := 3
|
|
2972
|
-
while y := mi(k)
|
|
2973
|
-
score = score + 100000
|
|
2974
|
-
k = k - 1
|
|
2975
|
-
n := nested(0) catch 0
|
|
2976
|
-
score + {if n == null then 1000000 else 0}
|
|
2977
|
-
";
|
|
2978
|
-
assert_eq!(runWithCrash("nullbind", src).0.unwrap(), 1 + 10 + 100 + 5000 + 20000 + 300000 + 1000000);
|
|
2979
|
-
}
|
|
2980
|
-
|
|
2981
|
-
#[test]
|
|
2982
|
-
fn nullable_value_enum_uses_the_code_word() {
|
|
2983
|
-
let src = "\
|
|
2984
|
-
import std/Str
|
|
2985
|
-
import std/Int
|
|
2986
|
-
import std/Float
|
|
2987
|
-
|
|
2988
|
-
struct Pair(x: Int, y: Int)
|
|
2989
|
-
|
|
2990
|
-
fun make(n: Int) -> Pair? =
|
|
2991
|
-
if n < 0
|
|
2992
|
-
return null
|
|
2993
|
-
Pair(x: n, y: n + 1)
|
|
2994
|
-
|
|
2995
|
-
fun main() -> Int =
|
|
2996
|
-
p := make(4)!
|
|
2997
|
-
q := make(-1) ?: Pair(x: 0, y: 9)
|
|
2998
|
-
p.x * 100 + p.y * 10 + q.y
|
|
2999
|
-
";
|
|
3000
|
-
assert_eq!(runWithCrash("nullvalue", src).0.unwrap(), 400 + 50 + 9);
|
|
3001
|
-
}
|
|
3002
|
-
|
|
3003
2221
|
#[test]
|
|
3004
2222
|
fn list_of_error_unions_across_sets_valid_wasm() {
|
|
3005
2223
|
let src = "\
|
|
@@ -3026,31 +2244,6 @@ fun main() -> Int =
|
|
|
3026
2244
|
assert_eq!(runWithCrash("listsets", src).0.unwrap(), 104);
|
|
3027
2245
|
}
|
|
3028
2246
|
|
|
3029
|
-
#[test]
|
|
3030
|
-
fn map_value_error_union_across_sets() {
|
|
3031
|
-
let src = "\
|
|
3032
|
-
import std/Str
|
|
3033
|
-
import std/Int
|
|
3034
|
-
import std/Float
|
|
3035
|
-
import std/Map
|
|
3036
|
-
|
|
3037
|
-
fun chk(n: Int) -> !Int =
|
|
3038
|
-
if n < 0
|
|
3039
|
-
return error.Neg
|
|
3040
|
-
n
|
|
3041
|
-
|
|
3042
|
-
fun look(m: Map[Int, error{Neg}!Int]) -> Int =
|
|
3043
|
-
v := m.get(1) catch chk(-1)
|
|
3044
|
-
v catch 0
|
|
3045
|
-
|
|
3046
|
-
fun main() -> Int =
|
|
3047
|
-
m := Map[Int, !Int]()
|
|
3048
|
-
m.set(1, chk(7))
|
|
3049
|
-
look(m)
|
|
3050
|
-
";
|
|
3051
|
-
assert_eq!(runWithCrash("mapsets", src).0.unwrap(), 7);
|
|
3052
|
-
}
|
|
3053
|
-
|
|
3054
2247
|
#[test]
|
|
3055
2248
|
fn error_value_runtime_in_slots() {
|
|
3056
2249
|
let src = "\
|
|
@@ -3101,27 +2294,6 @@ fun main() -> Int =
|
|
|
3101
2294
|
|
|
3102
2295
|
// ---- associated consts, statics and `primitive` ----
|
|
3103
2296
|
|
|
3104
|
-
#[test]
|
|
3105
|
-
fn primitive_consts_and_statics() {
|
|
3106
|
-
let src = format!("{}\
|
|
3107
|
-
primitive Geo =
|
|
3108
|
-
SIDES = 4
|
|
3109
|
-
AREA = SIDES * SIDES
|
|
3110
|
-
NAMES = List(\"a\", \"b\", \"c\")
|
|
3111
|
-
|
|
3112
|
-
fun twice(n: Int) -> Int =
|
|
3113
|
-
n * 2
|
|
3114
|
-
|
|
3115
|
-
fun sidesTwice() -> Int =
|
|
3116
|
-
twice(SIDES)
|
|
3117
|
-
|
|
3118
|
-
fun main() -> Int =
|
|
3119
|
-
Geo.SIDES + Geo.AREA + Geo.twice(5) + Geo.sidesTwice() + Geo.NAMES.length()
|
|
3120
|
-
", STD_HEAD);
|
|
3121
|
-
// 4 + 16 + 10 + 8 + 3
|
|
3122
|
-
assert_eq!(runMerged::<i64>("prim_consts", &src), 41);
|
|
3123
|
-
}
|
|
3124
|
-
|
|
3125
2297
|
#[test]
|
|
3126
2298
|
fn enum_assoc_const() {
|
|
3127
2299
|
let src = format!("{}\
|
|
@@ -3140,58 +2312,8 @@ fun main() -> Int =
|
|
|
3140
2312
|
assert_eq!(runMerged::<i64>("enum_const", &src), 23);
|
|
3141
2313
|
}
|
|
3142
2314
|
|
|
3143
|
-
#[test]
|
|
3144
|
-
fn value_enum_assoc_const() {
|
|
3145
|
-
let src = format!("{}\
|
|
3146
|
-
struct Vec2(x: Float, y: Float) =
|
|
3147
|
-
|
|
3148
|
-
ZERO = Vec2(x: 0.0, y: 0.0)
|
|
3149
|
-
UNIT = Vec2(x: 1.0, y: 1.0)
|
|
3150
|
-
|
|
3151
|
-
fun main() -> Int =
|
|
3152
|
-
z := Vec2.ZERO
|
|
3153
|
-
u := Vec2.UNIT
|
|
3154
|
-
if z.x == 0.0 && u.y == 1.0
|
|
3155
|
-
return 1
|
|
3156
|
-
return 0
|
|
3157
|
-
", STD_HEAD);
|
|
3158
|
-
assert_eq!(runMerged::<i64>("value_enum_const", &src), 1);
|
|
3159
|
-
}
|
|
3160
|
-
|
|
3161
|
-
#[test]
|
|
3162
|
-
fn extend_float_consts() {
|
|
3163
|
-
let src = format!("{}\
|
|
3164
|
-
fun main() -> Int =
|
|
3165
|
-
if Float.PI > 3.14 && Float.PI < 3.15 && Float.TAU == Float.PI * 2.0 && Int.MAX > 0
|
|
3166
|
-
return 1
|
|
3167
|
-
return 0
|
|
3168
|
-
", STD_HEAD);
|
|
3169
|
-
assert_eq!(runMerged::<i64>("float_consts", &src), 1);
|
|
3170
|
-
}
|
|
3171
|
-
|
|
3172
|
-
#[test]
|
|
3173
|
-
fn int_parse_static() {
|
|
3174
|
-
let src = format!("{}\
|
|
3175
|
-
fun main() -> Int =
|
|
3176
|
-
a := Int.parse(\"42\") catch -1
|
|
3177
|
-
b := Int.parse(\"x\") catch -1
|
|
3178
|
-
c := Float.parse(\"2.5\") catch -1.0
|
|
3179
|
-
a + b + Int(c * 2.0)
|
|
3180
|
-
", STD_HEAD);
|
|
3181
|
-
assert_eq!(runMerged::<i64>("int_parse", &src), 46);
|
|
3182
|
-
}
|
|
3183
|
-
|
|
3184
2315
|
#[test]
|
|
3185
2316
|
fn number_enum_removed() {
|
|
3186
|
-
let src = format!("{}\
|
|
3187
|
-
fun main() -> Int =
|
|
3188
|
-
x := -7
|
|
3189
|
-
f := 9.0
|
|
3190
|
-
if x.abs() == 7 && f.sqrt() == 3.0 && 3.min(4) == 3 && 2.5.max(1.5) == 2.5
|
|
3191
|
-
return 1
|
|
3192
|
-
return 0
|
|
3193
|
-
", STD_HEAD);
|
|
3194
|
-
assert_eq!(runMerged::<i64>("number_removed", &src), 1);
|
|
3195
2317
|
// `Number` is no longer a type.
|
|
3196
2318
|
let msg = checkErrorsMerged("number_type", &format!("{}\
|
|
3197
2319
|
fun f(n: Number) -> Int =
|
|
@@ -3203,40 +2325,6 @@ fun main() -> Int =
|
|
|
3203
2325
|
assert!(!msg.is_empty(), "Number must not resolve as a type");
|
|
3204
2326
|
}
|
|
3205
2327
|
|
|
3206
|
-
#[test]
|
|
3207
|
-
fn assoc_const_names_per_type() {
|
|
3208
|
-
let src = format!("{}\
|
|
3209
|
-
primitive Alpha =
|
|
3210
|
-
X = 1
|
|
3211
|
-
|
|
3212
|
-
primitive Beta =
|
|
3213
|
-
X = 20
|
|
3214
|
-
|
|
3215
|
-
record Gamma(n: Int) =
|
|
3216
|
-
X = 300
|
|
3217
|
-
|
|
3218
|
-
fun sum(self) -> Int =
|
|
3219
|
-
X + Alpha.X + Beta.X
|
|
3220
|
-
|
|
3221
|
-
fun main() -> Int =
|
|
3222
|
-
g := Gamma(n: 0)
|
|
3223
|
-
Alpha.X + Beta.X + Gamma.X + g.sum()
|
|
3224
|
-
", STD_HEAD);
|
|
3225
|
-
assert_eq!(runMerged::<i64>("const_names", &src), 1 + 20 + 300 + 321);
|
|
3226
|
-
}
|
|
3227
|
-
|
|
3228
|
-
#[test]
|
|
3229
|
-
fn primitive_builtin_declares_methods() {
|
|
3230
|
-
// `x.abs()` resolves through `primitive Int` in plum-std/Int.plum.
|
|
3231
|
-
let text = std::fs::read_to_string(repoRoot().join("plum-std/Int.plum")).unwrap();
|
|
3232
|
-
assert!(text.contains("primitive Int ="));
|
|
3233
|
-
let src = format!("{}\
|
|
3234
|
-
fun main() -> Int =
|
|
3235
|
-
{{-5}}.abs() + 3.max(9)
|
|
3236
|
-
", STD_HEAD);
|
|
3237
|
-
assert_eq!(runMerged::<i64>("prim_builtin", &src), 14);
|
|
3238
|
-
}
|
|
3239
|
-
|
|
3240
2328
|
#[test]
|
|
3241
2329
|
fn primitive_not_a_type() {
|
|
3242
2330
|
let head = STD_HEAD;
|
|
@@ -3328,12 +2416,6 @@ fn strIsOneStructWithOneByteArrayField() {
|
|
|
3328
2416
|
assert_eq!(runWithCrash("strshape", src).0.unwrap(), 5);
|
|
3329
2417
|
}
|
|
3330
2418
|
|
|
3331
|
-
#[test]
|
|
3332
|
-
fn bufferToStrLengthExactAfterDoubling() {
|
|
3333
|
-
let src = "import std/Str\nimport std/Buffer\nimport std/Int\nimport std/Float\n\nfun main() -> Int =\n b := Buffer()\n i := 0\n while i < 37\n b.write(\"abc\")\n i = i + 1\n b.toStr().length() * 1000 + b.length()\n";
|
|
3334
|
-
assert_eq!(runWithCrash("bufexact", src).0.unwrap(), 111111);
|
|
3335
|
-
}
|
|
3336
|
-
|
|
3337
2419
|
// ---- struct-of-arrays layout for Array[struct] ----
|
|
3338
2420
|
|
|
3339
2421
|
#[test]
|
plum-wasm-codegen/tests/examples_test.rs
CHANGED
|
@@ -43,63 +43,6 @@ fn assertCompiles(name: &str) -> Vec<u8> {
|
|
|
43
43
|
bytes
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
/// Examples that stick to currently-supported codegen features (primitives, control
|
|
47
|
-
/// flow, classes/methods) must actually compile to valid wasm — not just parse and
|
|
48
|
-
/// type-check.
|
|
49
|
-
#[test]
|
|
50
|
-
fn basicsCompiles() {
|
|
51
|
-
assertCompiles("basics.plum");
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
#[test]
|
|
55
|
-
fn controlFlowCompiles() {
|
|
56
|
-
assertCompiles("control_flow.plum");
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
#[test]
|
|
60
|
-
fn functionsCompiles() {
|
|
61
|
-
assertCompiles("functions.plum");
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
#[test]
|
|
65
|
-
fn structsRecordsCompiles() {
|
|
66
|
-
assertCompiles("structs_records.plum");
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// unions.plum/match.plum/closures.plum's actual runtime-correctness checks
|
|
70
|
-
// now live as native `test`/`expect` blocks inside those example files
|
|
71
|
-
// themselves (run via `plum test plum-examples/<name>.plum`, see
|
|
72
|
-
// scripts/test-plum.sh) — `assertCompiles` below still exercises that each
|
|
73
|
-
// still compiles to valid wasm via this crate's direct (non-CLI) path.
|
|
74
|
-
#[test]
|
|
75
|
-
fn unionsCompiles() {
|
|
76
|
-
assertCompiles("unions.plum");
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
#[test]
|
|
80
|
-
fn gameLoopCompiles() {
|
|
81
|
-
assertCompiles("game_loop.plum");
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
#[test]
|
|
85
|
-
fn matchExampleCompiles() {
|
|
86
|
-
assertCompiles("match.plum");
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
#[test]
|
|
90
|
-
fn closuresExampleCompiles() {
|
|
91
|
-
assertCompiles("closures.plum");
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/// strings.plum exercises string interpolation (plain literals, `Str`/`Int`
|
|
95
|
-
/// interpolation, and escape sequences) — it has no `main` to execute, so (matching
|
|
96
|
-
/// `basicsCompiles`/`controlFlowCompiles`/etc. above for other main-less examples)
|
|
97
|
-
/// this only checks that it compiles to a valid module.
|
|
98
|
-
#[test]
|
|
99
|
-
fn stringsExampleCompiles() {
|
|
100
|
-
assertCompiles("strings.plum");
|
|
101
|
-
}
|
|
102
|
-
|
|
103
46
|
/// io.plum exercises `extern fun` (see `Fn.is_extern`/`FnBody::Extern` in
|
|
104
47
|
/// plum-core/src/ast.rs and their handling in plum-wasm-codegen/src/lib.rs) — it
|
|
105
48
|
/// declares its own `extern fun printLn(s: Str)` (redeclared locally rather than a
|
|
@@ -211,9 +154,10 @@ fn ioExampleCompilesAndRunsCorrectly() {
|
|
|
211
154
|
/// not the boxed `array (mut anyref)`.
|
|
212
155
|
#[test]
|
|
213
156
|
fn array_int_native() {
|
|
214
|
-
use wasmparser::{CompositeInnerType, Parser, Payload, StorageType, ValType};
|
|
157
|
+
use wasmparser::{CompositeInnerType, HeapType, Parser, Payload, StorageType, ValType};
|
|
215
158
|
let bytes = assertCompiles("typed_arrays.plum");
|
|
216
159
|
let mut elems: Vec<ValType> = Vec::new();
|
|
160
|
+
let mut erased = false;
|
|
217
161
|
for payload in Parser::new(0).parse_all(&bytes) {
|
|
218
162
|
if let Payload::TypeSection(reader) = payload.unwrap() {
|
|
219
163
|
for rec in reader {
|
|
@@ -221,6 +165,9 @@ fn array_int_native() {
|
|
|
221
165
|
if let CompositeInnerType::Array(a) = &sub.composite_type.inner {
|
|
222
166
|
if a.0.mutable {
|
|
223
167
|
if let StorageType::Val(v) = a.0.element_type {
|
|
168
|
+
if let ValType::Ref(r) = v {
|
|
169
|
+
erased |= matches!(r.heap_type(), HeapType::Abstract { .. });
|
|
170
|
+
}
|
|
224
171
|
elems.push(v);
|
|
225
172
|
}
|
|
226
173
|
}
|
|
@@ -229,44 +176,12 @@ fn array_int_native() {
|
|
|
229
176
|
}
|
|
230
177
|
}
|
|
231
178
|
}
|
|
179
|
+
assert!(!erased, "mutable array of erased abstract ref: {:?}", elems);
|
|
232
180
|
assert!(elems.contains(&ValType::I64), "no (mut i64) array in {:?}", elems);
|
|
233
181
|
assert!(elems.contains(&ValType::F64), "no (mut f64) array in {:?}", elems);
|
|
234
182
|
assert!(elems.contains(&ValType::I32), "no (mut i32) array in {:?}", elems);
|
|
235
183
|
}
|
|
236
184
|
|
|
237
|
-
/// Game-perf 1: no mutable array is an erased abstract-ref array, and
|
|
238
|
-
/// `Array[Int]`/`Array[Float]` element storage is exactly i64/f64.
|
|
239
|
-
#[test]
|
|
240
|
-
fn array_elements_are_never_erased_anyref() {
|
|
241
|
-
use wasmparser::{CompositeInnerType, HeapType, Parser, Payload, StorageType, ValType};
|
|
242
|
-
let bytes = assertCompiles("typed_arrays.plum");
|
|
243
|
-
let mut mut_elems: Vec<StorageType> = Vec::new();
|
|
244
|
-
for payload in Parser::new(0).parse_all(&bytes) {
|
|
245
|
-
if let Payload::TypeSection(reader) = payload.unwrap() {
|
|
246
|
-
for rec in reader {
|
|
247
|
-
for sub in rec.unwrap().into_types() {
|
|
248
|
-
if let CompositeInnerType::Array(a) = &sub.composite_type.inner {
|
|
249
|
-
if a.0.mutable {
|
|
250
|
-
mut_elems.push(a.0.element_type);
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
for e in &mut_elems {
|
|
258
|
-
if let StorageType::Val(ValType::Ref(r)) = e {
|
|
259
|
-
assert!(
|
|
260
|
-
!matches!(r.heap_type(), HeapType::Abstract { .. }),
|
|
261
|
-
"mutable array of erased abstract ref: {:?}",
|
|
262
|
-
mut_elems
|
|
263
|
-
);
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
assert!(mut_elems.contains(&StorageType::Val(ValType::I64)));
|
|
267
|
-
assert!(mut_elems.contains(&StorageType::Val(ValType::F64)));
|
|
268
|
-
}
|
|
269
|
-
|
|
270
185
|
/// L1 `const_table_1000_entries`: 1000 associated consts (spread over two
|
|
271
186
|
/// bodies, before and between other decls) plus a 1000-element list literal
|
|
272
187
|
/// parse, type-check and are usable inside methods.
|
plum-wasm-codegen/tests/unions_e2e_test.rs
CHANGED
|
@@ -65,66 +65,6 @@ fn withDefs(body: &str) -> String {
|
|
|
65
65
|
format!("{DEFS}{body}")
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
-
#[test]
|
|
69
|
-
fn list_of_shapes() {
|
|
70
|
-
let src = withDefs(
|
|
71
|
-
"\
|
|
72
|
-
fun main() -> Int =
|
|
73
|
-
xs := List[Shape]()
|
|
74
|
-
xs.push(Circle(r: 1.0))
|
|
75
|
-
xs.push(Rectangle(w: 2.0, h: 3.0))
|
|
76
|
-
sum := 0.0
|
|
77
|
-
for x := range xs
|
|
78
|
-
sum = sum + x.area()
|
|
79
|
-
Int(sum)
|
|
80
|
-
",
|
|
81
|
-
);
|
|
82
|
-
assert_eq!(runInt("list", &src), 9);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
#[test]
|
|
86
|
-
fn struct_member_copy() {
|
|
87
|
-
let src = withDefs(
|
|
88
|
-
"\
|
|
89
|
-
fun make() -> Shape = Circle(r: 1.0)
|
|
90
|
-
|
|
91
|
-
fun main() -> Int =
|
|
92
|
-
s := make()
|
|
93
|
-
r := 0.0
|
|
94
|
-
if Circle := s
|
|
95
|
-
c := s
|
|
96
|
-
c.r = 5.0
|
|
97
|
-
r = c.r + s.r
|
|
98
|
-
Int(r)
|
|
99
|
-
",
|
|
100
|
-
);
|
|
101
|
-
assert_eq!(runInt("copy", &src), 6);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
#[test]
|
|
105
|
-
fn narrow_and_dispatch() {
|
|
106
|
-
let src = withDefs(
|
|
107
|
-
"\
|
|
108
|
-
fun pick(big: Bool) -> Shape =
|
|
109
|
-
if big
|
|
110
|
-
Rectangle(w: 10.0, h: 10.0)
|
|
111
|
-
else
|
|
112
|
-
Circle(r: 1.0)
|
|
113
|
-
|
|
114
|
-
fun wide(s: Shape) -> Float =
|
|
115
|
-
match s
|
|
116
|
-
Circle => 0.0
|
|
117
|
-
Rectangle => s.w
|
|
118
|
-
|
|
119
|
-
fun main() -> Int =
|
|
120
|
-
a := pick(true)
|
|
121
|
-
b := pick(false)
|
|
122
|
-
Int(a.area() + b.area() + wide(a))
|
|
123
|
-
",
|
|
124
|
-
);
|
|
125
|
-
assert_eq!(runInt("narrow", &src), 113);
|
|
126
|
-
}
|
|
127
|
-
|
|
128
68
|
#[test]
|
|
129
69
|
fn member_into_optional_and_error_unions() {
|
|
130
70
|
let src = withDefs(
|
|
@@ -187,22 +127,3 @@ fun go(p: Pair) -> Int =
|
|
|
187
127
|
"{errs:?}"
|
|
188
128
|
);
|
|
189
129
|
}
|
|
190
|
-
|
|
191
|
-
#[test]
|
|
192
|
-
fn bool_is_a_member() {
|
|
193
|
-
let src = withDefs(
|
|
194
|
-
"\
|
|
195
|
-
enum Jb = BNull | Bool | Float
|
|
196
|
-
|
|
197
|
-
fun code(j: Jb) -> Int =
|
|
198
|
-
match j
|
|
199
|
-
BNull => 0
|
|
200
|
-
Bool => if j then 1 else 2
|
|
201
|
-
Float => 3
|
|
202
|
-
|
|
203
|
-
fun main() -> Int =
|
|
204
|
-
code(true) * 100 + code(false) * 10 + code(1.5)
|
|
205
|
-
",
|
|
206
|
-
);
|
|
207
|
-
assert_eq!(runInt("boolmember", &src), 123);
|
|
208
|
-
}
|