plum
git clone https://git.pyrossh.dev/plum
A statically typed, imperative programming language inspired by rust, python
5e131a3
— Peter John
2026-07-20T12:14:54+05:30
fix(plum-wasm-codegen): guard constructor-pattern match against payload-free sibling values
plum-wasm-codegen/src/lib.rs
CHANGED
|
@@ -978,10 +978,22 @@ fn compile_variant_constructor_arm(
|
|
|
978
978
|
let tag = info.tag;
|
|
979
979
|
let field_types = info.field_types.clone();
|
|
980
980
|
|
|
981
|
+
// A constructor pattern can only match if the runtime subject is actually
|
|
982
|
+
// a heap pointer (payload variants are always >= HEAP_BASE); a
|
|
983
|
+
// payload-free sibling variant is a small int tag, and loading i32 from
|
|
984
|
+
// that address would read unrelated/zeroed memory instead of a real tag.
|
|
985
|
+
// Guard with a range check before doing the I32Load.
|
|
986
|
+
Instruction::LocalGet(scratch_local).encode(body);
|
|
987
|
+
Instruction::I32Const(HEAP_BASE as i32).encode(body);
|
|
988
|
+
Instruction::I32GeU.encode(body);
|
|
989
|
+
Instruction::If(BlockType::Result(ValType::I32)).encode(body);
|
|
981
990
|
Instruction::LocalGet(scratch_local).encode(body);
|
|
982
991
|
Instruction::I32Load(MemArg { offset: 0, align: 2, memory_index: 0 }).encode(body);
|
|
983
992
|
Instruction::I32Const(tag).encode(body);
|
|
984
993
|
Instruction::I32Eq.encode(body);
|
|
994
|
+
Instruction::Else.encode(body);
|
|
995
|
+
Instruction::I32Const(0).encode(body);
|
|
996
|
+
Instruction::End.encode(body);
|
|
985
997
|
Instruction::If(BlockType::Empty).encode(body);
|
|
986
998
|
for (i, (pat, field_ty)) in fields.iter().zip(field_types.iter()).enumerate() {
|
|
987
999
|
let bind_name = match pat {
|
plum-wasm-codegen/tests/codegen_tests.rs
CHANGED
|
@@ -159,6 +159,36 @@ fn match_string_pattern_is_a_clear_error() {
|
|
|
159
159
|
assert!(err.contains("string match"), "got: {}", err);
|
|
160
160
|
}
|
|
161
161
|
|
|
162
|
+
#[test]
|
|
163
|
+
fn nested_constructor_pattern_is_a_clear_error() {
|
|
164
|
+
let src = "\
|
|
165
|
+
enum Option =
|
|
166
|
+
| Some(Int)
|
|
167
|
+
| None
|
|
168
|
+
|
|
169
|
+
enum Nested =
|
|
170
|
+
| Wrap(Option)
|
|
171
|
+
| Empty
|
|
172
|
+
|
|
173
|
+
f(n: Nested) -> Int =
|
|
174
|
+
match n
|
|
175
|
+
Wrap(Some(v)) =>
|
|
176
|
+
return v
|
|
177
|
+
Empty =>
|
|
178
|
+
return 0
|
|
179
|
+
|
|
180
|
+
main() -> Int =
|
|
181
|
+
f(Empty)
|
|
182
|
+
";
|
|
183
|
+
let source = parse(src);
|
|
184
|
+
let err = compile_source(&source).expect_err("nested constructor patterns are not yet supported");
|
|
185
|
+
assert!(
|
|
186
|
+
err.contains("only bare bindings or '_' are supported inside a constructor pattern"),
|
|
187
|
+
"got: {}",
|
|
188
|
+
err
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
|
|
162
192
|
/// Runs `main`'s wasm bytes and returns its i64 result. wasm's own validator
|
|
163
193
|
/// (via wasmparser, above) only proves the module is well-formed — it can't catch
|
|
164
194
|
/// wrong *values*, so these tests actually execute the compiled output.
|
|
@@ -469,3 +499,56 @@ main() -> Int =
|
|
|
469
499
|
let bytes = compile_source(&source).expect("compile failed");
|
|
470
500
|
assert_eq!(run_main(&bytes), 1);
|
|
471
501
|
}
|
|
502
|
+
|
|
503
|
+
#[test]
|
|
504
|
+
fn constructor_pattern_does_not_misfire_on_payload_free_sibling() {
|
|
505
|
+
// Regression test: `None` is a small-int tag, not a heap pointer. The
|
|
506
|
+
// constructor-pattern arm for `Some(v)` must not treat a payload-free
|
|
507
|
+
// sibling value as if it were a pointer to a `Some` payload.
|
|
508
|
+
let src = "\
|
|
509
|
+
enum Option =
|
|
510
|
+
| Some(Int)
|
|
511
|
+
| None
|
|
512
|
+
|
|
513
|
+
unwrapOr(o: Option, default: Int) -> Int =
|
|
514
|
+
match o
|
|
515
|
+
Some(v) =>
|
|
516
|
+
return v
|
|
517
|
+
None =>
|
|
518
|
+
return default
|
|
519
|
+
|
|
520
|
+
main() -> Int =
|
|
521
|
+
unwrapOr(None, 5)
|
|
522
|
+
";
|
|
523
|
+
let source = parse(src);
|
|
524
|
+
let bytes = compile_source(&source).expect("compile failed");
|
|
525
|
+
assert_eq!(run_main(&bytes), 5);
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
#[test]
|
|
529
|
+
fn enum_class_field_construct_and_destructure_runs_correctly() {
|
|
530
|
+
// Interop check: a class with a field of an enum type, constructed with a
|
|
531
|
+
// payload variant, then matched via the class field.
|
|
532
|
+
let src = "\
|
|
533
|
+
enum Option =
|
|
534
|
+
| Some(Int)
|
|
535
|
+
| None
|
|
536
|
+
|
|
537
|
+
type Box =
|
|
538
|
+
value: Option
|
|
539
|
+
|
|
540
|
+
unwrap<Box>(default: Int) -> Int =
|
|
541
|
+
match self.value
|
|
542
|
+
Some(v) =>
|
|
543
|
+
return v
|
|
544
|
+
None =>
|
|
545
|
+
return default
|
|
546
|
+
|
|
547
|
+
main() -> Int =
|
|
548
|
+
b = Box(value: Some(42))
|
|
549
|
+
b.unwrap(0)
|
|
550
|
+
";
|
|
551
|
+
let source = parse(src);
|
|
552
|
+
let bytes = compile_source(&source).expect("compile failed");
|
|
553
|
+
assert_eq!(run_main(&bytes), 42);
|
|
554
|
+
}
|