plum
git clone https://git.pyrossh.dev/plum
A statically typed, imperative programming language inspired by rust, python
b3bb7b5
— Peter John
2026-10-26T10:24:18+05:30
feat!: Bool is a built-in i32 primitive
- plum-checker/src/lib.rs +7 -16
- plum-checker/tests/checker_tests.rs +35 -0
- plum-std/Bool.plum +8 -15
- plum-wasm-codegen/src/lib.rs +247 -129
- plum-wasm-codegen/tests/codegen_tests.rs +97 -0
plum-checker/src/lib.rs
CHANGED
|
@@ -483,17 +483,6 @@ pub fn unify(t1: &PlumType, t2: &PlumType, ctx: &CheckCtx) -> Result<(), String>
|
|
|
483
483
|
{
|
|
484
484
|
Ok(())
|
|
485
485
|
}
|
|
486
|
-
// Same reasoning as `Str` above — `Bool` is an ordinary enum now
|
|
487
|
-
// (`enum Bool = | True | False` in `libs/std/Bool.plum`), so a bare
|
|
488
|
-
// `True`/`False`/`Ok(...)`-wrapped-variant reference can infer as
|
|
489
|
-
// `TNamed("Bool")` (via `EnumVariantInfo`/`bareVariantWrapType`)
|
|
490
|
-
// while a `Bool` type ANNOTATION resolves via `plumTypeFromName` to
|
|
491
|
-
// the dedicated `TBool` variant instead.
|
|
492
|
-
(PlumType::TBool, PlumType::TNamed(n)) | (PlumType::TNamed(n), PlumType::TBool)
|
|
493
|
-
if n == "Bool" =>
|
|
494
|
-
{
|
|
495
|
-
Ok(())
|
|
496
|
-
}
|
|
497
486
|
(PlumType::TByte, PlumType::TByte) => Ok(()),
|
|
498
487
|
(PlumType::TByteSlice, PlumType::TByteSlice) => Ok(()),
|
|
499
488
|
(PlumType::TUnit, PlumType::TUnit) => Ok(()),
|
|
@@ -2184,8 +2173,11 @@ fn checkPattern(
|
|
|
2184
2173
|
ast::CasePattern::Int(_) => unify(subject_ty, &PlumType::TInt, ctx),
|
|
2185
2174
|
ast::CasePattern::Float(_) => unify(subject_ty, &PlumType::TFloat, ctx),
|
|
2186
2175
|
ast::CasePattern::String(_) => unify(subject_ty, &PlumType::TStr, ctx),
|
|
2176
|
+
ast::CasePattern::Name(n) if n == "True" || n == "False" => {
|
|
2177
|
+
unify(subject_ty, &PlumType::TBool, ctx)
|
|
2178
|
+
}
|
|
2187
2179
|
ast::CasePattern::Name(n) => {
|
|
2188
|
-
let is_known_variant =
|
|
2180
|
+
let is_known_variant =n.chars().next().map(|c| c.is_uppercase()).unwrap_or(false)
|
|
2189
2181
|
&& ctx.enum_variants.contains_key(n);
|
|
2190
2182
|
if is_known_variant {
|
|
2191
2183
|
Ok(()) // equality check against a known enum tag, e.g. `True`
|
|
@@ -2425,7 +2417,7 @@ fn isTagLiteralName(n: &str, ctx: &CheckCtx) -> bool {
|
|
|
2425
2417
|
n.chars().next().is_some_and(|c| c.is_uppercase())
|
|
2426
2418
|
&& !matches!(
|
|
2427
2419
|
n,
|
|
2428
|
-
"Int" | "Float" | "Bool" | "Str" | "Byte" | "Unit" | "Self"
|
|
2420
|
+
"Int" | "Float" | "Bool" | "Str" | "Byte" | "Unit" | "Self" | "True" | "False"
|
|
2429
2421
|
)
|
|
2430
2422
|
&& !ctx.enum_variants.contains_key(n)
|
|
2431
2423
|
&& !ctx.enum_variants.values().any(|i| i.enum_name == n)
|
|
@@ -2738,12 +2730,11 @@ fn inferExprInner(
|
|
|
2738
2730
|
// const (`PI`, `MAX_VALUE`, ...), since there's no separate "constant"
|
|
2739
2731
|
// token. Prefer a matching const's real type over the enum-variant/
|
|
2740
2732
|
// unmodeled-type-name fallbacks below.
|
|
2733
|
+
// `True`/`False` are built-in `Bool` literals, always available.
|
|
2734
|
+
ast::Expr::TypeName(n) if n == "True" || n == "False" => Ok(PlumType::TBool),
|
|
2741
2735
|
ast::Expr::TypeName(n) => match lookup(env, n) {
|
|
2742
2736
|
Ok(ty) if !matches!(ty, PlumType::TVar(_)) => Ok(ty),
|
|
2743
2737
|
_ => match ctx.enum_variants.get(n) {
|
|
2744
|
-
// `Bool` normalizes to the dedicated `TBool` variant (not
|
|
2745
|
-
// `TNamed("Bool")`) — see the note on `unify`'s Bool arm.
|
|
2746
|
-
Some(info) if info.enum_name == "Bool" => Ok(PlumType::TBool),
|
|
2747
2738
|
Some(info) => {
|
|
2748
2739
|
let (_, result, binds) = startVariant(info, &[], expected);
|
|
2749
2740
|
Ok(substVars(&result, &binds))
|
plum-checker/tests/checker_tests.rs
CHANGED
|
@@ -2239,3 +2239,38 @@ enum Box[T] =
|
|
|
2239
2239
|
let result = checkSource(&parse(src));
|
|
2240
2240
|
assert!(result.is_ok(), "got {:?}", result.err());
|
|
2241
2241
|
}
|
|
2242
|
+
|
|
2243
|
+
// ---------- built-in Bool (no `import std/Bool`, no `enum Bool`) ----------
|
|
2244
|
+
|
|
2245
|
+
#[test]
|
|
2246
|
+
fn builtin_bool_needs_no_import() {
|
|
2247
|
+
let src = "\
|
|
2248
|
+
fun both(a: Bool, b: Bool) -> Bool =
|
|
2249
|
+
a && !b || False
|
|
2250
|
+
|
|
2251
|
+
fun main() -> Bool =
|
|
2252
|
+
x := True
|
|
2253
|
+
x == both(True, False)
|
|
2254
|
+
";
|
|
2255
|
+
let r = checkSource(&parseClean(src));
|
|
2256
|
+
assert!(r.is_ok(), "got {:?}", r.err());
|
|
2257
|
+
}
|
|
2258
|
+
|
|
2259
|
+
#[test]
|
|
2260
|
+
fn builtin_bool_match_missing_false_is_rejected() {
|
|
2261
|
+
let r = errText(checkSource(&parseClean(
|
|
2262
|
+
"fun name(b: Bool) -> Str =\n match b\n True => \"y\"\n",
|
|
2263
|
+
)));
|
|
2264
|
+
assert!(r.contains("non-exhaustive match on Bool: missing False"), "got: {}", r);
|
|
2265
|
+
let ok = checkSource(&parseClean(
|
|
2266
|
+
"fun name(b: Bool) -> Str =\n match b\n True => \"y\"\n False => \"n\"\n",
|
|
2267
|
+
));
|
|
2268
|
+
assert!(ok.is_ok(), "got {:?}", ok.err());
|
|
2269
|
+
}
|
|
2270
|
+
|
|
2271
|
+
#[test]
|
|
2272
|
+
fn true_false_are_never_tags() {
|
|
2273
|
+
let src = "fun f() -> Int =\n x := True\n y := False\n x == y ? 1 : 0\n";
|
|
2274
|
+
let r = checkSource(&parseClean(src));
|
|
2275
|
+
assert!(r.is_ok(), "got {:?}", r.err());
|
|
2276
|
+
}
|
plum-std/Bool.plum
CHANGED
|
@@ -2,9 +2,12 @@ module std
|
|
|
2
2
|
import std/Try
|
|
3
3
|
import std/Str
|
|
4
4
|
|
|
5
|
+
# `Bool` is a built-in primitive (a wasm i32, 0/1); `True`/`False` are always
|
|
6
|
+
# available literals and need no import. This file only provides the METHODS
|
|
7
|
+
# whose receiver is a `Bool` (the single-variant enum is a pure marker, exactly
|
|
8
|
+
# like `Byte.plum`/`Number.plum`).
|
|
5
9
|
enum Bool =
|
|
6
|
-
|
|
|
10
|
+
| Bool
|
|
7
|
-
| False
|
|
8
11
|
|
|
9
12
|
fun parse(v: Str) -> Try[Bool, [BadBoolStr]] =
|
|
10
13
|
if v == "true"
|
|
@@ -15,20 +18,10 @@ enum Bool =
|
|
|
15
18
|
Err(BadBoolStr)
|
|
16
19
|
|
|
17
20
|
fun and(self, o: Bool) -> Bool =
|
|
18
|
-
match self, o
|
|
19
|
-
True, True => True
|
|
20
|
-
True, False => False
|
|
21
|
-
False, True => False
|
|
22
|
-
|
|
21
|
+
self && o
|
|
23
22
|
|
|
24
23
|
fun or(self, o: Bool) -> Bool =
|
|
25
|
-
|
|
24
|
+
self || o
|
|
26
|
-
True, True => True
|
|
27
|
-
True, False => True
|
|
28
|
-
False, True => True
|
|
29
|
-
False, False => False
|
|
30
25
|
|
|
31
26
|
fun toStr(self) -> Str =
|
|
32
|
-
match self
|
|
33
|
-
True => "True"
|
|
34
|
-
|
|
27
|
+
self ? "True" : "False"
|
plum-wasm-codegen/src/lib.rs
CHANGED
|
@@ -643,6 +643,10 @@ fn collectTagNamesTy(ty: &PlumType, out: &mut std::collections::BTreeSet<String>
|
|
|
643
643
|
/// as an `anyref`: `Int`/`Float`/`Byte` get a `$BoxI64`/`$BoxF64`/`$BoxI32`,
|
|
644
644
|
/// reference values are stored as-is.
|
|
645
645
|
fn boxTagPayload(ty: &PlumType, body: &mut Vec<u8>, ctx: &LocalCtx) {
|
|
646
|
+
if matches!(ty, PlumType::TBool) {
|
|
647
|
+
Instruction::Call(ctx.gc_types.box_bool_fn).encode(body);
|
|
648
|
+
return;
|
|
649
|
+
}
|
|
646
650
|
match plumTypeToValtype(ty) {
|
|
647
651
|
ValType::I64 => Instruction::Call(ctx.gc_types.box_i64_fn).encode(body),
|
|
648
652
|
ValType::F64 => Instruction::Call(ctx.gc_types.box_f64_fn).encode(body),
|
|
@@ -651,6 +655,33 @@ fn boxTagPayload(ty: &PlumType, body: &mut Vec<u8>, ctx: &LocalCtx) {
|
|
|
651
655
|
}
|
|
652
656
|
}
|
|
653
657
|
|
|
658
|
+
/// Stand-in plum type for "any erased reference" in `coerceTy` calls.
|
|
659
|
+
fn anyTy() -> PlumType {
|
|
660
|
+
// A function type lowers to `anyref`, the closure ABI's erased slot type.
|
|
661
|
+
PlumType::TFun(Vec::new(), Box::new(PlumType::TUnit))
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
/// `coerceVt` with static types: a `Bool` and a `Byte` are both wasm `i32`, so
|
|
665
|
+
/// boxing/unboxing across an erased edge must look at the plum type to pick
|
|
666
|
+
/// `$BoxBool` over `$BoxI32`.
|
|
667
|
+
fn coerceTy(from: &PlumType, to: &PlumType, body: &mut Vec<u8>, g: &GcTypeRegistry) {
|
|
668
|
+
let (fv, tv) = (plumTypeToGcValtype(from, g), plumTypeToGcValtype(to, g));
|
|
669
|
+
match (from, to) {
|
|
670
|
+
(PlumType::TBool, _) if matches!(tv, ValType::Ref(_)) => {
|
|
671
|
+
Instruction::Call(g.box_bool_fn).encode(body)
|
|
672
|
+
}
|
|
673
|
+
(_, PlumType::TBool) if matches!(fv, ValType::Ref(_)) => {
|
|
674
|
+
Instruction::RefCastNonNull(HeapType::Concrete(g.box_bool_idx)).encode(body);
|
|
675
|
+
Instruction::StructGet {
|
|
676
|
+
struct_type_index: g.box_bool_idx,
|
|
677
|
+
field_index: fld(0),
|
|
678
|
+
}
|
|
679
|
+
.encode(body);
|
|
680
|
+
}
|
|
681
|
+
_ => coerceVt(fv, tv, body, g),
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
|
|
654
685
|
/// Converts the value on top of the stack from representation `from` to `to`
|
|
655
686
|
/// (the erased-generics edge conversions): a primitive entering a reference slot
|
|
656
687
|
/// (`ref null $Obj` / `anyref`) is boxed; a reference leaving such a slot for a
|
|
@@ -707,8 +738,12 @@ fn compileExprAs(
|
|
|
707
738
|
) -> Result<(), String> {
|
|
708
739
|
compileExpr(expr, body, ctx, state)?;
|
|
709
740
|
if let Some(vt) = expected {
|
|
710
|
-
let
|
|
741
|
+
let own_ty = inferLocalType(expr, ctx);
|
|
742
|
+
if matches!(own_ty, PlumType::TBool) && matches!(vt, ValType::Ref(_)) {
|
|
711
|
-
|
|
743
|
+
coerceTy(&own_ty, &anyTy(), body, ctx.gc_types);
|
|
744
|
+
} else {
|
|
745
|
+
coerceVt(plumTypeToValtype(&own_ty), vt, body, ctx.gc_types);
|
|
746
|
+
}
|
|
712
747
|
}
|
|
713
748
|
Ok(())
|
|
714
749
|
}
|
|
@@ -762,6 +797,10 @@ fn unboxTagPayload(ty: &PlumType, body: &mut Vec<u8>, ctx: &LocalCtx) {
|
|
|
762
797
|
}
|
|
763
798
|
.encode(body);
|
|
764
799
|
};
|
|
800
|
+
if matches!(ty, PlumType::TBool) {
|
|
801
|
+
unbox(ctx.gc_types.box_bool_idx, body);
|
|
802
|
+
return;
|
|
803
|
+
}
|
|
765
804
|
match plumTypeToValtype(ty) {
|
|
766
805
|
ValType::I64 => unbox(ctx.gc_types.box_i64_idx, body),
|
|
767
806
|
ValType::F64 => unbox(ctx.gc_types.box_f64_idx, body),
|
|
@@ -892,13 +931,7 @@ fn plumTypeToValtype(t: &PlumType) -> ValType {
|
|
|
892
931
|
PlumType::TApp(n, _) => plumTypeToValtype(&PlumType::TNamed(n.clone())),
|
|
893
932
|
PlumType::TInt => ValType::I64,
|
|
894
933
|
PlumType::TFloat => ValType::F64,
|
|
895
|
-
PlumType::TBool =>
|
|
934
|
+
PlumType::TBool => ValType::I32,
|
|
896
|
-
gcRef(
|
|
897
|
-
*r.enum_super_type_idx
|
|
898
|
-
.get("Bool")
|
|
899
|
-
.expect("Bool must be registered"),
|
|
900
|
-
)
|
|
901
|
-
}),
|
|
902
935
|
// `Str` is an ORDINARY class (`type Str(...) = data: Buffer ...` in
|
|
903
936
|
// `str.plum`) — its own real struct type, registered exactly like any
|
|
904
937
|
// other class (see `buildGcTypeRegistry`'s `Slot::Class` handling),
|
|
@@ -986,6 +1019,8 @@ pub struct GcTypeRegistry {
|
|
|
986
1019
|
pub box_i64_idx: u32,
|
|
987
1020
|
pub box_f64_idx: u32,
|
|
988
1021
|
pub box_i32_idx: u32,
|
|
1022
|
+
/// `$BoxBool {typeId, i32}` — a `Bool` crossing an erased edge (`toStr` must say True/False).
|
|
1023
|
+
pub box_bool_idx: u32,
|
|
989
1024
|
/// The `$Obj {typeId: i32}` supertype of every class/enum/tag/box struct.
|
|
990
1025
|
pub obj_type_idx: u32,
|
|
991
1026
|
/// Enum name and variant name -> the runtime `typeId` stored in field 0 of
|
|
@@ -996,6 +1031,7 @@ pub struct GcTypeRegistry {
|
|
|
996
1031
|
pub box_i64_fn: u32,
|
|
997
1032
|
pub box_f64_fn: u32,
|
|
998
1033
|
pub box_i32_fn: u32,
|
|
1034
|
+
pub box_bool_fn: u32,
|
|
999
1035
|
/// Helper `(arr: array<i8>, len: i64) -> Str` building `Str{Buffer{arr,len}}`
|
|
1000
1036
|
/// with correct headers; `None` when the program has no `Str`/`Buffer`.
|
|
1001
1037
|
pub mk_str_fn: Option<u32>,
|
|
@@ -1008,6 +1044,8 @@ pub const TYPE_ID_BOX_I64: i32 = 1;
|
|
|
1008
1044
|
pub const TYPE_ID_BOX_F64: i32 = 2;
|
|
1009
1045
|
pub const TYPE_ID_BOX_I32: i32 = 3;
|
|
1010
1046
|
pub const TYPE_ID_TAG: i32 = 4;
|
|
1047
|
+
/// Box for a `Bool` (i32 payload); ids 5/6 are `Str`/`Buffer`.
|
|
1048
|
+
pub const TYPE_ID_BOX_BOOL: i32 = 7;
|
|
1011
1049
|
/// `Str` and `Buffer` get fixed ids so hosts can build them without a lookup.
|
|
1012
1050
|
pub const TYPE_ID_STR: i32 = 5;
|
|
1013
1051
|
pub const TYPE_ID_BUFFER: i32 = 6;
|
|
@@ -1047,16 +1085,7 @@ fn plumTypeToGcValtype(t: &PlumType, registry: &GcTypeRegistry) -> ValType {
|
|
|
1047
1085
|
PlumType::TApp(n, _) => plumTypeToGcValtype(&PlumType::TNamed(n.clone()), registry),
|
|
1048
1086
|
PlumType::TInt => ValType::I64,
|
|
1049
1087
|
PlumType::TFloat => ValType::F64,
|
|
1050
|
-
PlumType::TBool =>
|
|
1088
|
+
PlumType::TBool => ValType::I32,
|
|
1051
|
-
let idx = *registry
|
|
1052
|
-
.enum_super_type_idx
|
|
1053
|
-
.get("Bool")
|
|
1054
|
-
.expect("internal codegen error: Bool must be registered in the GC type registry");
|
|
1055
|
-
ValType::Ref(RefType {
|
|
1056
|
-
nullable: true,
|
|
1057
|
-
heap_type: HeapType::Concrete(idx),
|
|
1058
|
-
})
|
|
1059
|
-
}
|
|
1060
1089
|
// See the matching comment in `plumTypeToValtype` — `Str` is an
|
|
1061
1090
|
// ordinary class's own real struct type, not the raw
|
|
1062
1091
|
// `byte_array_type_idx`, except when `Str` was never actually
|
|
@@ -1126,6 +1155,7 @@ fn buildGcTypeRegistry(
|
|
|
1126
1155
|
BoxI64,
|
|
1127
1156
|
BoxF64,
|
|
1128
1157
|
BoxI32,
|
|
1158
|
+
BoxBool,
|
|
1129
1159
|
}
|
|
1130
1160
|
|
|
1131
1161
|
// Pass 1: assign every entry a slot (and therefore a type index) up front, before
|
|
@@ -1216,6 +1246,8 @@ fn buildGcTypeRegistry(
|
|
|
1216
1246
|
slots.push(Slot::BoxF64);
|
|
1217
1247
|
let box_i32_idx = slots.len() as u32;
|
|
1218
1248
|
slots.push(Slot::BoxI32);
|
|
1249
|
+
let box_bool_idx = slots.len() as u32;
|
|
1250
|
+
slots.push(Slot::BoxBool);
|
|
1219
1251
|
|
|
1220
1252
|
// The registry is fully index-complete after pass 1 (every name has an assigned
|
|
1221
1253
|
// slot) even though no field lists exist yet — safe to hand to `plumTypeToGcValtype`
|
|
@@ -1232,11 +1264,13 @@ fn buildGcTypeRegistry(
|
|
|
1232
1264
|
box_i64_idx,
|
|
1233
1265
|
box_f64_idx,
|
|
1234
1266
|
box_i32_idx,
|
|
1267
|
+
box_bool_idx,
|
|
1235
1268
|
obj_type_idx,
|
|
1236
1269
|
type_ids,
|
|
1237
1270
|
box_i64_fn: 0,
|
|
1238
1271
|
box_f64_fn: 0,
|
|
1239
1272
|
box_i32_fn: 0,
|
|
1273
|
+
box_bool_fn: 0,
|
|
1240
1274
|
mk_str_fn: None,
|
|
1241
1275
|
dyn_fns: HashMap::new(),
|
|
1242
1276
|
};
|
|
@@ -1354,6 +1388,7 @@ fn buildGcTypeRegistry(
|
|
|
1354
1388
|
Slot::BoxI64 => boxSubType(ValType::I64, obj_type_idx),
|
|
1355
1389
|
Slot::BoxF64 => boxSubType(ValType::F64, obj_type_idx),
|
|
1356
1390
|
Slot::BoxI32 => boxSubType(ValType::I32, obj_type_idx),
|
|
1391
|
+
Slot::BoxBool => boxSubType(ValType::I32, obj_type_idx),
|
|
1357
1392
|
Slot::ArrayRef => SubType {
|
|
1358
1393
|
is_final: true,
|
|
1359
1394
|
supertype_idx: None,
|
|
@@ -1908,6 +1943,8 @@ fn compileSourceInner(
|
|
|
1908
1943
|
registerBoxHelper(&mut module, ValType::F64, TYPE_ID_BOX_F64, gc_types.box_f64_idx);
|
|
1909
1944
|
gc_types.box_i32_fn =
|
|
1910
1945
|
registerBoxHelper(&mut module, ValType::I32, TYPE_ID_BOX_I32, gc_types.box_i32_idx);
|
|
1946
|
+
gc_types.box_bool_fn =
|
|
1947
|
+
registerBoxHelper(&mut module, ValType::I32, TYPE_ID_BOX_BOOL, gc_types.box_bool_idx);
|
|
1911
1948
|
if let (Some(&buffer_idx), Some(&str_idx)) = (
|
|
1912
1949
|
gc_types.class_type_idx.get("Buffer"),
|
|
1913
1950
|
gc_types.class_type_idx.get("Str"),
|
|
@@ -2135,10 +2172,18 @@ fn compileSourceInner(
|
|
|
2135
2172
|
tbody.push(0u8); // no locals beyond the params already in the signature
|
|
2136
2173
|
for (i, vt) in sig.params.iter().enumerate() {
|
|
2137
2174
|
Instruction::LocalGet((i + 1) as u32).encode(&mut tbody); // local 0 is env, ignored
|
|
2175
|
+
match sig.decl_params.get(i) {
|
|
2176
|
+
Some(PlumType::TBool) => {
|
|
2177
|
+
coerceTy(&anyTy(), &PlumType::TBool, &mut tbody, &gc_types)
|
|
2178
|
+
}
|
|
2138
|
-
|
|
2179
|
+
_ => coerceVt(ValType::Ref(RefType::ANYREF), *vt, &mut tbody, &gc_types),
|
|
2180
|
+
}
|
|
2139
2181
|
}
|
|
2140
2182
|
Instruction::Call(real_func_idx).encode(&mut tbody);
|
|
2141
2183
|
match sig.ret {
|
|
2184
|
+
Some(_) if matches!(sig.decl_ret, PlumType::TBool) => {
|
|
2185
|
+
coerceTy(&PlumType::TBool, &anyTy(), &mut tbody, &gc_types)
|
|
2186
|
+
}
|
|
2142
2187
|
Some(rvt) => coerceVt(rvt, ValType::Ref(RefType::ANYREF), &mut tbody, &gc_types),
|
|
2143
2188
|
None => Instruction::RefNull(HeapType::ANY).encode(&mut tbody),
|
|
2144
2189
|
}
|
|
@@ -2358,6 +2403,7 @@ fn registerDynDispatchers(
|
|
|
2358
2403
|
("Int", ValType::I64, gc_types.box_i64_idx, TYPE_ID_BOX_I64),
|
|
2359
2404
|
("Float", ValType::F64, gc_types.box_f64_idx, TYPE_ID_BOX_F64),
|
|
2360
2405
|
("Byte", ValType::I32, gc_types.box_i32_idx, TYPE_ID_BOX_I32),
|
|
2406
|
+
("Bool", ValType::I32, gc_types.box_bool_idx, TYPE_ID_BOX_BOOL),
|
|
2361
2407
|
] {
|
|
2362
2408
|
let direct = format!("{}::{}", prim, m.name);
|
|
2363
2409
|
let mut b = Vec::new();
|
|
@@ -2481,6 +2527,7 @@ fn registerDynEqHelper(module: &mut WasmModule, g: &GcTypeRegistry, string_eq_fu
|
|
|
2481
2527
|
(TYPE_ID_BOX_I64, g.box_i64_idx, Instruction::I64Eq),
|
|
2482
2528
|
(TYPE_ID_BOX_F64, g.box_f64_idx, Instruction::F64Eq),
|
|
2483
2529
|
(TYPE_ID_BOX_I32, g.box_i32_idx, Instruction::I32Eq),
|
|
2530
|
+
(TYPE_ID_BOX_BOOL, g.box_bool_idx, Instruction::I32Eq),
|
|
2484
2531
|
] {
|
|
2485
2532
|
tid(&mut b, 0);
|
|
2486
2533
|
Instruction::I32Const(id).encode(&mut b);
|
|
@@ -3461,7 +3508,7 @@ fn fvCollectBoundBlock(block: &ast::Block, bound: &mut std::collections::HashSet
|
|
|
3461
3508
|
|
|
3462
3509
|
fn fvCollectPatternBindings(pat: &ast::CasePattern, bound: &mut std::collections::HashSet<String>) {
|
|
3463
3510
|
match pat {
|
|
3464
|
-
ast::CasePattern::Name(n) => {
|
|
3511
|
+
ast::CasePattern::Name(n) if n != "True" && n != "False" => {
|
|
3465
3512
|
bound.insert(n.clone());
|
|
3466
3513
|
}
|
|
3467
3514
|
ast::CasePattern::Class { fields, .. } => {
|
|
@@ -3711,7 +3758,7 @@ impl<'a> Collector<'a> {
|
|
|
3711
3758
|
match pat {
|
|
3712
3759
|
ast::CasePattern::Name(n) => {
|
|
3713
3760
|
let is_variant = n.chars().next().map(|c| c.is_uppercase()).unwrap_or(false)
|
|
3714
|
-
&& self.enum_variants.contains_key(n);
|
|
3761
|
+
&& (self.enum_variants.contains_key(n) || n == "True" || n == "False");
|
|
3715
3762
|
if !is_variant {
|
|
3716
3763
|
self.bind(n, ty.clone());
|
|
3717
3764
|
}
|
|
@@ -4169,11 +4216,8 @@ fn compileStmtInValuePosition(
|
|
|
4169
4216
|
/// (each branch is `BlockType::Empty`, nothing left on the stack) or `Some(vt)` when this
|
|
4170
4217
|
/// `if` is in value position — every branch must then leave a `vt` value on the stack, which
|
|
4171
4218
|
/// requires an `else` (a value can't be produced on a path that doesn't exist).
|
|
4172
|
-
/// Compiles a `Bool`-typed expression
|
|
4219
|
+
/// Compiles a `Bool`-typed expression (a plain wasm `i32`, 1/0) for use as a native
|
|
4173
|
-
/// concrete type, leaving a plain `i32` (1/0) on the stack. Every place a `Bool` value
|
|
4174
|
-
/// drives wasm's OWN native control flow (`if`/`br_if`, which require a raw `i32`
|
|
4175
|
-
/// condition, not a `ref`) goes through this — see this migration plan's Decision 1
|
|
4176
|
-
///
|
|
4220
|
+
/// control-flow condition; `&&`/`||` short-circuit.
|
|
4177
4221
|
fn compileBoolConditionAsI32(
|
|
4178
4222
|
expr: &ast::Expr,
|
|
4179
4223
|
body: &mut Vec<u8>,
|
|
@@ -4184,20 +4228,11 @@ fn compileBoolConditionAsI32(
|
|
|
4184
4228
|
// alone executed) unless the left side's result already needs it — guarding
|
|
4185
4229
|
// `i < len && s.byteAt(i) != ...` on the length check only works if `byteAt`
|
|
4186
4230
|
// is never reached once `i < len` is false. Handled here (not just as a
|
|
4187
|
-
// generic `Expr::Bool` case in `compileExpr` below)
|
|
4231
|
+
// generic `Expr::Bool` case in `compileExpr` below).
|
|
4188
|
-
// directly as an `if`/`while` condition never pays for constructing a real
|
|
4189
|
-
// `Bool` ref just to immediately `ref.test` it back into an `i32`.
|
|
4190
4232
|
if let ast::Expr::Bool(b) = expr {
|
|
4191
4233
|
return compileShortCircuitBoolI32(b, body, ctx, state);
|
|
4192
4234
|
}
|
|
4193
|
-
compileExpr(expr, body, ctx, state)
|
|
4235
|
+
compileExpr(expr, body, ctx, state)
|
|
4194
|
-
let true_idx = *ctx
|
|
4195
|
-
.gc_types
|
|
4196
|
-
.variant_type_idx
|
|
4197
|
-
.get("True")
|
|
4198
|
-
.expect("internal codegen error: True must be registered in the GC type registry");
|
|
4199
|
-
Instruction::RefTestNonNull(HeapType::Concrete(true_idx)).encode(body);
|
|
4200
|
-
Ok(())
|
|
4201
4236
|
}
|
|
4202
4237
|
|
|
4203
4238
|
/// Leaves a short-circuited `i32` (1/0) on the stack for `b.left op b.right`:
|
|
@@ -4227,28 +4262,6 @@ fn compileShortCircuitBoolI32(
|
|
|
4227
4262
|
Ok(())
|
|
4228
4263
|
}
|
|
4229
4264
|
|
|
4230
|
-
/// Given an `i32` boolean (1/0) already on the stack, converts it into a `Bool` ref by
|
|
4231
|
-
/// selecting the pre-allocated `True`/`False` singleton (this migration plan's
|
|
4232
|
-
/// Decision 2) — the reverse of `compileBoolConditionAsI32`. Used wherever a native
|
|
4233
|
-
/// wasm comparison/logical-op instruction just left a raw `i32` predicate on the
|
|
4234
|
-
/// stack that needs to become a proper `Bool` value.
|
|
4235
|
-
fn pushBoolRefFromI32Flag(body: &mut Vec<u8>, ctx: &LocalCtx) {
|
|
4236
|
-
let bool_ref_ty = plumTypeToValtype(&PlumType::TBool);
|
|
4237
|
-
let true_global = *ctx
|
|
4238
|
-
.singleton_globals
|
|
4239
|
-
.get("True")
|
|
4240
|
-
.expect("internal codegen error: True singleton global missing");
|
|
4241
|
-
let false_global = *ctx
|
|
4242
|
-
.singleton_globals
|
|
4243
|
-
.get("False")
|
|
4244
|
-
.expect("internal codegen error: False singleton global missing");
|
|
4245
|
-
Instruction::If(BlockType::Result(bool_ref_ty)).encode(body);
|
|
4246
|
-
Instruction::GlobalGet(true_global).encode(body);
|
|
4247
|
-
Instruction::Else.encode(body);
|
|
4248
|
-
Instruction::GlobalGet(false_global).encode(body);
|
|
4249
|
-
Instruction::End.encode(body);
|
|
4250
|
-
}
|
|
4251
|
-
|
|
4252
4265
|
fn compileIf(
|
|
4253
4266
|
if_: &ast::If,
|
|
4254
4267
|
result_vt: Option<ValType>,
|
|
@@ -4322,12 +4335,7 @@ fn compileStmt(
|
|
|
4322
4335
|
// Convert to the representation the local was declared with
|
|
4323
4336
|
// (differs only across an erased/instantiated edge).
|
|
4324
4337
|
if let Some(decl) = ctx.local_decl_types.get(name) {
|
|
4325
|
-
coerceVt(
|
|
4326
|
-
|
|
4338
|
+
coerceTy(&vty, decl, body, ctx.gc_types);
|
|
4327
|
-
plumTypeToValtype(decl),
|
|
4328
|
-
body,
|
|
4329
|
-
ctx.gc_types,
|
|
4330
|
-
);
|
|
4331
4339
|
}
|
|
4332
4340
|
Instruction::LocalSet(idx).encode(body);
|
|
4333
4341
|
ctx.type_env
|
|
@@ -4427,7 +4435,11 @@ fn compileStmt(
|
|
|
4427
4435
|
// Closure ABI: the result leaves as an `anyref`.
|
|
4428
4436
|
let rt_vt = ctx.ret_type.as_ref().map(plumTypeToValtype);
|
|
4429
4437
|
if let Some(vt) = rt_vt {
|
|
4438
|
+
if let Some(rt @ PlumType::TBool) = ctx.ret_type.as_ref() {
|
|
4439
|
+
coerceTy(rt, &anyTy(), body, ctx.gc_types);
|
|
4440
|
+
} else {
|
|
4430
|
-
|
|
4441
|
+
coerceVt(vt, ValType::Ref(RefType::ANYREF), body, ctx.gc_types);
|
|
4442
|
+
}
|
|
4431
4443
|
}
|
|
4432
4444
|
}
|
|
4433
4445
|
Instruction::Return.encode(body);
|
|
@@ -4759,6 +4771,48 @@ fn matchCoversEveryEnumVariant(
|
|
|
4759
4771
|
!tuples_seen.is_empty() && tuples_seen.len() == total_combinations
|
|
4760
4772
|
}
|
|
4761
4773
|
|
|
4774
|
+
/// True if unguarded `True`/`False` arms between them cover every combination of
|
|
4775
|
+
/// `n` `Bool` subjects (the checker has already proven exhaustiveness).
|
|
4776
|
+
fn matchCoversEveryBoolCombo(cases: &[ast::Case], n: usize) -> bool {
|
|
4777
|
+
let mut seen: std::collections::BTreeSet<Vec<bool>> = std::collections::BTreeSet::new();
|
|
4778
|
+
for case in cases {
|
|
4779
|
+
if case.guard.is_some() || case.patterns.len() != n {
|
|
4780
|
+
continue;
|
|
4781
|
+
}
|
|
4782
|
+
let tuple: Option<Vec<Vec<bool>>> = case
|
|
4783
|
+
.patterns
|
|
4784
|
+
.iter()
|
|
4785
|
+
.map(|p| match p {
|
|
4786
|
+
ast::CasePattern::Name(nm) if nm == "True" => Some(vec![true]),
|
|
4787
|
+
ast::CasePattern::Name(nm) if nm == "False" => Some(vec![false]),
|
|
4788
|
+
ast::CasePattern::Wildcard => Some(vec![true, false]),
|
|
4789
|
+
ast::CasePattern::Name(nm)
|
|
4790
|
+
if !nm.chars().next().is_some_and(|c| c.is_uppercase()) =>
|
|
4791
|
+
{
|
|
4792
|
+
Some(vec![true, false])
|
|
4793
|
+
}
|
|
4794
|
+
_ => None,
|
|
4795
|
+
})
|
|
4796
|
+
.collect();
|
|
4797
|
+
let Some(options) = tuple else { continue };
|
|
4798
|
+
let mut combos: Vec<Vec<bool>> = vec![Vec::new()];
|
|
4799
|
+
for opts in &options {
|
|
4800
|
+
combos = combos
|
|
4801
|
+
.into_iter()
|
|
4802
|
+
.flat_map(|c| {
|
|
4803
|
+
opts.iter().map(move |b| {
|
|
4804
|
+
let mut c = c.clone();
|
|
4805
|
+
c.push(*b);
|
|
4806
|
+
c
|
|
4807
|
+
})
|
|
4808
|
+
})
|
|
4809
|
+
.collect();
|
|
4810
|
+
}
|
|
4811
|
+
seen.extend(combos);
|
|
4812
|
+
}
|
|
4813
|
+
n < 8 && seen.len() == 1usize << n
|
|
4814
|
+
}
|
|
4815
|
+
|
|
4762
4816
|
/// An uppercase `Name`/`Class` pattern — against a tag-union subject, a tag arm.
|
|
4763
4817
|
fn isTagPattern(pat: &ast::CasePattern) -> bool {
|
|
4764
4818
|
let upper = |n: &str| n.chars().next().is_some_and(|c| c.is_uppercase());
|
|
@@ -4885,8 +4939,14 @@ fn compileMatch(
|
|
|
4885
4939
|
.match_subject_types
|
|
4886
4940
|
.borrow()
|
|
4887
4941
|
.contains_key(&all_subjects[0].1);
|
|
4942
|
+
let bool_match = !m.subjects.is_empty()
|
|
4943
|
+
&& m.subjects
|
|
4944
|
+
.iter()
|
|
4945
|
+
.all(|e| matches!(inferLocalType(e, ctx), PlumType::TBool));
|
|
4888
4946
|
let exhaustive_fallback = result_vt.is_some()
|
|
4947
|
+
&& (tag_union_match
|
|
4948
|
+
|| (bool_match && matchCoversEveryBoolCombo(&m.cases, m.subjects.len()))
|
|
4889
|
-
|
|
4949
|
+
|| matchCoversEveryEnumVariant(&m.cases, &subject_vts, ctx));
|
|
4890
4950
|
|
|
4891
4951
|
compileMatchArmsMulti(
|
|
4892
4952
|
&m.cases,
|
|
@@ -5049,6 +5109,37 @@ fn compileCasePositions(
|
|
|
5049
5109
|
state,
|
|
5050
5110
|
)
|
|
5051
5111
|
}
|
|
5112
|
+
// `True`/`False` against a `Bool` (i32) subject: a plain i32 compare.
|
|
5113
|
+
ast::CasePattern::Name(n) if n == "True" || n == "False" => {
|
|
5114
|
+
Instruction::LocalGet(scratch_local).encode(body);
|
|
5115
|
+
if n == "False" {
|
|
5116
|
+
Instruction::I32Eqz.encode(body);
|
|
5117
|
+
}
|
|
5118
|
+
Instruction::If(blockTypeFor(result_vt)).encode(body);
|
|
5119
|
+
compileCasePositions(
|
|
5120
|
+
case,
|
|
5121
|
+
pos + 1,
|
|
5122
|
+
all_subjects,
|
|
5123
|
+
rest,
|
|
5124
|
+
result_vt,
|
|
5125
|
+
exhaustive_fallback,
|
|
5126
|
+
body,
|
|
5127
|
+
ctx,
|
|
5128
|
+
state,
|
|
5129
|
+
)?;
|
|
5130
|
+
Instruction::Else.encode(body);
|
|
5131
|
+
compileMatchArmsMulti(
|
|
5132
|
+
rest,
|
|
5133
|
+
all_subjects,
|
|
5134
|
+
result_vt,
|
|
5135
|
+
exhaustive_fallback,
|
|
5136
|
+
body,
|
|
5137
|
+
ctx,
|
|
5138
|
+
state,
|
|
5139
|
+
)?;
|
|
5140
|
+
Instruction::End.encode(body);
|
|
5141
|
+
Ok(())
|
|
5142
|
+
}
|
|
5052
5143
|
ast::CasePattern::Name(n) => {
|
|
5053
5144
|
let is_variant = n.chars().next().map(|c| c.is_uppercase()).unwrap_or(false)
|
|
5054
5145
|
&& ctx.enum_variants.contains_key(n);
|
|
@@ -5371,7 +5462,7 @@ fn compileFieldPatterns(
|
|
|
5371
5462
|
field_index: fld(fpos),
|
|
5372
5463
|
}
|
|
5373
5464
|
.encode(b);
|
|
5374
|
-
|
|
5465
|
+
coerceTy(decl_ty, field_ty, b, ctx.gc_types);
|
|
5375
5466
|
},
|
|
5376
5467
|
result_vt,
|
|
5377
5468
|
body,
|
|
@@ -5427,6 +5518,48 @@ fn compileFieldPatterns(
|
|
|
5427
5518
|
state,
|
|
5428
5519
|
on_match,
|
|
5429
5520
|
),
|
|
5521
|
+
// A nested `True`/`False` against a `Bool` field: i32 compare.
|
|
5522
|
+
ast::CasePattern::Name(n) if n == "True" || n == "False" => {
|
|
5523
|
+
Instruction::LocalGet(container_local).encode(body);
|
|
5524
|
+
Instruction::StructGet {
|
|
5525
|
+
struct_type_index: container_type_idx,
|
|
5526
|
+
field_index: fld(fpos),
|
|
5527
|
+
}
|
|
5528
|
+
.encode(body);
|
|
5529
|
+
coerceTy(decl_ty, &PlumType::TBool, body, ctx.gc_types);
|
|
5530
|
+
if n == "False" {
|
|
5531
|
+
Instruction::I32Eqz.encode(body);
|
|
5532
|
+
}
|
|
5533
|
+
Instruction::If(blockTypeFor(result_vt)).encode(body);
|
|
5534
|
+
compileFieldPatterns(
|
|
5535
|
+
fields,
|
|
5536
|
+
field_types,
|
|
5537
|
+
inst_types,
|
|
5538
|
+
fpos + 1,
|
|
5539
|
+
container_local,
|
|
5540
|
+
container_type_idx,
|
|
5541
|
+
rest,
|
|
5542
|
+
all_subjects,
|
|
5543
|
+
result_vt,
|
|
5544
|
+
exhaustive_fallback,
|
|
5545
|
+
body,
|
|
5546
|
+
ctx,
|
|
5547
|
+
state,
|
|
5548
|
+
on_match,
|
|
5549
|
+
)?;
|
|
5550
|
+
Instruction::Else.encode(body);
|
|
5551
|
+
compileMatchArmsMulti(
|
|
5552
|
+
rest,
|
|
5553
|
+
all_subjects,
|
|
5554
|
+
result_vt,
|
|
5555
|
+
exhaustive_fallback,
|
|
5556
|
+
body,
|
|
5557
|
+
ctx,
|
|
5558
|
+
state,
|
|
5559
|
+
)?;
|
|
5560
|
+
Instruction::End.encode(body);
|
|
5561
|
+
Ok(())
|
|
5562
|
+
}
|
|
5430
5563
|
ast::CasePattern::Name(n)
|
|
5431
5564
|
if !(n.chars().next().map(|c| c.is_uppercase()).unwrap_or(false)
|
|
5432
5565
|
&& ctx.enum_variants.contains_key(n)) =>
|
|
@@ -5443,7 +5576,7 @@ fn compileFieldPatterns(
|
|
|
5443
5576
|
field_index: fld(fpos),
|
|
5444
5577
|
}
|
|
5445
5578
|
.encode(body);
|
|
5446
|
-
|
|
5579
|
+
coerceTy(decl_ty, field_ty, body, ctx.gc_types);
|
|
5447
5580
|
Instruction::LocalSet(idx).encode(body);
|
|
5448
5581
|
ctx.type_env
|
|
5449
5582
|
.borrow_mut()
|
|
@@ -5527,7 +5660,7 @@ fn compileFieldPatterns(
|
|
|
5527
5660
|
field_index: fld(fpos),
|
|
5528
5661
|
}
|
|
5529
5662
|
.encode(body);
|
|
5530
|
-
|
|
5663
|
+
coerceTy(decl_ty, field_ty, body, ctx.gc_types);
|
|
5531
5664
|
Instruction::I64Const(*n).encode(body);
|
|
5532
5665
|
Instruction::I64Eq.encode(body);
|
|
5533
5666
|
Instruction::If(blockTypeFor(result_vt)).encode(body);
|
|
@@ -5698,8 +5831,7 @@ fn compileExpr(
|
|
|
5698
5831
|
// type may differ across an erased edge (a generic `T` slot read
|
|
5699
5832
|
// at its instantiated type).
|
|
5700
5833
|
if let Some(decl) = ctx.local_decl_types.get(name.as_str()) {
|
|
5701
|
-
let want = plumTypeToValtype(&inferLocalType(expr, ctx));
|
|
5702
|
-
|
|
5834
|
+
coerceTy(decl, &inferLocalType(expr, ctx), body, ctx.gc_types);
|
|
5703
5835
|
}
|
|
5704
5836
|
}
|
|
5705
5837
|
// Not a local: a bare reference to a top-level function used as a
|
|
@@ -5772,14 +5904,13 @@ fn compileExpr(
|
|
|
5772
5904
|
})?;
|
|
5773
5905
|
// The payload slot is declared at its generic type (`T` -> `$Obj`); the
|
|
5774
5906
|
// value this expression yields is at its instantiated type.
|
|
5775
|
-
let
|
|
5907
|
+
let raw_success_ty = success_info
|
|
5776
|
-
&success_info
|
|
5777
|
-
|
|
5908
|
+
.field_types
|
|
5778
|
-
|
|
5909
|
+
.first()
|
|
5779
|
-
|
|
5910
|
+
.cloned()
|
|
5780
|
-
|
|
5911
|
+
.unwrap_or(PlumType::TUnit);
|
|
5781
|
-
);
|
|
5912
|
+
let success_ty = inferLocalType(expr, ctx);
|
|
5782
|
-
let success_vt = plumTypeToValtype(&
|
|
5913
|
+
let success_vt = plumTypeToValtype(&success_ty);
|
|
5783
5914
|
|
|
5784
5915
|
let key = expr as *const ast::Expr as usize;
|
|
5785
5916
|
let slot = *ctx
|
|
@@ -5840,7 +5971,7 @@ fn compileExpr(
|
|
|
5840
5971
|
field_index: fld(0),
|
|
5841
5972
|
}
|
|
5842
5973
|
.encode(body);
|
|
5843
|
-
|
|
5974
|
+
coerceTy(&raw_success_ty, &success_ty, body, ctx.gc_types);
|
|
5844
5975
|
}
|
|
5845
5976
|
Instruction::End.encode(body);
|
|
5846
5977
|
}
|
|
@@ -5893,14 +6024,13 @@ fn compileExpr(
|
|
|
5893
6024
|
failure_name
|
|
5894
6025
|
)
|
|
5895
6026
|
})?;
|
|
5896
|
-
let
|
|
6027
|
+
let raw_success_ty = success_info
|
|
5897
|
-
&success_info
|
|
5898
|
-
|
|
6028
|
+
.field_types
|
|
5899
|
-
|
|
6029
|
+
.first()
|
|
5900
|
-
|
|
6030
|
+
.cloned()
|
|
5901
|
-
|
|
6031
|
+
.unwrap_or(PlumType::TUnit);
|
|
5902
|
-
);
|
|
6032
|
+
let success_ty = inferLocalType(expr, ctx);
|
|
5903
|
-
let success_vt = plumTypeToValtype(&
|
|
6033
|
+
let success_vt = plumTypeToValtype(&success_ty);
|
|
5904
6034
|
|
|
5905
6035
|
let key = expr as *const ast::Expr as usize;
|
|
5906
6036
|
let slot = *ctx
|
|
@@ -5923,7 +6053,7 @@ fn compileExpr(
|
|
|
5923
6053
|
field_index: fld(0),
|
|
5924
6054
|
}
|
|
5925
6055
|
.encode(body);
|
|
5926
|
-
|
|
6056
|
+
coerceTy(&raw_success_ty, &success_ty, body, ctx.gc_types);
|
|
5927
6057
|
}
|
|
5928
6058
|
Instruction::End.encode(body);
|
|
5929
6059
|
}
|
|
@@ -5989,12 +6119,10 @@ fn compileExpr(
|
|
|
5989
6119
|
}
|
|
5990
6120
|
ast::Expr::Bool(b) => {
|
|
5991
6121
|
compileShortCircuitBoolI32(b, body, ctx, state)?;
|
|
5992
|
-
pushBoolRefFromI32Flag(body, ctx);
|
|
5993
6122
|
}
|
|
5994
6123
|
ast::Expr::Not(inner) => {
|
|
5995
6124
|
compileBoolConditionAsI32(inner, body, ctx, state)?;
|
|
5996
6125
|
Instruction::I32Eqz.encode(body);
|
|
5997
|
-
pushBoolRefFromI32Flag(body, ctx);
|
|
5998
6126
|
}
|
|
5999
6127
|
ast::Expr::Compare(c) => {
|
|
6000
6128
|
let left_ty = inferLocalType(&c.left, ctx);
|
|
@@ -6019,7 +6147,6 @@ fn compileExpr(
|
|
|
6019
6147
|
))
|
|
6020
6148
|
}
|
|
6021
6149
|
}
|
|
6022
|
-
pushBoolRefFromI32Flag(body, ctx);
|
|
6023
6150
|
return Ok(());
|
|
6024
6151
|
}
|
|
6025
6152
|
compileExpr(&c.left, body, ctx, state)?;
|
|
@@ -6085,7 +6212,17 @@ fn compileExpr(
|
|
|
6085
6212
|
))
|
|
6086
6213
|
}
|
|
6087
6214
|
},
|
|
6215
|
+
PlumType::TBool => match &c.op {
|
|
6216
|
+
ast::CmpOp::Eq => Instruction::I32Eq.encode(body),
|
|
6217
|
+
ast::CmpOp::Neq | ast::CmpOp::NotEq2 => Instruction::I32Ne.encode(body),
|
|
6218
|
+
other => {
|
|
6219
|
+
return Err(format!(
|
|
6220
|
+
"codegen: '{:?}' is not supported between Bool values",
|
|
6221
|
+
other
|
|
6222
|
+
))
|
|
6223
|
+
}
|
|
6224
|
+
},
|
|
6088
|
-
//
|
|
6225
|
+
// Class/enum values are wasm-gc refs — `==`/`!=` compares
|
|
6089
6226
|
// reference identity via `ref.eq`. That's exactly right for a
|
|
6090
6227
|
// payload-free singleton (`End`/`True`/`False`, this migration
|
|
6091
6228
|
// plan's Decision 2) and for class-instance identity.
|
|
@@ -6104,7 +6241,6 @@ fn compileExpr(
|
|
|
6104
6241
|
}
|
|
6105
6242
|
},
|
|
6106
6243
|
}
|
|
6107
|
-
pushBoolRefFromI32Flag(body, ctx);
|
|
6108
6244
|
}
|
|
6109
6245
|
ast::Expr::Ternary(t) => {
|
|
6110
6246
|
let result_vt = plumTypeToValtype(&inferLocalType(expr, ctx));
|
|
@@ -6243,6 +6379,9 @@ fn compileExpr(
|
|
|
6243
6379
|
.ok_or_else(|| "codegen: 'self' used outside a method".to_string())?;
|
|
6244
6380
|
Instruction::LocalGet(idx).encode(body);
|
|
6245
6381
|
}
|
|
6382
|
+
ast::Expr::TypeName(n) if n == "True" || n == "False" => {
|
|
6383
|
+
Instruction::I32Const((n == "True") as i32).encode(body);
|
|
6384
|
+
}
|
|
6246
6385
|
ast::Expr::TypeName(n) => {
|
|
6247
6386
|
match ctx.enum_variants.get(n) {
|
|
6248
6387
|
// A bare uppercase name the checker typed as a tag union is a tag
|
|
@@ -6445,12 +6584,7 @@ fn coerceCallResultTo(decl_ret: &PlumType, want: &PlumType, body: &mut Vec<u8>,
|
|
|
6445
6584
|
if matches!(decl_ret, PlumType::TUnit) || matches!(want, PlumType::TUnit | PlumType::TVar(_)) {
|
|
6446
6585
|
return;
|
|
6447
6586
|
}
|
|
6448
|
-
coerceVt(
|
|
6449
|
-
|
|
6587
|
+
coerceTy(decl_ret, want, body, ctx.gc_types);
|
|
6450
|
-
plumTypeToValtype(want),
|
|
6451
|
-
body,
|
|
6452
|
-
ctx.gc_types,
|
|
6453
|
-
);
|
|
6454
6588
|
}
|
|
6455
6589
|
|
|
6456
6590
|
/// Pushes the arguments of a call to the function described by `sig`: each fixed
|
|
@@ -6526,7 +6660,7 @@ fn compileFieldRead(
|
|
|
6526
6660
|
))
|
|
6527
6661
|
}
|
|
6528
6662
|
};
|
|
6529
|
-
let want =
|
|
6663
|
+
let want = &inferLocalType(expr, ctx);
|
|
6530
6664
|
// A discriminant enum's shared params are declared directly on the enum's
|
|
6531
6665
|
// SUPERTYPE (see `buildGcTypeRegistry`'s `EnumSuper` arm) — no `ref.cast` to
|
|
6532
6666
|
// any one variant needed.
|
|
@@ -6548,12 +6682,7 @@ fn compileFieldRead(
|
|
|
6548
6682
|
field_index: fld(field_idx),
|
|
6549
6683
|
}
|
|
6550
6684
|
.encode(body);
|
|
6551
|
-
coerceVt(
|
|
6552
|
-
|
|
6685
|
+
coerceTy(¶ms[field_idx].1, want, body, ctx.gc_types);
|
|
6553
|
-
want,
|
|
6554
|
-
body,
|
|
6555
|
-
ctx.gc_types,
|
|
6556
|
-
);
|
|
6557
6686
|
return Ok(());
|
|
6558
6687
|
}
|
|
6559
6688
|
// Not a discriminant enum: `class_name` is an ordinary enum, and the checker
|
|
@@ -6590,12 +6719,7 @@ fn compileFieldRead(
|
|
|
6590
6719
|
field_index: fld(field_idx),
|
|
6591
6720
|
}
|
|
6592
6721
|
.encode(body);
|
|
6593
|
-
coerceVt(
|
|
6594
|
-
|
|
6722
|
+
coerceTy(&info.field_types[field_idx], want, body, ctx.gc_types);
|
|
6595
|
-
want,
|
|
6596
|
-
body,
|
|
6597
|
-
ctx.gc_types,
|
|
6598
|
-
);
|
|
6599
6723
|
Ok(())
|
|
6600
6724
|
}
|
|
6601
6725
|
|
|
@@ -7025,12 +7149,8 @@ fn compileClosureLiteral(
|
|
|
7025
7149
|
)
|
|
7026
7150
|
})?;
|
|
7027
7151
|
Instruction::LocalGet(local_idx).encode(body);
|
|
7028
|
-
let have = ctx
|
|
7029
|
-
.local_decl_types
|
|
7030
|
-
.get(name)
|
|
7031
|
-
.map(plumTypeToValtype)
|
|
7032
|
-
|
|
7152
|
+
let have = ctx.local_decl_types.get(name).unwrap_or(cap_ty);
|
|
7033
|
-
|
|
7153
|
+
coerceTy(have, cap_ty, body, ctx.gc_types);
|
|
7034
7154
|
}
|
|
7035
7155
|
Instruction::StructNew(info.env_type_idx).encode(body);
|
|
7036
7156
|
Instruction::StructNew(ctx.gc_types.closure_type_idx).encode(body);
|
|
@@ -7097,8 +7217,7 @@ fn compileClosureCall(
|
|
|
7097
7217
|
if closureCallReturnsUnit(&call.name, ctx) {
|
|
7098
7218
|
Instruction::Drop.encode(body);
|
|
7099
7219
|
} else {
|
|
7100
|
-
|
|
7220
|
+
coerceTy(&anyTy(), &inferLocalType(call_expr, ctx), body, ctx.gc_types);
|
|
7101
|
-
coerceVt(any, want, body, ctx.gc_types);
|
|
7102
7221
|
}
|
|
7103
7222
|
Ok(())
|
|
7104
7223
|
}
|
|
@@ -7163,12 +7282,12 @@ fn compileClosureBody(
|
|
|
7163
7282
|
let mut idx = 0u32;
|
|
7164
7283
|
|
|
7165
7284
|
idx += 1 + cl.params.len() as u32; // env + raw params (all in the signature)
|
|
7166
|
-
let mut param_prologue: Vec<(u32, u32,
|
|
7285
|
+
let mut param_prologue: Vec<(u32, u32, PlumType)> = Vec::new(); // (raw, typed, ptype)
|
|
7167
7286
|
for (i, (name, pty)) in cl.params.iter().zip(info.param_ptypes.iter()).enumerate() {
|
|
7168
7287
|
let vt = plumTypeToValtype(pty);
|
|
7169
7288
|
locals.insert(name.clone(), idx);
|
|
7170
7289
|
groups.push(vt);
|
|
7171
|
-
param_prologue.push((i as u32 + 1, idx,
|
|
7290
|
+
param_prologue.push((i as u32 + 1, idx, pty.clone()));
|
|
7172
7291
|
idx += 1;
|
|
7173
7292
|
}
|
|
7174
7293
|
for (name, ty) in &info.free_vars {
|
|
@@ -7228,9 +7347,9 @@ fn compileClosureBody(
|
|
|
7228
7347
|
}
|
|
7229
7348
|
|
|
7230
7349
|
// Prologue: convert each raw `anyref` param to its real representation.
|
|
7231
|
-
for (raw, typed,
|
|
7350
|
+
for (raw, typed, pty) in ¶m_prologue {
|
|
7232
7351
|
Instruction::LocalGet(*raw).encode(&mut body);
|
|
7233
|
-
|
|
7352
|
+
coerceTy(&anyTy(), pty, &mut body, &ctx.gc_types);
|
|
7234
7353
|
Instruction::LocalSet(*typed).encode(&mut body);
|
|
7235
7354
|
}
|
|
7236
7355
|
// Restore each captured free variable from the env struct (local 0, statically
|
|
@@ -7280,14 +7399,13 @@ fn compileClosureBody(
|
|
|
7280
7399
|
subject_ptypes: RefCell::new(HashMap::new()),
|
|
7281
7400
|
};
|
|
7282
7401
|
|
|
7283
|
-
let any = ValType::Ref(RefType::ANYREF);
|
|
7284
7402
|
if matches!(info.ret_ptype, PlumType::TUnit) {
|
|
7285
7403
|
compileBlockAsFnBody(&cl.body, &mut body, &local_ctx, state, None)?;
|
|
7286
7404
|
Instruction::RefNull(HeapType::ANY).encode(&mut body);
|
|
7287
7405
|
} else {
|
|
7288
7406
|
let ret_vt = plumTypeToValtype(&info.ret_ptype);
|
|
7289
7407
|
compileBlockAsFnBody(&cl.body, &mut body, &local_ctx, state, Some(ret_vt))?;
|
|
7290
|
-
|
|
7408
|
+
coerceTy(&info.ret_ptype, &anyTy(), &mut body, &ctx.gc_types);
|
|
7291
7409
|
}
|
|
7292
7410
|
|
|
7293
7411
|
Instruction::End.encode(&mut body);
|
plum-wasm-codegen/tests/codegen_tests.rs
CHANGED
|
@@ -1772,3 +1772,100 @@ fun main() -> Int =
|
|
|
1772
1772
|
assert_eq!(maps.len(), 1, "got {:?}", keys);
|
|
1773
1773
|
assert!(!keys.iter().any(|k| k.contains('$')), "mangled names remain");
|
|
1774
1774
|
}
|
|
1775
|
+
|
|
1776
|
+
// ---- built-in Bool (i32; no `import std/Bool`) ----
|
|
1777
|
+
|
|
1778
|
+
#[test]
|
|
1779
|
+
fn builtin_bool_without_import() {
|
|
1780
|
+
let src = "\
|
|
1781
|
+
import std/Str
|
|
1782
|
+
import std/Number
|
|
1783
|
+
|
|
1784
|
+
fun flip(b: Bool) -> Bool =
|
|
1785
|
+
!b
|
|
1786
|
+
|
|
1787
|
+
fun name(b: Bool) -> Str =
|
|
1788
|
+
match b
|
|
1789
|
+
True => \"yes\"
|
|
1790
|
+
False => \"no\"
|
|
1791
|
+
|
|
1792
|
+
fun main() -> Int =
|
|
1793
|
+
x := True
|
|
1794
|
+
if flip(False) && x == True && !False || False
|
|
1795
|
+
if name(x) == \"yes\" && name(False) == \"no\" && \"{x}\" == \"True\"
|
|
1796
|
+
return 1
|
|
1797
|
+
return 0
|
|
1798
|
+
";
|
|
1799
|
+
assert_eq!(runMerged::<i64>("bool_noimport", src), 1);
|
|
1800
|
+
}
|
|
1801
|
+
|
|
1802
|
+
#[test]
|
|
1803
|
+
fn builtin_bool_list_round_trip() {
|
|
1804
|
+
let src = "\
|
|
1805
|
+
import std/Str
|
|
1806
|
+
import std/Number
|
|
1807
|
+
import std/List
|
|
1808
|
+
import std/Try
|
|
1809
|
+
|
|
1810
|
+
fun main() -> Int =
|
|
1811
|
+
l := List[Bool]()
|
|
1812
|
+
l.push(True)
|
|
1813
|
+
l.push(False)
|
|
1814
|
+
l.push(True)
|
|
1815
|
+
a := l.get(0) ?? False
|
|
1816
|
+
b := l.get(1) ?? True
|
|
1817
|
+
c := l.get(2) ?? False
|
|
1818
|
+
if a && !b && c && l.length() == 3
|
|
1819
|
+
return 1
|
|
1820
|
+
return 0
|
|
1821
|
+
";
|
|
1822
|
+
assert_eq!(runMerged::<i64>("bool_list", src), 1);
|
|
1823
|
+
}
|
|
1824
|
+
|
|
1825
|
+
#[test]
|
|
1826
|
+
fn boxed_bool_to_str_and_eq() {
|
|
1827
|
+
let src = "\
|
|
1828
|
+
import std/Str
|
|
1829
|
+
import std/Number
|
|
1830
|
+
|
|
1831
|
+
fun show(x: ToStr) -> Str =
|
|
1832
|
+
x.toStr()
|
|
1833
|
+
|
|
1834
|
+
fun same(a: T, b: T) -> Bool =
|
|
1835
|
+
a == b
|
|
1836
|
+
|
|
1837
|
+
fun main() -> Int =
|
|
1838
|
+
if show(True) == \"True\" && show(False) == \"False\" && same(True, True) && !same(True, False)
|
|
1839
|
+
return 1
|
|
1840
|
+
return 0
|
|
1841
|
+
";
|
|
1842
|
+
assert_eq!(runMerged::<i64>("bool_boxed", src), 1);
|
|
1843
|
+
}
|
|
1844
|
+
|
|
1845
|
+
#[test]
|
|
1846
|
+
fn builtin_bool_in_try_with_question_and_elvis() {
|
|
1847
|
+
let src = "\
|
|
1848
|
+
import std/Str
|
|
1849
|
+
import std/Number
|
|
1850
|
+
import std/Try
|
|
1851
|
+
|
|
1852
|
+
fun yes(ok: Bool) -> Try[Bool, [Nope]] =
|
|
1853
|
+
if ok
|
|
1854
|
+
Ok(True)
|
|
1855
|
+
else
|
|
1856
|
+
Err(Nope)
|
|
1857
|
+
|
|
1858
|
+
fun chain(ok: Bool) -> Try[Bool, [Nope]] =
|
|
1859
|
+
v := yes(ok)?
|
|
1860
|
+
Ok(!v)
|
|
1861
|
+
|
|
1862
|
+
fun main() -> Int =
|
|
1863
|
+
a := chain(True) ?? True
|
|
1864
|
+
b := chain(False) ?? True
|
|
1865
|
+
p := Bool.parse(\"true\") ?? False
|
|
1866
|
+
if !a && b && p
|
|
1867
|
+
return 1
|
|
1868
|
+
return 0
|
|
1869
|
+
";
|
|
1870
|
+
assert_eq!(runMerged::<i64>("bool_try", src), 1);
|
|
1871
|
+
}
|