plum
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
|
|
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
|
+
}
|