plum

#treesitter#compiler#wasm

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

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


8336a15 — Peter John 2026-10-25T16:08:35+05:30
fix(codegen): crash import detection ignores string literals; add tag probe tests
plum-wasm-codegen/src/lib.rs CHANGED
@@ -534,6 +534,32 @@ thread_local! {
534
534
  /// Import key of the `plum::crash(Str)` host function in `func_ids`.
535
535
  const CRASH_FN_KEY: &str = "__crash";
536
536
 
537
+ /// Whether the `Debug` dump of the AST contains a `Crash(` constructor token
538
+ /// outside any quoted string (string literals and identifiers are quoted in
539
+ /// `Debug` output, so they can't produce a false match).
540
+ fn sourceUsesCrash(dbg: &str) -> bool {
541
+ let b = dbg.as_bytes();
542
+ let mut i = 0;
543
+ while i < b.len() {
544
+ match b[i] {
545
+ b'"' => {
546
+ i += 1;
547
+ while i < b.len() && b[i] != b'"' {
548
+ i += if b[i] == b'\\' { 2 } else { 1 };
549
+ }
550
+ i += 1;
551
+ }
552
+ b'C' if dbg[i..].starts_with("Crash(")
553
+ && (i == 0 || matches!(b[i - 1], b'[' | b'(' | b' ')) =>
554
+ {
555
+ return true
556
+ }
557
+ _ => i += 1,
558
+ }
559
+ }
560
+ false
561
+ }
562
+
537
563
  fn tagId(name: &str) -> i32 {
538
564
  CURRENT_TAG_IDS.with(|c| {
539
565
  let mut m = c.borrow_mut();
@@ -1576,10 +1602,7 @@ fn compileSourceInner(
1576
1602
  // `plum::crash(Str)`: same wasm type as `printLn`; imported only when the
1577
1603
  // program actually contains a `crash` statement.
1578
1604
  {
1579
- let dbg = format!("{:?}", source.items);
1605
+ let uses_crash = sourceUsesCrash(&format!("{:?}", source.items));
1580
- let uses_crash = dbg
1581
- .match_indices("Crash(")
1582
- .any(|(i, _)| i == 0 || matches!(dbg.as_bytes()[i - 1], b'[' | b'(' | b' '));
1583
1606
  if uses_crash {
1584
1607
  let str_vt = plumTypeToValtype(&PlumType::TStr);
1585
1608
  let type_idx = module.addType(&[str_vt], &[]);
plum-wasm-codegen/tests/codegen_tests.rs CHANGED
@@ -1237,3 +1237,92 @@ fun main() -> Int = 1
1237
1237
  let wider = assertValid(&prog("[A, B]"));
1238
1238
  assert_eq!(countTypes(&same), countTypes(&wider));
1239
1239
  }
1240
+
1241
+ #[test]
1242
+ fn crash_import_not_triggered_by_string_literal() {
1243
+ let src = "fun main() -> Int =\n s := \"x Crash(y) [Crash(\"\n 1\n";
1244
+ let bytes = assertValid(src);
1245
+ let engine = gcEngine();
1246
+ let module = wasmtime::Module::new(&engine, &bytes).unwrap();
1247
+ assert!(module.imports().all(|i| i.name() != "crash"));
1248
+ }
1249
+
1250
+ #[test]
1251
+ fn probe_numeric_extremes() {
1252
+ let prog = |v: &str| {
1253
+ format!(
1254
+ "fun make() -> Try[Int, [Big(Int), NotFound]] =\n Err(Big({}))\n\nfun main() -> Int =\n match make()\n Ok(v) => v\n Err(NotFound) => 0\n Err(Big(n)) => n\n",
1255
+ v
1256
+ )
1257
+ };
1258
+ assert_eq!(runTryMain(&prog("9223372036854775807")), i64::MAX);
1259
+ assert_eq!(runTryMain(&prog("-9007199254740993")), -9007199254740993);
1260
+ assert_eq!(runTryMain(&prog("0 - 5")), -5);
1261
+ }
1262
+
1263
+ #[test]
1264
+ fn probe_tag_arg_and_eq_var() {
1265
+ let got = runTryMain(
1266
+ "\
1267
+ fun f(t: [A, B]) -> Int =
1268
+ match t
1269
+ A => 1
1270
+ B => 2
1271
+
1272
+ fun main() -> Int =
1273
+ x := B
1274
+ r := f(B)
1275
+ if x == B
1276
+ return r + 10
1277
+ return r
1278
+ ",
1279
+ );
1280
+ assert_eq!(got, 12);
1281
+ }
1282
+
1283
+ #[test]
1284
+ fn probe_question_mark_payload_tag_and_str_payload() {
1285
+ let prog = |inner: &str| {
1286
+ format!(
1287
+ "\
1288
+ fun inner() -> Try[Int, [Bad(Str)]] =
1289
+ {}
1290
+
1291
+ fun outer() -> Try[Str, [Bad(Str), Other(Int)]] =
1292
+ x := inner()?
1293
+ Ok(\"fine\")
1294
+
1295
+ fun main() -> Int =
1296
+ match outer()
1297
+ Ok(_) => 1
1298
+ Err(Bad(s)) =>
1299
+ t := s
1300
+ 4
1301
+ Err(Other(_)) => 3
1302
+ ",
1303
+ inner
1304
+ )
1305
+ };
1306
+ assert_eq!(runTryMain(&prog("Err(Bad(\"abcd\"))")), 4);
1307
+ assert_eq!(runTryMain(&prog("Ok(1)")), 1);
1308
+ }
1309
+
1310
+ #[test]
1311
+ fn probe_float_negative_zero_and_nan_payload() {
1312
+ let got = runTryMain(
1313
+ "\
1314
+ fun nan() -> Try[Int, [Frac(Float)]] =
1315
+ z := 0.0
1316
+ Err(Frac(z / z))
1317
+
1318
+ fun main() -> Int =
1319
+ match nan()
1320
+ Ok(_) => 0
1321
+ Err(Frac(f)) =>
1322
+ if f == f
1323
+ return 20
1324
+ return 10
1325
+ ",
1326
+ );
1327
+ assert_eq!(got, 10);
1328
+ }