plum

#treesitter#compiler#wasm

git clone https://git.pyrossh.dev/plum

A statically typed, imperative programming language inspired by rust, python


d94bce0 — Peter John 2026-10-25T16:57:08+05:30
fix(codegen): Try/?? wording in error messages; add numeric/deep/state probes
plum-wasm-codegen/src/lib.rs CHANGED
@@ -5398,7 +5398,7 @@ fn compileExpr(
5398
5398
  PlumType::TNamed(n) => n.clone(),
5399
5399
  other => {
5400
5400
  return Err(format!(
5401
- "codegen: '?' requires a Result or Option value, found {}",
5401
+ "codegen: '?' requires a Try value, found {}",
5402
5402
  other
5403
5403
  ))
5404
5404
  }
@@ -5412,7 +5412,7 @@ fn compileExpr(
5412
5412
  let (success_name, success_info, failure_name, _) =
5413
5413
  plum_checker::tryOperatorVariants(&enum_name, &cctx).ok_or_else(|| {
5414
5414
  format!(
5415
- "codegen: '?' requires a Result or Option value, found '{}'",
5415
+ "codegen: '?' requires a Try value, found '{}'",
5416
5416
  enum_name
5417
5417
  )
5418
5418
  })?;
@@ -5510,7 +5510,7 @@ fn compileExpr(
5510
5510
  PlumType::TNamed(n) => n.clone(),
5511
5511
  other => {
5512
5512
  return Err(format!(
5513
- "codegen: '?:' requires a Result or Option value, found {}",
5513
+ "codegen: '??' requires a Try value, found {}",
5514
5514
  other
5515
5515
  ))
5516
5516
  }
@@ -5524,7 +5524,7 @@ fn compileExpr(
5524
5524
  let (success_name, success_info, failure_name, _) =
5525
5525
  plum_checker::tryOperatorVariants(&enum_name, &cctx).ok_or_else(|| {
5526
5526
  format!(
5527
- "codegen: '?:' requires a Result or Option value, found '{}'",
5527
+ "codegen: '??' requires a Try value, found '{}'",
5528
5528
  enum_name
5529
5529
  )
5530
5530
  })?;
@@ -5560,7 +5560,7 @@ fn compileExpr(
5560
5560
  let slot = *ctx
5561
5561
  .nested_class_scratch
5562
5562
  .get(&key)
5563
- .ok_or_else(|| "internal codegen error: missing '?:' scratch slot".to_string())?;
5563
+ .ok_or_else(|| "internal codegen error: missing '??' scratch slot".to_string())?;
5564
5564
  let scratch_local = ctx.nested_class_scratch_base + slot;
5565
5565
 
5566
5566
  compileExpr(&e.left, body, ctx, state)?;
plum-wasm-codegen/tests/codegen_tests.rs CHANGED
@@ -1330,7 +1330,7 @@ fun main() -> Int =
1330
1330
  /// Regression (T5): the scratch-slot collector didn't descend into string
1331
1331
  /// interpolations, so a `??`/`?` inside `"{...}"` -- which is exactly what a
1332
1332
  /// failing `assert a ?? b == c` in a `test` block desugars to -- hit
1333
- /// "missing '?:' scratch slot" at compile time.
1333
+ /// "missing '??' scratch slot" at compile time.
1334
1334
  #[test]
1335
1335
  fn elvis_inside_string_interpolation_compiles() {
1336
1336
  let got = runTryMain(
@@ -1345,3 +1345,215 @@ fun main() -> Int =
1345
1345
  );
1346
1346
  assert_eq!(got, 1);
1347
1347
  }
1348
+
1349
+ #[test]
1350
+ fn probe_float_payload_negative_zero_preserved() {
1351
+ let got = runTryMain(
1352
+ "\
1353
+ fun make() -> Try[Int, [Frac(Float)]] =
1354
+ z := 0.0
1355
+ Err(Frac(-1.0 * z))
1356
+
1357
+ fun main() -> Int =
1358
+ match make()
1359
+ Ok(_) => 0
1360
+ Err(Frac(f)) =>
1361
+ if 1.0 / f < 0.0
1362
+ return 1
1363
+ return 2
1364
+ ",
1365
+ );
1366
+ assert_eq!(got, 1);
1367
+ }
1368
+
1369
+ #[test]
1370
+ fn probe_int_min_payload() {
1371
+ let got = runTryMain(
1372
+ "\
1373
+ fun make() -> Try[Int, [Big(Int)]] =
1374
+ m := 0 - 9223372036854775807 - 1
1375
+ Err(Big(m))
1376
+
1377
+ fun main() -> Int =
1378
+ match make()
1379
+ Ok(_) => 0
1380
+ Err(Big(n)) =>
1381
+ if n < 0 - 9223372036854775807
1382
+ return 1
1383
+ return 2
1384
+ ",
1385
+ );
1386
+ assert_eq!(got, 1);
1387
+ }
1388
+
1389
+ #[test]
1390
+ fn probe_subnormal_float_payload() {
1391
+ let got = runTryMain(
1392
+ "\
1393
+ fun make() -> Try[Int, [Frac(Float)]] =
1394
+ Err(Frac(5e-324))
1395
+
1396
+ fun main() -> Int =
1397
+ match make()
1398
+ Ok(_) => 0
1399
+ Err(Frac(f)) =>
1400
+ if f > 0.0
1401
+ if f < 1e-320
1402
+ return 1
1403
+ return 3
1404
+ return 2
1405
+ ",
1406
+ );
1407
+ assert_eq!(got, 1);
1408
+ }
1409
+
1410
+ // OPEN: a tag's payload sub-pattern may only be a name or `_` (codegen rejects
1411
+ // "unsupported pattern inside tag 'Wrap' payload"), and with a generic enum as the
1412
+ // payload (`Wrap(Try[..])`) it currently panics earlier ("'Ok' missing from the GC
1413
+ // type registry") because monomorphize never specializes a tag-payload type.
1414
+ #[test]
1415
+ #[ignore = "constructor sub-pattern inside a tag payload is unsupported (panics 'Ok' missing from the GC type registry)"]
1416
+ fn probe_three_level_nested_tag_pattern_generic_payload() {
1417
+ let prog = |ctor: &str| {
1418
+ format!(
1419
+ "\
1420
+ fun make() -> Try[Int, [Wrap(Try[Int, [Inner, Other]]), Top]] =
1421
+ {}
1422
+
1423
+ fun main() -> Int =
1424
+ match make()
1425
+ Ok(v) => v
1426
+ Err(Top) => 1
1427
+ Err(Wrap(Ok(n))) => n
1428
+ Err(Wrap(Err(Inner))) => 2
1429
+ Err(Wrap(_)) => 3
1430
+ ",
1431
+ ctor
1432
+ )
1433
+ };
1434
+ assert_eq!(runTryMain(&prog("Ok(70)")), 70);
1435
+ assert_eq!(runTryMain(&prog("Err(Top)")), 1);
1436
+ assert_eq!(runTryMain(&prog("Err(Wrap(Ok(80)))")), 80);
1437
+ assert_eq!(runTryMain(&prog("Err(Wrap(Err(Inner)))")), 2);
1438
+ assert_eq!(runTryMain(&prog("Err(Wrap(Err(Other)))")), 3);
1439
+ }
1440
+
1441
+ #[test]
1442
+ fn probe_nested_generic_mangled_name_roundtrip() {
1443
+ let got = runTryMain(
1444
+ "\
1445
+ enum Box[T] =
1446
+ | Item(T)
1447
+
1448
+ fun make() -> Try[Box[Try[Int, [A]]], [B]] =
1449
+ Ok(Item(Ok(41)))
1450
+
1451
+ fun main() -> Int =
1452
+ match make()
1453
+ Err(_) => 0
1454
+ Ok(Item(inner)) =>
1455
+ match inner
1456
+ Ok(n) => n + 1
1457
+ Err(_) => 2
1458
+ ",
1459
+ );
1460
+ assert_eq!(got, 42);
1461
+ }
1462
+
1463
+ #[test]
1464
+ fn probe_question_mark_merges_two_ok_types() {
1465
+ let prog = |a: &str, b: &str| {
1466
+ format!(
1467
+ "\
1468
+ fun getInt() -> Try[Int, [A]] =
1469
+ {}
1470
+
1471
+ fun getStr() -> Try[Str, [B]] =
1472
+ {}
1473
+
1474
+ fun both() -> Try[Int, [A, B]] =
1475
+ n := getInt()?
1476
+ s := getStr()?
1477
+ Ok(n)
1478
+
1479
+ fun main() -> Int =
1480
+ match both()
1481
+ Ok(v) => v
1482
+ Err(A) => 100
1483
+ Err(B) => 200
1484
+ ",
1485
+ a, b
1486
+ )
1487
+ };
1488
+ assert_eq!(runTryMain(&prog("Ok(5)", "Ok(\"x\")")), 5);
1489
+ assert_eq!(runTryMain(&prog("Err(A)", "Ok(\"x\")")), 100);
1490
+ assert_eq!(runTryMain(&prog("Ok(5)", "Err(B)")), 200);
1491
+ }
1492
+
1493
+ #[test]
1494
+ fn probe_question_mark_inside_closure_same_try_type() {
1495
+ let prog = |inner: &str| {
1496
+ format!(
1497
+ "\
1498
+ fun inner() -> Try[Int, [A]] =
1499
+ {}
1500
+
1501
+ fun outer() -> Try[Int, [A]] =
1502
+ f := || Ok(inner()? + 1)
1503
+ f()
1504
+
1505
+ fun main() -> Int =
1506
+ match outer()
1507
+ Ok(v) => v
1508
+ Err(A) => 100
1509
+ ",
1510
+ inner
1511
+ )
1512
+ };
1513
+ assert_eq!(runTryMain(&prog("Ok(5)")), 6);
1514
+ assert_eq!(runTryMain(&prog("Err(A)")), 100);
1515
+ }
1516
+
1517
+ #[test]
1518
+ fn probe_three_level_nested_tag_pattern() {
1519
+ let prog = |ctor: &str| {
1520
+ format!(
1521
+ "\
1522
+ fun make() -> Try[Int, [Wrap([Inner, Other]), Top]] =
1523
+ {}
1524
+
1525
+ fun main() -> Int =
1526
+ match make()
1527
+ Ok(v) => v
1528
+ Err(Top) => 1
1529
+ Err(Wrap(w)) =>
1530
+ match w
1531
+ Inner => 2
1532
+ Other => 3
1533
+ ",
1534
+ ctor
1535
+ )
1536
+ };
1537
+ assert_eq!(runTryMain(&prog("Ok(70)")), 70);
1538
+ assert_eq!(runTryMain(&prog("Err(Top)")), 1);
1539
+ assert_eq!(runTryMain(&prog("Err(Wrap(Inner))")), 2);
1540
+ assert_eq!(runTryMain(&prog("Err(Wrap(Other))")), 3);
1541
+ }
1542
+
1543
+ #[test]
1544
+ fn probe_three_level_nested_ok_pattern() {
1545
+ let got = runTryMain(
1546
+ "\
1547
+ fun make() -> Try[Try[Try[Int, [A]], [B]], [C]] =
1548
+ Ok(Ok(Ok(9)))
1549
+
1550
+ fun main() -> Int =
1551
+ match make()
1552
+ Ok(Ok(Ok(n))) => n
1553
+ Ok(Ok(_)) => 1
1554
+ Ok(_) => 2
1555
+ Err(_) => 3
1556
+ ",
1557
+ );
1558
+ assert_eq!(got, 9);
1559
+ }