plum

#treesitter#compiler#wasm

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

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


plum-wasm-codegen/tests/codegen_tests.rs
3d6f280 1
#![allow(non_snake_case)]
3d6f280 2
use plum_wasm_codegen::compileSource;
bb8ca38 3
use plum_core::AstParser;
db00bd9 4
use wasm_encoder::Encode;
bb8ca38 5
bb8ca38 6
fn parse(src: &str) -> plum_core::ast::Source {
bb8ca38 7
    let mut parser = tree_sitter::Parser::new();
bb8ca38 8
    parser.set_language(&tree_sitter_plum::LANGUAGE.into()).unwrap();
bb8ca38 9
    let tree = parser.parse(src, None).unwrap();
bb8ca38 10
    let ap = AstParser::new(src);
3d6f280 11
    ap.parseSource(tree.root_node())
bb8ca38 12
}
bb8ca38 13
5ef4579 14
/// The wasm-gc migration (docs/superpowers/plans/2026-07-25-wasm-gc-migration.md)
5ef4579 15
/// needs both `wasm_gc` and `wasm_function_references` enabled — confirmed
5ef4579 16
/// empirically (see the `wasmtimeGcConfig*` tests below) rather than assumed from
5ef4579 17
/// docs, since wasmtime's own doc comment on `Config::wasm_gc` warns its GC support
5ef4579 18
/// is still in progress. Every test that instantiates/runs compiled output uses
5ef4579 19
/// this shared engine so the whole harness stays on one config.
5ef4579 20
fn gcEngine() -> wasmtime::Engine {
5ef4579 21
    let mut config = wasmtime::Config::new();
5ef4579 22
    config.wasm_gc(true);
5ef4579 23
    config.wasm_function_references(true);
5ef4579 24
    wasmtime::Engine::new(&config).expect("engine with GC config should construct")
5ef4579 25
}
5ef4579 26
bb8ca38 27
#[test]
3d6f280 28
fn compilesToValidWasm() {
b48d3a3 29
    let src = "fun add(a: Int, b: Int) -> Int =\n  a + b\n";
bb8ca38 30
    let source = parse(src);
3d6f280 31
    let bytes = compileSource(&source).expect("compile failed");
bb8ca38 32
    // Valid WASM starts with the magic number
bb8ca38 33
    assert_eq!(&bytes[0..4], b"\0asm");
bb8ca38 34
    assert_eq!(&bytes[4..8], &[1, 0, 0, 0]); // version 1
bb8ca38 35
}
bb8ca38 36
bb8ca38 37
#[test]
3d6f280 38
fn outputValidates() {
b48d3a3 39
    let src = "fun add(a: Int, b: Int) -> Int =\n  a + b\n";
bb8ca38 40
    let source = parse(src);
3d6f280 41
    let bytes = compileSource(&source).expect("compile failed");
bb8ca38 42
    // wasmparser should accept the output
bb8ca38 43
    let result = wasmparser::validate(&bytes);
bb8ca38 44
    assert!(result.is_ok(), "wasm validation failed: {:?}", result.err());
bb8ca38 45
}
e71210a 46
e71210a 47
#[test]
3d6f280 48
fn factorialCompiles() {
5d8ada1 49
    let src = "\
b48d3a3 50
fun factorial(x: Int) -> Int =
5d8ada1 51
  if x < 2
5d8ada1 52
    return 1
5d8ada1 53
  return x * factorial(x - 1)
5d8ada1 54
";
e71210a 55
    let source = parse(src);
3d6f280 56
    let bytes = compileSource(&source).expect("factorial should compile");
1e3672d 57
    assert_eq!(&bytes[0..4], b"\0asm");
1e3672d 58
    let result = wasmparser::validate(&bytes);
1e3672d 59
    assert!(result.is_ok(), "wasm validation failed: {:?}", result.err());
e71210a 60
}
e71210a 61
e71210a 62
#[test]
3d6f280 63
fn give42CompilesAndExports() {
b48d3a3 64
    let src = "fun give42() -> Int =\n  42\n";
e71210a 65
    let source = parse(src);
3d6f280 66
    let bytes = compileSource(&source).expect("compile failed");
e71210a 67
    assert_eq!(&bytes[0..4], b"\0asm");
e71210a 68
    let result = wasmparser::validate(&bytes);
e71210a 69
    assert!(result.is_ok(), "wasm validation failed: {:?}", result.err());
e71210a 70
}
5d8ada1 71
3d6f280 72
fn assertValid(src: &str) -> Vec<u8> {
5d8ada1 73
    let source = parse(src);
3d6f280 74
    let bytes = compileSource(&source).unwrap_or_else(|e| panic!("compile failed for {:?}: {}", src, e));
5d8ada1 75
    let result = wasmparser::validate(&bytes);
5d8ada1 76
    assert!(result.is_ok(), "wasm validation failed: {:?}", result.err());
5d8ada1 77
    bytes
5d8ada1 78
}
5d8ada1 79
5d8ada1 80
#[test]
3d6f280 81
fn boolLiteralsCompile() {
3d6f280 82
    assertValid("fun main() -> Bool =\n  True\n");
3d6f280 83
    assertValid("fun main() -> Bool =\n  False\n");
5d8ada1 84
}
5d8ada1 85
5d8ada1 86
#[test]
3d6f280 87
fn stringLiteralCompiles() {
3d6f280 88
    assertValid("fun main() -> Str =\n  \"hello\"\n");
5d8ada1 89
}
5d8ada1 90
5d8ada1 91
#[test]
3d6f280 92
fn emptyStringLiteralCompiles() {
3d6f280 93
    assertValid("fun main() -> Str =\n  \"\"\n");
5d8ada1 94
}
5d8ada1 95
5d8ada1 96
#[test]
3d6f280 97
fn stringInterpolationOfAnIntRunsCorrectly() {
b48d3a3 98
    let src = "fun main() -> Str =\n  x = 42\n  \"{x}\"\n";
5d8ada1 99
    let source = parse(src);
3d6f280 100
    let bytes = compileSource(&source).expect("compile failed");
3d6f280 101
    assert_eq!(runMainStr(&bytes), "42");
35af6cf 102
}
35af6cf 103
35af6cf 104
#[test]
3d6f280 105
fn stringInterpolationOfANegativeIntRunsCorrectly() {
b48d3a3 106
    let src = "fun main() -> Str =\n  x = -7\n  \"{x}\"\n";
35af6cf 107
    let source = parse(src);
3d6f280 108
    let bytes = compileSource(&source).expect("compile failed");
3d6f280 109
    assert_eq!(runMainStr(&bytes), "-7");
35af6cf 110
}
35af6cf 111
35af6cf 112
#[test]
3d6f280 113
fn stringInterpolationOfZeroRunsCorrectly() {
b48d3a3 114
    let src = "fun main() -> Str =\n  x = 0\n  \"{x}\"\n";
35af6cf 115
    let source = parse(src);
3d6f280 116
    let bytes = compileSource(&source).expect("compile failed");
3d6f280 117
    assert_eq!(runMainStr(&bytes), "0");
35af6cf 118
}
35af6cf 119
35af6cf 120
#[test]
3d6f280 121
fn stringInterpolationWithSurroundingTextAndMultipleInterpsRunsCorrectly() {
b48d3a3 122
    let src = "fun main() -> Str =\n  count = 3\n  total = 10\n  \"{count} of {total} complete\"\n";
35af6cf 123
    let source = parse(src);
3d6f280 124
    let bytes = compileSource(&source).expect("compile failed");
3d6f280 125
    assert_eq!(runMainStr(&bytes), "3 of 10 complete");
35af6cf 126
}
35af6cf 127
35af6cf 128
#[test]
3d6f280 129
fn stringInterpolationOfAStrRunsCorrectly() {
b48d3a3 130
    let src = "fun greet(name: Str) -> Str =\n  \"Hello, {name}!\"\n\nfun main() -> Str =\n  greet(\"World\")\n";
35af6cf 131
    let source = parse(src);
3d6f280 132
    let bytes = compileSource(&source).expect("compile failed");
3d6f280 133
    assert_eq!(runMainStr(&bytes), "Hello, World!");
35af6cf 134
}
35af6cf 135
35af6cf 136
#[test]
3d6f280 137
fn stringInterpolationOfABoolRunsCorrectly() {
b48d3a3 138
    let src = "fun main() -> Str =\n  b = True\n  \"is {b}\"\n";
35af6cf 139
    let source = parse(src);
3d6f280 140
    let bytes = compileSource(&source).expect("compile failed");
3d6f280 141
    assert_eq!(runMainStr(&bytes), "is True");
35af6cf 142
}
35af6cf 143
35af6cf 144
#[test]
3d6f280 145
fn stringInterpolationOfAFloatIsAClearError() {
35af6cf 146
    // Float-to-decimal-string formatting is a substantial separate undertaking
35af6cf 147
    // (correct rounding needs something like Grisu/Ryu); scoped out for now with
35af6cf 148
    // an explicit error rather than emitting an incorrect conversion.
b48d3a3 149
    let src = "fun main() -> Str =\n  x = 1.5\n  \"{x}\"\n";
35af6cf 150
    let source = parse(src);
3d6f280 151
    let err = compileSource(&source).expect_err("float interpolation should not silently compile");
35af6cf 152
    assert!(err.contains("Float"), "got: {}", err);
5d8ada1 153
}
5d8ada1 154
5d8ada1 155
#[test]
3d6f280 156
fn floatArithmeticAndNegationCompile() {
3d6f280 157
    assertValid("fun main(x: Float) -> Float =\n  y = -x\n  y + 1.5\n");
5d8ada1 158
}
5d8ada1 159
5d8ada1 160
#[test]
3d6f280 161
fn classFieldAndMethodCompile() {
5d8ada1 162
    let src = "\
5d8ada1 163
type Cat =
5d8ada1 164
  name: Str
5d8ada1 165
  age: Int
5d8ada1 166
805d96d 167
  fun getAge() -> Int =
805d96d 168
    self.age
5d8ada1 169
b48d3a3 170
fun makeCat() -> Int =
5d8ada1 171
  c = Cat(name: \"x\", age: 3)
5d8ada1 172
  c.getAge()
5d8ada1 173
";
3d6f280 174
    assertValid(src);
5d8ada1 175
}
5d8ada1 176
5d8ada1 177
#[test]
3d6f280 178
fn nestedClassCallCompiles() {
5d8ada1 179
    let src = "\
5d8ada1 180
type Pair =
5d8ada1 181
  a: Int
5d8ada1 182
  b: Int
5d8ada1 183
5d8ada1 184
type Wrapper =
5d8ada1 185
  inner: Pair
5d8ada1 186
  tag: Int
5d8ada1 187
b48d3a3 188
fun make() -> Int =
5d8ada1 189
  w = Wrapper(inner: Pair(a: 1, b: 2), tag: 9)
5d8ada1 190
  w.tag
5d8ada1 191
";
3d6f280 192
    assertValid(src);
5d8ada1 193
}
5d8ada1 194
5d8ada1 195
#[test]
3d6f280 196
fn matchWithIntAndWildcardCompiles() {
b48d3a3 197
    let src = "fun main(a: Int) -> Int =\n  match a\n    1 =>\n      return 10\n    _ =>\n      return 0\n";
3d6f280 198
    assertValid(src);
5d8ada1 199
}
5d8ada1 200
5d8ada1 201
#[test]
3d6f280 202
fn matchBindingPatternCompiles() {
b48d3a3 203
    let src = "fun main(a: Int) -> Int =\n  match a\n    x =>\n      return x\n";
3d6f280 204
    assertValid(src);
5d8ada1 205
}
5d8ada1 206
660674c 207
#[test]
3d6f280 208
fn matchInlineCaseBodyCompiles() {
660674c 209
    // Case bodies can be a single inline expression, not just an indented block.
b48d3a3 210
    let src = "fun main(a: Int) =\n  match a\n    1 => 10\n    _ => 0\n";
3d6f280 211
    assertValid(src);
660674c 212
}
660674c 213
5d8ada1 214
#[test]
3d6f280 215
fn matchBoolVariantPatternCompiles() {
b48d3a3 216
    let src = "fun main(a: Bool) -> Int =\n  match a\n    True =>\n      return 1\n    False =>\n      return 0\n";
3d6f280 217
    assertValid(src);
5d8ada1 218
}
5d8ada1 219
5d8ada1 220
#[test]
3d6f280 221
fn matchStringPatternIsAClearError() {
b48d3a3 222
    let src = "fun main(a: Str) -> Int =\n  match a\n    \"x\" =>\n      1\n    _ =>\n      0\n";
5d8ada1 223
    let source = parse(src);
3d6f280 224
    let err = compileSource(&source).expect_err("string match patterns are not yet supported");
5d8ada1 225
    assert!(err.contains("string match"), "got: {}", err);
5d8ada1 226
}
5d8ada1 227
5e131a3 228
#[test]
3d6f280 229
fn nestedConstructorPatternMatchesAndBindsRunsCorrectly() {
5e131a3 230
    let src = "\
5e131a3 231
enum Option =
d958617 232
  | Some[Int]
5e131a3 233
  | None
5e131a3 234
5e131a3 235
enum Nested =
d958617 236
  | Wrap[Option]
5e131a3 237
  | Empty
5e131a3 238
b48d3a3 239
fun f(n: Nested) -> Int =
5e131a3 240
  match n
5e131a3 241
    Wrap(Some(v)) =>
5e131a3 242
      return v
35af6cf 243
    Wrap(None) =>
35af6cf 244
      return -1
5e131a3 245
    Empty =>
5e131a3 246
      return 0
5e131a3 247
b48d3a3 248
fun main() -> Int =
35af6cf 249
  f(Wrap(Some(5)))
5e131a3 250
";
5e131a3 251
    let source = parse(src);
3d6f280 252
    let bytes = compileSource(&source).expect("compile failed");
3d6f280 253
    assert_eq!(runMain(&bytes), 5);
35af6cf 254
}
35af6cf 255
35af6cf 256
#[test]
3d6f280 257
fn nestedConstructorPatternMismatchFallsThroughToNextCaseRunsCorrectly() {
35af6cf 258
    // `Wrap(Some(v))` shouldn't match a runtime `Wrap(None)` — codegen must fall
35af6cf 259
    // through to the next *top-level* case, not just fail to bind `v`.
35af6cf 260
    let src = "\
35af6cf 261
enum Option =
d958617 262
  | Some[Int]
35af6cf 263
  | None
35af6cf 264
35af6cf 265
enum Nested =
d958617 266
  | Wrap[Option]
35af6cf 267
  | Empty
35af6cf 268
b48d3a3 269
fun f(n: Nested) -> Int =
35af6cf 270
  match n
35af6cf 271
    Wrap(Some(v)) =>
35af6cf 272
      return v
35af6cf 273
    Wrap(None) =>
35af6cf 274
      return -1
35af6cf 275
    Empty =>
35af6cf 276
      return 0
35af6cf 277
b48d3a3 278
fun main() -> Int =
35af6cf 279
  f(Wrap(None))
35af6cf 280
";
35af6cf 281
    let source = parse(src);
3d6f280 282
    let bytes = compileSource(&source).expect("compile failed");
3d6f280 283
    assert_eq!(runMain(&bytes), -1);
35af6cf 284
}
35af6cf 285
35af6cf 286
#[test]
3d6f280 287
fn nestedConstructorPatternAgainstASpecializedGenericEnumRunsCorrectly() {
35af6cf 288
    // Exercises monomorphize.rs's recursive mangling: the outer `Full(...)` pattern
35af6cf 289
    // matches against `Box`'s own specialization, but the *inner* `Some(v)`/`None`
35af6cf 290
    // sub-pattern matches against `Box`'s generic field type (`Option`, itself
35af6cf 291
    // specialized to `Option$Int`) — each level needs its own mangling table, not
35af6cf 292
    // just the outermost one.
35af6cf 293
    let src = "\
35af6cf 294
enum Option =
d958617 295
  | Some[T]
35af6cf 296
  | None
35af6cf 297
35af6cf 298
enum Box =
d958617 299
  | Full[T]
35af6cf 300
  | Empty
35af6cf 301
b48d3a3 302
fun unwrap(b: Box) -> Int =
35af6cf 303
  match b
35af6cf 304
    Full(Some(v)) => v
35af6cf 305
    Full(None) => -1
35af6cf 306
    Empty => 0
35af6cf 307
b48d3a3 308
fun main() -> Int =
35af6cf 309
  unwrap(Full(Some(7)))
35af6cf 310
";
35af6cf 311
    let source = parse(src);
3d6f280 312
    let bytes = compileSource(&source).expect("compile failed");
3d6f280 313
    assert_eq!(runMain(&bytes), 7);
35af6cf 314
}
35af6cf 315
35af6cf 316
#[test]
3d6f280 317
fn doublyNestedConstructorPatternRunsCorrectly() {
35af6cf 318
    // `Some(Some(v))` — two levels of nesting, proving the recursion isn't just
35af6cf 319
    // one level deep.
35af6cf 320
    let src = "\
35af6cf 321
enum Option =
d958617 322
  | Some[Option]
35af6cf 323
  | None
35af6cf 324
b48d3a3 325
fun unwrapTwice(o: Option) -> Int =
35af6cf 326
  match o
35af6cf 327
    Some(Some(None)) => 1
35af6cf 328
    Some(None) => 2
35af6cf 329
    None => 3
35af6cf 330
    _ => 0
35af6cf 331
b48d3a3 332
fun main() -> Int =
35af6cf 333
  unwrapTwice(Some(Some(None)))
35af6cf 334
";
35af6cf 335
    let source = parse(src);
3d6f280 336
    let bytes = compileSource(&source).expect("compile failed");
3d6f280 337
    assert_eq!(runMain(&bytes), 1);
5e131a3 338
}
5e131a3 339
5d8ada1 340
/// Runs `main`'s wasm bytes and returns its i64 result. wasm's own validator
5d8ada1 341
/// (via wasmparser, above) only proves the module is well-formed — it can't catch
5d8ada1 342
/// wrong *values*, so these tests actually execute the compiled output.
3d6f280 343
fn runMain(bytes: &[u8]) -> i64 {
5ef4579 344
    let engine = gcEngine();
5d8ada1 345
    let module = wasmtime::Module::new(&engine, bytes).expect("module should be loadable");
5d8ada1 346
    let mut store = wasmtime::Store::new(&engine, ());
5d8ada1 347
    let instance = wasmtime::Instance::new(&mut store, &module, &[]).expect("module should instantiate");
5d8ada1 348
    let main = instance
5d8ada1 349
        .get_typed_func::<(), i64>(&mut store, "main")
5d8ada1 350
        .expect("main should have signature () -> i64");
5d8ada1 351
    main.call(&mut store, ()).expect("main should not trap")
5d8ada1 352
}
5d8ada1 353
3d6f280 354
fn runMainF64(bytes: &[u8]) -> f64 {
5ef4579 355
    let engine = gcEngine();
35af6cf 356
    let module = wasmtime::Module::new(&engine, bytes).expect("module should be loadable");
35af6cf 357
    let mut store = wasmtime::Store::new(&engine, ());
35af6cf 358
    let instance = wasmtime::Instance::new(&mut store, &module, &[]).expect("module should instantiate");
35af6cf 359
    let main = instance
35af6cf 360
        .get_typed_func::<(), f64>(&mut store, "main")
35af6cf 361
        .expect("main should have signature () -> f64");
35af6cf 362
    main.call(&mut store, ()).expect("main should not trap")
35af6cf 363
}
35af6cf 364
0e39618 365
/// Runs a `() -> Str`-returning `main`, reading the returned `array<i8>` GC value
0e39618 366
/// back out byte-by-byte via wasmtime's host-side GC ref API (`Str` has no length
0e39618 367
/// prefix of its own now — `array.len` is native, see Decision 4 of the wasm-gc
0e39618 368
/// migration plan) — untyped `Func::call` is used because `main`'s wasm return type
0e39618 369
/// is a concrete `(ref $Str)`, not one `get_typed_func` can name directly.
3d6f280 370
fn runMainStr(bytes: &[u8]) -> String {
5ef4579 371
    let engine = gcEngine();
35af6cf 372
    let module = wasmtime::Module::new(&engine, bytes).expect("module should be loadable");
35af6cf 373
    let mut store = wasmtime::Store::new(&engine, ());
35af6cf 374
    let instance = wasmtime::Instance::new(&mut store, &module, &[]).expect("module should instantiate");
0e39618 375
    let main = instance.get_func(&mut store, "main").expect("module should export main");
0e39618 376
    let mut results = [wasmtime::Val::null_any_ref()];
0e39618 377
    main.call(&mut store, &[], &mut results).expect("main should not trap");
0e39618 378
    let any_ref = match &results[0] {
0e39618 379
        wasmtime::Val::AnyRef(Some(r)) => *r,
0e39618 380
        other => panic!("main should return a non-null anyref (Str), got {:?}", other),
0e39618 381
    };
0e39618 382
    let array = any_ref.unwrap_array(&store).expect("Str's returned anyref should be a GC array");
0e39618 383
    let len = array.len(&store).expect("array.len should succeed");
0e39618 384
    let mut bytes_out = Vec::with_capacity(len as usize);
0e39618 385
    for i in 0..len {
0e39618 386
        let byte = match array.get(&mut store, i).expect("array.get should succeed") {
0e39618 387
            wasmtime::Val::I32(b) => b as u8,
0e39618 388
            other => panic!("Str array element should be i32, got {:?}", other),
0e39618 389
        };
0e39618 390
        bytes_out.push(byte);
0e39618 391
    }
0e39618 392
    String::from_utf8(bytes_out).expect("string bytes should be valid utf8")
35af6cf 393
}
35af6cf 394
5d8ada1 395
#[test]
3d6f280 396
fn factorialRunsCorrectly() {
5d8ada1 397
    let src = "\
b48d3a3 398
fun factorial(x: Int) -> Int =
5d8ada1 399
  if x < 2
5d8ada1 400
    return 1
5d8ada1 401
  return x * factorial(x - 1)
5d8ada1 402
b48d3a3 403
fun main() -> Int =
5d8ada1 404
  factorial(5)
5d8ada1 405
";
5d8ada1 406
    let source = parse(src);
3d6f280 407
    let bytes = compileSource(&source).expect("compile failed");
3d6f280 408
    assert_eq!(runMain(&bytes), 120);
5d8ada1 409
}
5d8ada1 410
5d8ada1 411
#[test]
3d6f280 412
fn classFieldAndMethodRunCorrectly() {
5d8ada1 413
    let src = "\
5d8ada1 414
type Cat =
5d8ada1 415
  name: Str
5d8ada1 416
  age: Int
5d8ada1 417
805d96d 418
  fun getAge() -> Int =
805d96d 419
    self.age
5d8ada1 420
b48d3a3 421
fun main() -> Int =
5d8ada1 422
  c = Cat(name: \"x\", age: 7)
5d8ada1 423
  c.getAge()
5d8ada1 424
";
5d8ada1 425
    let source = parse(src);
3d6f280 426
    let bytes = compileSource(&source).expect("compile failed");
3d6f280 427
    assert_eq!(runMain(&bytes), 7);
5d8ada1 428
}
5d8ada1 429
d2640d2 430
#[test]
3d6f280 431
fn nestedMethodDeclarationRunsCorrectly() {
d2640d2 432
    let src = "\
d2640d2 433
type Cat =
d2640d2 434
  name: Str
d2640d2 435
  age: Int
d2640d2 436
d2640d2 437
  fun getAge(self) -> Int =
d2640d2 438
    self.age
d2640d2 439
d2640d2 440
fun main() -> Int =
d2640d2 441
  c = Cat(name: \"x\", age: 7)
d2640d2 442
  c.getAge()
d2640d2 443
";
d2640d2 444
    let source = parse(src);
3d6f280 445
    let bytes = compileSource(&source).expect("compile failed");
3d6f280 446
    assert_eq!(runMain(&bytes), 7);
d2640d2 447
}
d2640d2 448
d2640d2 449
#[test]
3d6f280 450
fn enumDiscriminantValueFieldAccessRunsCorrectlyForEachVariant() {
d2640d2 451
    let src = "\
d2640d2 452
enum Step(n: Int) =
d2640d2 453
  | ReadMin(10)
d2640d2 454
  | ReadMax(20)
d2640d2 455
d2640d2 456
  fun toNumber(self) -> Int =
d2640d2 457
    self.n
d2640d2 458
d2640d2 459
fun main() -> Int =
d2640d2 460
  ReadMin.toNumber() * 100 + ReadMax.toNumber()
d2640d2 461
";
d2640d2 462
    let source = parse(src);
3d6f280 463
    let bytes = compileSource(&source).expect("compile failed");
3d6f280 464
    assert_eq!(runMain(&bytes), 1020);
d2640d2 465
}
d2640d2 466
d2640d2 467
#[test]
3d6f280 468
fn enumDiscriminantValueMatchesByVariantNameCorrectly() {
d2640d2 469
    let src = "\
d2640d2 470
enum Step(n: Int) =
d2640d2 471
  | ReadMin(10)
d2640d2 472
  | ReadMax(20)
d2640d2 473
d2640d2 474
fun toNumber(s: Step) -> Int =
d2640d2 475
  match s
d2640d2 476
    ReadMin => 1
d2640d2 477
    ReadMax => 2
d2640d2 478
d2640d2 479
fun main() -> Int =
d2640d2 480
  toNumber(ReadMin) * 10 + toNumber(ReadMax)
d2640d2 481
";
d2640d2 482
    let source = parse(src);
3d6f280 483
    let bytes = compileSource(&source).expect("compile failed");
3d6f280 484
    assert_eq!(runMain(&bytes), 12);
d2640d2 485
}
d2640d2 486
5d8ada1 487
#[test]
3d6f280 488
fn nestedClassCallRunsCorrectly() {
5d8ada1 489
    let src = "\
5d8ada1 490
type Pair =
5d8ada1 491
  a: Int
5d8ada1 492
  b: Int
5d8ada1 493
5d8ada1 494
type Wrapper =
5d8ada1 495
  inner: Pair
5d8ada1 496
  tag: Int
5d8ada1 497
b48d3a3 498
fun main() -> Int =
5d8ada1 499
  w = Wrapper(inner: Pair(a: 11, b: 22), tag: 99)
5d8ada1 500
  w.inner.b
5d8ada1 501
";
5d8ada1 502
    let source = parse(src);
3d6f280 503
    let bytes = compileSource(&source).expect("compile failed");
3d6f280 504
    assert_eq!(runMain(&bytes), 22);
5d8ada1 505
}
5d8ada1 506
5d8ada1 507
#[test]
3d6f280 508
fn repeatedClassCallInALoopDoesNotAlias() {
5d8ada1 509
    // Regression test: class instances are bump-allocated at *runtime* (via a
5d8ada1 510
    // mutable wasm global), not at a compile-time-fixed address — otherwise every
5d8ada1 511
    // iteration's `Box(...)` would alias the same memory and this would sum to 5*4=20
5d8ada1 512
    // instead of 0+1+2+3+4=10.
5d8ada1 513
    let src = "\
5d8ada1 514
type Box =
5d8ada1 515
  v: Int
5d8ada1 516
b48d3a3 517
fun sumBoxes() -> Int =
5d8ada1 518
  total = 0
0000000 519
  for i := range 5
5d8ada1 520
    b = Box(v: i)
5d8ada1 521
    total = total + b.v
5d8ada1 522
  return total
5d8ada1 523
b48d3a3 524
fun main() -> Int =
5d8ada1 525
  sumBoxes()
5d8ada1 526
";
5d8ada1 527
    let source = parse(src);
3d6f280 528
    let bytes = compileSource(&source).expect("compile failed");
3d6f280 529
    assert_eq!(runMain(&bytes), 10);
5d8ada1 530
}
5d8ada1 531
5d8ada1 532
#[test]
3d6f280 533
fn matchIntAndWildcardRunCorrectly() {
5d8ada1 534
    let src = "\
b48d3a3 535
fun classify(a: Int) -> Int =
5d8ada1 536
  match a
5d8ada1 537
    1 =>
5d8ada1 538
      return 100
5d8ada1 539
    2 =>
5d8ada1 540
      return 200
5d8ada1 541
    _ =>
5d8ada1 542
      return 0
5d8ada1 543
b48d3a3 544
fun main() -> Int =
5d8ada1 545
  classify(2)
5d8ada1 546
";
5d8ada1 547
    let source = parse(src);
3d6f280 548
    let bytes = compileSource(&source).expect("compile failed");
3d6f280 549
    assert_eq!(runMain(&bytes), 200);
5d8ada1 550
}
5d8ada1 551
5d8ada1 552
#[test]
3d6f280 553
fn matchBoolVariantPatternRunsCorrectly() {
5d8ada1 554
    // Regression test: True/False are built-in Bool variants and must be treated as
5d8ada1 555
    // tag comparisons, not bindings, even without an explicit `enum Bool` in this
5d8ada1 556
    // source file — otherwise the first arm always "matches" (as a rebinding) and
5d8ada1 557
    // `pick(False)` would wrongly return 1.
5d8ada1 558
    let src = "\
b48d3a3 559
fun pick(a: Bool) -> Int =
5d8ada1 560
  match a
5d8ada1 561
    True =>
5d8ada1 562
      return 1
5d8ada1 563
    False =>
5d8ada1 564
      return 0
5d8ada1 565
b48d3a3 566
fun main() -> Int =
5d8ada1 567
  pick(False)
5d8ada1 568
";
5d8ada1 569
    let source = parse(src);
3d6f280 570
    let bytes = compileSource(&source).expect("compile failed");
3d6f280 571
    assert_eq!(runMain(&bytes), 0);
5d8ada1 572
}
5d8ada1 573
5d8ada1 574
#[test]
3d6f280 575
fn lowercaseSingleWordFunctionCallRunsCorrectly() {
5d8ada1 576
    // Regression test: `factorial(...)` (an all-lowercase, no-uppercase, no-underscore
5d8ada1 577
    // callee) used to fail to parse at all — `var_identifier` and `fn_identifier` both
5d8ada1 578
    // matched its text and the grammar's lexer would nondeterministically commit to
5d8ada1 579
    // `var_identifier`, breaking every such call site.
5d8ada1 580
    let src = "\
b48d3a3 581
fun double(n: Int) -> Int =
5d8ada1 582
  n * 2
5d8ada1 583
b48d3a3 584
fun main() -> Int =
5d8ada1 585
  double(21)
5d8ada1 586
";
5d8ada1 587
    let source = parse(src);
3d6f280 588
    let bytes = compileSource(&source).expect("compile failed");
3d6f280 589
    assert_eq!(runMain(&bytes), 42);
5d8ada1 590
}
12537c4 591
12537c4 592
#[test]
3d6f280 593
fn assertTrapsOnFalseAndPassesThroughOnTrue() {
12537c4 594
    let src_ok = "\
b48d3a3 595
fun check(n: Int) -> Int =
12537c4 596
  assert n > 0
12537c4 597
  n
12537c4 598
b48d3a3 599
fun main() -> Int =
12537c4 600
  check(5)
12537c4 601
";
12537c4 602
    let source = parse(src_ok);
3d6f280 603
    let bytes = compileSource(&source).expect("compile failed");
3d6f280 604
    assert_eq!(runMain(&bytes), 5);
12537c4 605
12537c4 606
    let src_trap = "\
b48d3a3 607
fun check(n: Int) -> Int =
12537c4 608
  assert n > 0
12537c4 609
  n
12537c4 610
b48d3a3 611
fun main() -> Int =
12537c4 612
  check(-1)
12537c4 613
";
12537c4 614
    let source = parse(src_trap);
3d6f280 615
    let bytes = compileSource(&source).expect("compile failed");
5ef4579 616
    let engine = gcEngine();
12537c4 617
    let module = wasmtime::Module::new(&engine, &bytes).unwrap();
12537c4 618
    let mut store = wasmtime::Store::new(&engine, ());
12537c4 619
    let instance = wasmtime::Instance::new(&mut store, &module, &[]).unwrap();
12537c4 620
    let main = instance.get_typed_func::<(), i64>(&mut store, "main").unwrap();
12537c4 621
    let err = main.call(&mut store, ()).expect_err("a false assert should trap, not silently continue");
12537c4 622
    assert_eq!(err.downcast_ref::<wasmtime::Trap>(), Some(&wasmtime::Trap::UnreachableCodeReached), "got: {}", err);
12537c4 623
}
12537c4 624
12537c4 625
#[test]
3d6f280 626
fn todoTrapsAtRuntime() {
12537c4 627
    // `todo` marks an unimplemented body — it must trap, not silently do nothing.
12537c4 628
    let src = "\
b48d3a3 629
fun notDoneYet() -> Int =
12537c4 630
  todo
12537c4 631
b48d3a3 632
fun main() -> Int =
12537c4 633
  notDoneYet()
12537c4 634
";
12537c4 635
    let source = parse(src);
3d6f280 636
    let bytes = compileSource(&source).expect("compile failed");
5ef4579 637
    let engine = gcEngine();
12537c4 638
    let module = wasmtime::Module::new(&engine, &bytes).unwrap();
12537c4 639
    let mut store = wasmtime::Store::new(&engine, ());
12537c4 640
    let instance = wasmtime::Instance::new(&mut store, &module, &[]).unwrap();
12537c4 641
    let main = instance.get_typed_func::<(), i64>(&mut store, "main").unwrap();
12537c4 642
    let err = main.call(&mut store, ()).expect_err("todo should trap");
12537c4 643
    assert_eq!(err.downcast_ref::<wasmtime::Trap>(), Some(&wasmtime::Trap::UnreachableCodeReached), "got: {}", err);
12537c4 644
}
380a51c 645
380a51c 646
#[test]
3d6f280 647
fn payloadFreeVariantConstructionCompiles() {
380a51c 648
    let src = "\
380a51c 649
enum Color =
380a51c 650
  | Red
380a51c 651
  | Green
380a51c 652
  | Blue
380a51c 653
b48d3a3 654
fun main() -> Int =\n  x = Green\n  0\n";
3d6f280 655
    assertValid(src);
380a51c 656
}
380a51c 657
380a51c 658
#[test]
3d6f280 659
fn payloadVariantConstructionCompilesAndRuns() {
380a51c 660
    let src = "\
380a51c 661
enum Option =
d958617 662
  | Some[Int]
380a51c 663
  | None
380a51c 664
b48d3a3 665
fun unwrapOr(o: Option, default: Int) -> Int =
380a51c 666
  match o
380a51c 667
    Some(v) =>
380a51c 668
      return v
380a51c 669
    None =>
380a51c 670
      return default
380a51c 671
b48d3a3 672
fun main() -> Int =
380a51c 673
  unwrapOr(Some(7), 0)
380a51c 674
";
380a51c 675
    let source = parse(src);
3d6f280 676
    let bytes = compileSource(&source).expect("compile failed");
3d6f280 677
    assert_eq!(runMain(&bytes), 7);
380a51c 678
}
380a51c 679
380a51c 680
#[test]
3d6f280 681
fn multiFieldVariantConstructionCompilesAndRuns() {
380a51c 682
    let src = "\
380a51c 683
enum Shape =
d958617 684
  | Rect[Int, Int]
d958617 685
  | Circle[Int]
380a51c 686
b48d3a3 687
fun area(s: Shape) -> Int =
380a51c 688
  match s
380a51c 689
    Rect(w, h) =>
380a51c 690
      return w * h
380a51c 691
    Circle(r) =>
380a51c 692
      return r * r
380a51c 693
b48d3a3 694
fun main() -> Int =
380a51c 695
  area(Rect(3, 4))
380a51c 696
";
380a51c 697
    let source = parse(src);
3d6f280 698
    let bytes = compileSource(&source).expect("compile failed");
3d6f280 699
    assert_eq!(runMain(&bytes), 12);
380a51c 700
}
8ecbf56 701
8ecbf56 702
#[test]
3d6f280 703
fn nonBoolBareTagPatternRunsCorrectly() {
8ecbf56 704
    let src = "\
8ecbf56 705
enum Color =
8ecbf56 706
  | Red
8ecbf56 707
  | Green
8ecbf56 708
  | Blue
8ecbf56 709
b48d3a3 710
fun code(c: Color) -> Int =
8ecbf56 711
  match c
8ecbf56 712
    Red =>
8ecbf56 713
      return 1
8ecbf56 714
    Green =>
8ecbf56 715
      return 2
8ecbf56 716
    Blue =>
8ecbf56 717
      return 3
8ecbf56 718
b48d3a3 719
fun main() -> Int =
8ecbf56 720
  code(Green)
8ecbf56 721
";
8ecbf56 722
    let source = parse(src);
3d6f280 723
    let bytes = compileSource(&source).expect("compile failed");
3d6f280 724
    assert_eq!(runMain(&bytes), 2);
8ecbf56 725
}
8ecbf56 726
8ecbf56 727
#[test]
3d6f280 728
fn constructorPatternWildcardFieldRunsCorrectly() {
8ecbf56 729
    let src = "\
8ecbf56 730
enum Option =
d958617 731
  | Some[Int]
8ecbf56 732
  | None
8ecbf56 733
b48d3a3 734
fun isSome(o: Option) -> Int =
8ecbf56 735
  match o
8ecbf56 736
    Some(_) =>
8ecbf56 737
      return 1
8ecbf56 738
    None =>
8ecbf56 739
      return 0
8ecbf56 740
b48d3a3 741
fun main() -> Int =
8ecbf56 742
  isSome(Some(99))
8ecbf56 743
";
8ecbf56 744
    let source = parse(src);
3d6f280 745
    let bytes = compileSource(&source).expect("compile failed");
3d6f280 746
    assert_eq!(runMain(&bytes), 1);
8ecbf56 747
}
5e131a3 748
5e131a3 749
#[test]
3d6f280 750
fn constructorPatternDoesNotMisfireOnPayloadFreeSibling() {
5e131a3 751
    // Regression test: `None` is a small-int tag, not a heap pointer. The
5e131a3 752
    // constructor-pattern arm for `Some(v)` must not treat a payload-free
5e131a3 753
    // sibling value as if it were a pointer to a `Some` payload.
5e131a3 754
    let src = "\
5e131a3 755
enum Option =
d958617 756
  | Some[Int]
5e131a3 757
  | None
5e131a3 758
b48d3a3 759
fun unwrapOr(o: Option, default: Int) -> Int =
5e131a3 760
  match o
5e131a3 761
    Some(v) =>
5e131a3 762
      return v
5e131a3 763
    None =>
5e131a3 764
      return default
5e131a3 765
b48d3a3 766
fun main() -> Int =
5e131a3 767
  unwrapOr(None, 5)
5e131a3 768
";
5e131a3 769
    let source = parse(src);
3d6f280 770
    let bytes = compileSource(&source).expect("compile failed");
3d6f280 771
    assert_eq!(runMain(&bytes), 5);
5e131a3 772
}
5e131a3 773
5e131a3 774
#[test]
3d6f280 775
fn enumClassFieldConstructAndDestructureRunsCorrectly() {
5e131a3 776
    // Interop check: a class with a field of an enum type, constructed with a
5e131a3 777
    // payload variant, then matched via the class field.
5e131a3 778
    let src = "\
5e131a3 779
enum Option =
d958617 780
  | Some[Int]
5e131a3 781
  | None
5e131a3 782
5e131a3 783
type Box =
5e131a3 784
  value: Option
5e131a3 785
805d96d 786
  fun unwrap(default: Int) -> Int =
805d96d 787
    match self.value
805d96d 788
      Some(v) =>
805d96d 789
        return v
805d96d 790
      None =>
805d96d 791
        return default
5e131a3 792
b48d3a3 793
fun main() -> Int =
5e131a3 794
  b = Box(value: Some(42))
5e131a3 795
  b.unwrap(0)
5e131a3 796
";
5e131a3 797
    let source = parse(src);
3d6f280 798
    let bytes = compileSource(&source).expect("compile failed");
3d6f280 799
    assert_eq!(runMain(&bytes), 42);
5e131a3 800
}
3254688 801
3254688 802
#[test]
3d6f280 803
fn tailMatchWithoutReturnRunsCorrectly() {
3254688 804
    let src = "\
b48d3a3 805
fun bindExample(n: Int) -> Int =
3254688 806
  match n
3254688 807
    x =>
3254688 808
      x
3254688 809
b48d3a3 810
fun main() -> Int =
3254688 811
  bindExample(5)
3254688 812
";
3254688 813
    let source = parse(src);
3d6f280 814
    let bytes = compileSource(&source).expect("compile failed");
3d6f280 815
    assert_eq!(runMain(&bytes), 5);
3254688 816
}
3254688 817
3254688 818
#[test]
3d6f280 819
fn tailIfWithoutReturnRunsCorrectly() {
3254688 820
    let src = "\
b48d3a3 821
fun abs(n: Int) -> Int =
3254688 822
  if n < 0
3254688 823
    -n
3254688 824
  else
3254688 825
    n
3254688 826
b48d3a3 827
fun main() -> Int =
3254688 828
  abs(-7)
3254688 829
";
3254688 830
    let source = parse(src);
3d6f280 831
    let bytes = compileSource(&source).expect("compile failed");
3d6f280 832
    assert_eq!(runMain(&bytes), 7);
3254688 833
}
3254688 834
3254688 835
#[test]
3d6f280 836
fn tailIfNestedInsideMatchArmWithoutReturnRunsCorrectly() {
3254688 837
    let src = "\
b48d3a3 838
fun classify(n: Int) -> Int =
3254688 839
  match n
3254688 840
    0 =>
3254688 841
      1
3254688 842
    x =>
3254688 843
      if x < 0
3254688 844
        -1
3254688 845
      else
3254688 846
        2
3254688 847
b48d3a3 848
fun main() -> Int =
3254688 849
  classify(-5)
3254688 850
";
3254688 851
    let source = parse(src);
3d6f280 852
    let bytes = compileSource(&source).expect("compile failed");
3d6f280 853
    assert_eq!(runMain(&bytes), -1);
3254688 854
}
3254688 855
3254688 856
#[test]
3d6f280 857
fn tailMatchMixingReturnAndBareExprArmsRunsCorrectly() {
3254688 858
    let src = "\
b48d3a3 859
fun describe(n: Int) -> Int =
3254688 860
  match n
3254688 861
    0 =>
3254688 862
      return 100
3254688 863
    x =>
3254688 864
      x * 2
3254688 865
b48d3a3 866
fun main() -> Int =
3254688 867
  describe(21)
3254688 868
";
3254688 869
    let source = parse(src);
3d6f280 870
    let bytes = compileSource(&source).expect("compile failed");
3d6f280 871
    assert_eq!(runMain(&bytes), 42);
3254688 872
}
3254688 873
3254688 874
#[test]
3d6f280 875
fn tailEnumMatchWithoutReturnRunsCorrectly() {
3254688 876
    let src = "\
3254688 877
enum Option =
d958617 878
  | Some[Int]
3254688 879
  | None
3254688 880
b48d3a3 881
fun unwrapOr(o: Option, default: Int) -> Int =
3254688 882
  match o
3254688 883
    Some(v) =>
3254688 884
      v
3254688 885
    None =>
3254688 886
      default
3254688 887
b48d3a3 888
fun main() -> Int =
3254688 889
  unwrapOr(Some(9), 0)
3254688 890
";
3254688 891
    let source = parse(src);
3d6f280 892
    let bytes = compileSource(&source).expect("compile failed");
3d6f280 893
    assert_eq!(runMain(&bytes), 9);
3254688 894
}
3254688 895
3254688 896
#[test]
3d6f280 897
fn tailIfWithoutElseIsAClearError() {
3254688 898
    let src = "\
b48d3a3 899
fun bad(n: Int) -> Int =
3254688 900
  if n < 0
3254688 901
    return 1
3254688 902
";
3254688 903
    let source = parse(src);
3d6f280 904
    let err = compileSource(&source).expect_err("if without else in value position must be a clear error, not invalid wasm");
3254688 905
    assert!(err.contains("doesn't produce a return value"), "got: {}", err);
3254688 906
}
3254688 907
3254688 908
#[test]
3d6f280 909
fn tailMatchNonExhaustiveIsAClearError() {
3254688 910
    let src = "\
b48d3a3 911
fun bad(n: Int) -> Int =
3254688 912
  match n
3254688 913
    0 =>
3254688 914
      1
3254688 915
";
3254688 916
    let source = parse(src);
3d6f280 917
    let err = compileSource(&source).expect_err("non-exhaustive match in value position must be a clear error, not invalid wasm");
3254688 918
    assert!(err.contains("doesn't produce a return value"), "got: {}", err);
3254688 919
}
3254688 920
3254688 921
#[test]
3d6f280 922
fn tailMatchArmEndingInNonValueStatementIsAClearError() {
3254688 923
    let src = "\
b48d3a3 924
fun bad(n: Int) -> Int =
3254688 925
  match n
3254688 926
    x =>
3254688 927
      y = x
3254688 928
";
3254688 929
    let source = parse(src);
3d6f280 930
    let err = compileSource(&source).expect_err("a match arm ending in a non-value statement must be a clear error, not invalid wasm");
3254688 931
    assert!(err.contains("doesn't produce a return value"), "got: {}", err);
3254688 932
}
2e28ecc 933
2e28ecc 934
#[test]
3d6f280 935
fn genericClassSpecializedAtTwoTypesDoesNotAlias() {
2e28ecc 936
    let src = "\
d958617 937
type Box[T] =
d958617 938
  value: T
2e28ecc 939
805d96d 940
  fun getIntValue() -> Int =
805d96d 941
    self.value
2e28ecc 942
b48d3a3 943
fun useInt() -> Int =
2e28ecc 944
  b = Box(value: 7)
2e28ecc 945
  b.getIntValue()
2e28ecc 946
b48d3a3 947
fun main() -> Int =
2e28ecc 948
  useInt()
2e28ecc 949
";
2e28ecc 950
    let source = parse(src);
3d6f280 951
    let bytes = compileSource(&source).expect("compile failed");
3d6f280 952
    assert_eq!(runMain(&bytes), 7);
2e28ecc 953
}
2e28ecc 954
2e28ecc 955
#[test]
3d6f280 956
fn genericFunctionCalledAtMultipleConcreteTypesRunsCorrectly() {
2e28ecc 957
    let src = "\
b48d3a3 958
fun identity(value: T) -> T =
2e28ecc 959
  value
2e28ecc 960
b48d3a3 961
fun main() -> Int =
2e28ecc 962
  identity(5) + identity(37)
2e28ecc 963
";
2e28ecc 964
    let source = parse(src);
3d6f280 965
    let bytes = compileSource(&source).expect("compile failed");
3d6f280 966
    assert_eq!(runMain(&bytes), 42);
2e28ecc 967
}
2e28ecc 968
2e28ecc 969
#[test]
3d6f280 970
fn genericMethodOnGenericClassRunsCorrectly() {
2e28ecc 971
    let src = "\
d958617 972
type Box[T] =
d958617 973
  value: T
2e28ecc 974
805d96d 975
  fun getValue() -> Int =
805d96d 976
    self.value
2e28ecc 977
b48d3a3 978
fun main() -> Int =
2e28ecc 979
  b = Box(value: 9)
2e28ecc 980
  b.getValue()
2e28ecc 981
";
2e28ecc 982
    let source = parse(src);
3d6f280 983
    let bytes = compileSource(&source).expect("compile failed");
3d6f280 984
    assert_eq!(runMain(&bytes), 9);
2e28ecc 985
}
2e28ecc 986
2e28ecc 987
#[test]
3d6f280 988
fn transitivelyGenericCallChainRunsCorrectly() {
2e28ecc 989
    let src = "\
b48d3a3 990
fun identity(value: T) -> T =
2e28ecc 991
  value
2e28ecc 992
b48d3a3 993
fun doubled(value: T) -> Int =
2e28ecc 994
  identity(value) + identity(value)
2e28ecc 995
b48d3a3 996
fun main() -> Int =
2e28ecc 997
  doubled(21)
2e28ecc 998
";
2e28ecc 999
    let source = parse(src);
3d6f280 1000
    let bytes = compileSource(&source).expect("compile failed");
3d6f280 1001
    assert_eq!(runMain(&bytes), 42);
2e28ecc 1002
}
2e28ecc 1003
2e28ecc 1004
#[test]
3d6f280 1005
fn genericEnumSpecializedAndMatchedRunsCorrectly() {
2e28ecc 1006
    let src = "\
2e28ecc 1007
enum Option =
d958617 1008
  | Some[T]
2e28ecc 1009
  | None
2e28ecc 1010
b48d3a3 1011
fun unwrapOr(o: Option, default: Int) -> Int =
2e28ecc 1012
  match o
2e28ecc 1013
    Some(v) =>
2e28ecc 1014
      v
2e28ecc 1015
    None =>
2e28ecc 1016
      default
2e28ecc 1017
b48d3a3 1018
fun main() -> Int =
2e28ecc 1019
  unwrapOr(Some(13), 0)
2e28ecc 1020
";
2e28ecc 1021
    let source = parse(src);
3d6f280 1022
    let bytes = compileSource(&source).expect("compile failed");
3d6f280 1023
    assert_eq!(runMain(&bytes), 13);
2e28ecc 1024
}
2216237 1025
2216237 1026
#[test]
3d6f280 1027
fn genericEnumMultipleInstantiationsCoexistAndRunCorrectly() {
2216237 1028
    // `Str.length()` is not a real, working method in this codebase (no built-in
2216237 1029
    // Str methods exist in codegen, and string-literal match patterns are an
2216237 1030
    // explicit, documented "not yet supported" error — see
3d6f280 1031
    // `matchStringPatternIsAClearError` above). So the `Some(v) => ...` arm
2216237 1032
    // for the Str instantiation returns a fixed literal instead of deriving
2216237 1033
    // anything from `v`'s content; the point of this test is that `Option$Str`
2216237 1034
    // coexists with `Option$Int` and both run correctly, not string processing.
2216237 1035
    let src = "\
2216237 1036
enum Option =
d958617 1037
  | Some[T]
2216237 1038
  | None
2216237 1039
b48d3a3 1040
fun unwrapIntOr(o: Option, default: Int) -> Int =
2216237 1041
  match o
2216237 1042
    Some(v) =>
2216237 1043
      v
2216237 1044
    None =>
2216237 1045
      default
2216237 1046
b48d3a3 1047
fun unwrapStrOr(o: Option, default: Int) -> Int =
2216237 1048
  match o
2216237 1049
    Some(v) =>
2216237 1050
      4
2216237 1051
    None =>
2216237 1052
      default
2216237 1053
b48d3a3 1054
fun main() -> Int =
2216237 1055
  unwrapIntOr(Some(13), 0) + unwrapStrOr(Some(\"abcd\"), 0)
2216237 1056
";
2216237 1057
    let source = parse(src);
3d6f280 1058
    let bytes = compileSource(&source).expect("compile failed");
3d6f280 1059
    assert_eq!(runMain(&bytes), 17);
2216237 1060
}
2216237 1061
b6a9042 1062
#[test]
3d6f280 1063
fn sameBareGenericEnumParamFunctionCalledMultipleTimesRunsCorrectly() {
b6a9042 1064
    // Regression test: the same generic function taking a bare generic-enum-typed param,
b6a9042 1065
    // called at the same concrete instantiation multiple times, must correctly specialize
b6a9042 1066
    // and reuse that specialization. This tests that the mangling logic for `unwrapOr`
b6a9042 1067
    // produces identical specialized code on both call sites, not aliased/incorrect code.
b6a9042 1068
    let src = "\
b6a9042 1069
enum Option =
d958617 1070
  | Some[T]
b6a9042 1071
  | None
b6a9042 1072
b48d3a3 1073
fun unwrapOr(o: Option, default: Int) -> Int =
b6a9042 1074
  match o
b6a9042 1075
    Some(v) =>
b6a9042 1076
      v
b6a9042 1077
    None =>
b6a9042 1078
      default
b6a9042 1079
b48d3a3 1080
fun main() -> Int =
b6a9042 1081
  unwrapOr(Some(5), 0) + unwrapOr(Some(37), 0)
b6a9042 1082
";
b6a9042 1083
    let source = parse(src);
3d6f280 1084
    let bytes = compileSource(&source).expect("compile failed");
3d6f280 1085
    assert_eq!(runMain(&bytes), 42);
b6a9042 1086
}
b6a9042 1087
2216237 1088
#[test]
3d6f280 1089
fn ordinaryFunctionWithBareGenericClassParamRunsCorrectly() {
2216237 1090
    let src = "\
d958617 1091
type Box[T] =
d958617 1092
  value: T
2216237 1093
805d96d 1094
  fun getBoxValue() -> Int =
805d96d 1095
    self.value
2216237 1096
b48d3a3 1097
fun sumBox(b: Box) -> Int =
2216237 1098
  b.getBoxValue()
2216237 1099
b48d3a3 1100
fun main() -> Int =
2216237 1101
  sumBox(Box(value: 11))
2216237 1102
";
2216237 1103
    let source = parse(src);
3d6f280 1104
    let bytes = compileSource(&source).expect("compile failed");
3d6f280 1105
    assert_eq!(runMain(&bytes), 11);
2216237 1106
}
db00bd9 1107
db00bd9 1108
#[test]
3d6f280 1109
fn wasmModuleWithATableElementValidatesAndCallIndirectWorks() {
db00bd9 1110
    // Exercises WasmModule's new table/element support directly, independent of any
db00bd9 1111
    // closure-compiling logic (which doesn't exist yet) — builds a tiny module by
db00bd9 1112
    // hand: one function that returns 42, registered as table element 0, called via
db00bd9 1113
    // `call_indirect` from `main` using a runtime-computed (not compile-time-constant)
db00bd9 1114
    // table index, to prove the table/element wiring is real, not coincidentally
db00bd9 1115
    // skipped by validation.
db00bd9 1116
    let mut module = plum_wasm_codegen::WasmModule::new();
3d6f280 1117
    let ret42_type = module.addType(&[], &[wasm_encoder::ValType::I64]);
3d6f280 1118
    let ret42_idx = module.addFunction(ret42_type, &{
db00bd9 1119
        let mut body = vec![0u8]; // 0 local-decl groups
db00bd9 1120
        wasm_encoder::Instruction::I64Const(42).encode(&mut body);
db00bd9 1121
        wasm_encoder::Instruction::End.encode(&mut body);
db00bd9 1122
        body
db00bd9 1123
    });
3d6f280 1124
    let table_idx = module.addTableElement(ret42_idx);
db00bd9 1125
    assert_eq!(table_idx, 0);
db00bd9 1126
3d6f280 1127
    let main_type = module.addType(&[], &[wasm_encoder::ValType::I64]);
3d6f280 1128
    let main_idx = module.addFunction(main_type, &{
db00bd9 1129
        let mut body = vec![0u8]; // 0 local-decl groups
db00bd9 1130
        wasm_encoder::Instruction::I32Const(0).encode(&mut body); // table index operand
db00bd9 1131
        wasm_encoder::Instruction::CallIndirect { type_index: ret42_type, table_index: 0 }.encode(&mut body);
db00bd9 1132
        wasm_encoder::Instruction::End.encode(&mut body);
db00bd9 1133
        body
db00bd9 1134
    });
3d6f280 1135
    module.addExport("main", wasm_encoder::ExportKind::Func, main_idx);
db00bd9 1136
db00bd9 1137
    let bytes = module.finish();
db00bd9 1138
    let result = wasmparser::validate(&bytes);
db00bd9 1139
    assert!(result.is_ok(), "wasm validation failed: {:?}", result.err());
db00bd9 1140
5ef4579 1141
    let engine = gcEngine();
db00bd9 1142
    let wasm_module = wasmtime::Module::new(&engine, &bytes).expect("module should be loadable");
db00bd9 1143
    let mut store = wasmtime::Store::new(&engine, ());
db00bd9 1144
    let instance = wasmtime::Instance::new(&mut store, &wasm_module, &[]).expect("module should instantiate");
db00bd9 1145
    let main = instance.get_typed_func::<(), i64>(&mut store, "main").expect("main should have signature () -> i64");
db00bd9 1146
    assert_eq!(main.call(&mut store, ()).expect("main should not trap"), 42);
db00bd9 1147
}
4ba0db3 1148
4ba0db3 1149
4ba0db3 1150
#[test]
3d6f280 1151
fn nonCapturingClosurePassedAndCalledRunsCorrectly() {
4ba0db3 1152
    let src = "\
b48d3a3 1153
fun each(cb: fn(Int) -> Int) -> Int =
4ba0db3 1154
  cb(5)
4ba0db3 1155
b48d3a3 1156
fun main() -> Int =
4ba0db3 1157
  each(|v| v)
4ba0db3 1158
";
4ba0db3 1159
    let source = parse(src);
3d6f280 1160
    let bytes = compileSource(&source).expect("compile failed");
3d6f280 1161
    assert_eq!(runMain(&bytes), 5);
4ba0db3 1162
}
4ba0db3 1163
35af6cf 1164
#[test]
3d6f280 1165
fn multiLineClosureCallArgumentWithClosingParenOnLastStatementLineRunsCorrectly() {
35af6cf 1166
    // Was a documented gap: the external scanner never emitted a dedent for a
35af6cf 1167
    // multi-line closure body immediately followed by `)` on the same line as the
35af6cf 1168
    // body's last statement, so this shape didn't parse at all before.
35af6cf 1169
    let src = "\
b48d3a3 1170
fun each(cb: fn(Int) -> Int) -> Int =
35af6cf 1171
  cb(5)
35af6cf 1172
b48d3a3 1173
fun main() -> Int =
35af6cf 1174
  each(|v|
35af6cf 1175
    x = v + 1
35af6cf 1176
    x * 2)
35af6cf 1177
";
35af6cf 1178
    let source = parse(src);
3d6f280 1179
    let bytes = compileSource(&source).expect("compile failed");
3d6f280 1180
    assert_eq!(runMain(&bytes), 12);
35af6cf 1181
}
35af6cf 1182
4ba0db3 1183
#[test]
3d6f280 1184
fn capturingClosureSnapshotsValueAtCreationTimeRunsCorrectly() {
4ba0db3 1185
    let src = "\
b48d3a3 1186
fun each(cb: fn(Int) -> Int) -> Int =
4ba0db3 1187
  cb(0)
4ba0db3 1188
b48d3a3 1189
fun useClosure() -> Int =
4ba0db3 1190
  x = 10
4ba0db3 1191
  cb = |v|
4ba0db3 1192
    x + v
4ba0db3 1193
  x = 999
4ba0db3 1194
  each(cb)
4ba0db3 1195
b48d3a3 1196
fun main() -> Int =
4ba0db3 1197
  useClosure()
4ba0db3 1198
";
4ba0db3 1199
    let source = parse(src);
3d6f280 1200
    let bytes = compileSource(&source).expect("compile failed");
4ba0db3 1201
    // The closure must see x==10 (its value when the closure was created), not 999
4ba0db3 1202
    // (its value when `each(cb)` is actually called) - proving snapshot-by-value
4ba0db3 1203
    // capture, not a live/shared reference.
3d6f280 1204
    assert_eq!(runMain(&bytes), 10);
4ba0db3 1205
}
4ba0db3 1206
35af6cf 1207
#[test]
3d6f280 1208
fn closureAssignedThenCalledAtFloatTypeRunsCorrectly() {
35af6cf 1209
    // Was a documented gap: a closure created via assignment (not passed directly as
35af6cf 1210
    // a call argument) and later called at a concrete non-Int type could hit a wasm
35af6cf 1211
    // runtime trap — the checker's own closure inference gives every param a fresh
35af6cf 1212
    // TVar and never unifies it against how it's used in the body, so a genuinely
35af6cf 1213
    // Float param silently defaulted to Int, producing a `call_indirect` signature
35af6cf 1214
    // mismatch between the compiled closure body and its call site.
35af6cf 1215
    let src = "\
b48d3a3 1216
fun eachF(cb: fn(Float) -> Float) -> Float =
35af6cf 1217
  cb(0.0)
35af6cf 1218
b48d3a3 1219
fun useClosure() -> Float =
35af6cf 1220
  offset = 2.5
35af6cf 1221
  cb = |v|
35af6cf 1222
    offset + v
35af6cf 1223
  eachF(cb)
35af6cf 1224
b48d3a3 1225
fun main() -> Float =
35af6cf 1226
  useClosure()
35af6cf 1227
";
35af6cf 1228
    let source = parse(src);
3d6f280 1229
    let bytes = compileSource(&source).expect("compile failed");
3d6f280 1230
    assert_eq!(runMainF64(&bytes), 2.5);
35af6cf 1231
}
35af6cf 1232
35af6cf 1233
#[test]
3d6f280 1234
fn closureAssignedThenCalledAtClassTypeRunsCorrectly() {
35af6cf 1235
    let src = "\
35af6cf 1236
type Cat =
35af6cf 1237
  age: Int
35af6cf 1238
b48d3a3 1239
fun eachCat(cb: fn(Cat) -> Int) -> Int =
35af6cf 1240
  cb(Cat(age: 7))
35af6cf 1241
b48d3a3 1242
fun useClosure() -> Int =
35af6cf 1243
  cb = |c|
35af6cf 1244
    c.age
35af6cf 1245
  eachCat(cb)
35af6cf 1246
b48d3a3 1247
fun main() -> Int =
35af6cf 1248
  useClosure()
35af6cf 1249
";
35af6cf 1250
    let source = parse(src);
3d6f280 1251
    let bytes = compileSource(&source).expect("compile failed");
3d6f280 1252
    assert_eq!(runMain(&bytes), 7);
35af6cf 1253
}
35af6cf 1254
4ba0db3 1255
#[test]
3d6f280 1256
fn closurePassedThroughAlreadyGenericHigherOrderFunctionRunsCorrectly() {
4ba0db3 1257
    let src = "\
b48d3a3 1258
fun identity(value: T) -> T =
4ba0db3 1259
  value
4ba0db3 1260
b48d3a3 1261
fun each(cb: fn(Int) -> Int) -> Int =
4ba0db3 1262
  cb(identity(7))
4ba0db3 1263
b48d3a3 1264
fun main() -> Int =
4ba0db3 1265
  each(|v| v * 2)
4ba0db3 1266
";
4ba0db3 1267
    let source = parse(src);
3d6f280 1268
    let bytes = compileSource(&source).expect("compile failed");
3d6f280 1269
    assert_eq!(runMain(&bytes), 14);
4ba0db3 1270
}
35af6cf 1271
35af6cf 1272
#[test]
3d6f280 1273
fn nestedClosureLiteralRunsCorrectly() {
35af6cf 1274
    // Was a documented gap: a closure literal nested inside another closure's body
35af6cf 1275
    // produced a clear compile error (the discovery pre-pass never recursed into a
35af6cf 1276
    // closure's own body to find closures nested inside it). Here `inner` (nested
35af6cf 1277
    // inside `outer`'s body) needs `offset` — a name from `useNested`'s scope, two
35af6cf 1278
    // levels up from `inner` itself, and not referenced by `outer` directly — which
35af6cf 1279
    // exercises the multi-level capture chain: `outer` must itself capture `offset`
35af6cf 1280
    // purely because `inner` needs it, not because `outer` uses it.
35af6cf 1281
    let src = "\
b48d3a3 1282
fun each(cb: fn(Int) -> Int) -> Int =
35af6cf 1283
  cb(5)
35af6cf 1284
b48d3a3 1285
fun useNested() -> Int =
35af6cf 1286
  offset = 100
35af6cf 1287
  outer = |v|
35af6cf 1288
    inner = |w|
35af6cf 1289
      w + offset
35af6cf 1290
    inner(v)
35af6cf 1291
  each(outer)
35af6cf 1292
b48d3a3 1293
fun main() -> Int =
35af6cf 1294
  useNested()
35af6cf 1295
";
35af6cf 1296
    let source = parse(src);
3d6f280 1297
    let bytes = compileSource(&source).expect("compile failed");
3d6f280 1298
    assert_eq!(runMain(&bytes), 105);
35af6cf 1299
}
35af6cf 1300
35af6cf 1301
#[test]
3d6f280 1302
fn nestedClosureLiteralPassedDirectlyAsCallArgumentRunsCorrectly() {
35af6cf 1303
    let src = "\
b48d3a3 1304
fun each(cb: fn(Int) -> Int) -> Int =
35af6cf 1305
  cb(5)
35af6cf 1306
b48d3a3 1307
fun main() -> Int =
35af6cf 1308
  each(|v|
35af6cf 1309
    each(|w| w + v))
35af6cf 1310
";
35af6cf 1311
    let source = parse(src);
3d6f280 1312
    let bytes = compileSource(&source).expect("compile failed");
3d6f280 1313
    assert_eq!(runMain(&bytes), 10);
35af6cf 1314
}
35af6cf 1315
35af6cf 1316
#[test]
3d6f280 1317
fn namedFunctionUsedAsAClosureTypedValueRunsCorrectly() {
35af6cf 1318
    // A plain top-level named function (not a `|params| body` closure literal) used
35af6cf 1319
    // wherever a `fn(...)`-typed value is expected — no closure literal involved at
35af6cf 1320
    // the call site at all.
35af6cf 1321
    let src = "\
b48d3a3 1322
fun double(x: Int) -> Int =
35af6cf 1323
  x * 2
35af6cf 1324
b48d3a3 1325
fun each(cb: fn(Int) -> Int) -> Int =
35af6cf 1326
  cb(21)
35af6cf 1327
b48d3a3 1328
fun main() -> Int =
35af6cf 1329
  each(double)
35af6cf 1330
";
35af6cf 1331
    let source = parse(src);
3d6f280 1332
    let bytes = compileSource(&source).expect("compile failed");
3d6f280 1333
    assert_eq!(runMain(&bytes), 42);
35af6cf 1334
}
35af6cf 1335
35af6cf 1336
#[test]
3d6f280 1337
fn namedFunctionUsedAsAValueAssignedThenCalledRunsCorrectly() {
35af6cf 1338
    let src = "\
b48d3a3 1339
fun double(x: Int) -> Int =
35af6cf 1340
  x * 2
35af6cf 1341
b48d3a3 1342
fun main() -> Int =
35af6cf 1343
  f = double
35af6cf 1344
  f(21)
35af6cf 1345
";
35af6cf 1346
    let source = parse(src);
3d6f280 1347
    let bytes = compileSource(&source).expect("compile failed");
3d6f280 1348
    assert_eq!(runMain(&bytes), 42);
35af6cf 1349
}
35af6cf 1350
35af6cf 1351
#[test]
3d6f280 1352
fn namedFunctionUsedAsAValueAlongsideAClosureAtTheSameCallTypeRunsCorrectly() {
35af6cf 1353
    // Proves the trampoline shares the same call_indirect type as an ordinary
35af6cf 1354
    // closure of the same signature (both must resolve to the same wasm function
35af6cf 1355
    // type, since both flow through the exact same `cb(...)` call site).
35af6cf 1356
    let src = "\
b48d3a3 1357
fun double(x: Int) -> Int =
35af6cf 1358
  x * 2
35af6cf 1359
b48d3a3 1360
fun each(cb: fn(Int) -> Int) -> Int =
35af6cf 1361
  cb(10)
35af6cf 1362
b48d3a3 1363
fun main() -> Int =
35af6cf 1364
  each(double) + each(|v| v + 1)
35af6cf 1365
";
35af6cf 1366
    let source = parse(src);
3d6f280 1367
    let bytes = compileSource(&source).expect("compile failed");
3d6f280 1368
    assert_eq!(runMain(&bytes), 31);
35af6cf 1369
}
35af6cf 1370
35af6cf 1371
#[test]
3d6f280 1372
fn multiSubjectMatchWithEnumTagsRunsCorrectly() {
35af6cf 1373
    // Mirrors libs/std/bool.plum's `and`/`or`: `match self, o` against two Bool
35af6cf 1374
    // subjects, each case naming a tag pattern per position.
35af6cf 1375
    let src = "\
b48d3a3 1376
fun and(a: Bool, b: Bool) -> Bool =
35af6cf 1377
  match a, b
35af6cf 1378
    True, True => True
35af6cf 1379
    True, False => False
35af6cf 1380
    False, True => False
35af6cf 1381
    False, False => False
35af6cf 1382
b48d3a3 1383
fun main() -> Int =
35af6cf 1384
  x = and(True, True)
35af6cf 1385
  y = and(True, False)
35af6cf 1386
  match x, y
35af6cf 1387
    True, False => 1
35af6cf 1388
    _, _ => 0
35af6cf 1389
";
35af6cf 1390
    let source = parse(src);
3d6f280 1391
    let bytes = compileSource(&source).expect("compile failed");
3d6f280 1392
    assert_eq!(runMain(&bytes), 1);
35af6cf 1393
}
35af6cf 1394
35af6cf 1395
#[test]
3d6f280 1396
fn multiSubjectMatchFallsThroughToNextCaseWhenOnlyFirstPositionMatches() {
35af6cf 1397
    // The first case's position-0 pattern (`1`) matches, but position-1 (`1`)
35af6cf 1398
    // doesn't (b is 2) — codegen must fall through to the *next case* (trying its
35af6cf 1399
    // own position 0 again), not just "move on" within the first case.
35af6cf 1400
    let src = "\
b48d3a3 1401
fun classify(a: Int, b: Int) -> Int =
35af6cf 1402
  match a, b
35af6cf 1403
    1, 1 => 100
35af6cf 1404
    1, 2 => 200
35af6cf 1405
    _, _ => 0
35af6cf 1406
b48d3a3 1407
fun main() -> Int =
35af6cf 1408
  classify(1, 2)
35af6cf 1409
";
35af6cf 1410
    let source = parse(src);
3d6f280 1411
    let bytes = compileSource(&source).expect("compile failed");
3d6f280 1412
    assert_eq!(runMain(&bytes), 200);
35af6cf 1413
}
35af6cf 1414
35af6cf 1415
#[test]
3d6f280 1416
fn multiSubjectMatchWithBindingAndWildcardRunsCorrectly() {
35af6cf 1417
    let src = "\
b48d3a3 1418
fun combine(a: Int, b: Int) -> Int =
35af6cf 1419
  match a, b
35af6cf 1420
    0, y => y
35af6cf 1421
    x, 0 => x
35af6cf 1422
    x, y => x + y
35af6cf 1423
b48d3a3 1424
fun main() -> Int =
35af6cf 1425
  combine(3, 4)
35af6cf 1426
";
35af6cf 1427
    let source = parse(src);
3d6f280 1428
    let bytes = compileSource(&source).expect("compile failed");
3d6f280 1429
    assert_eq!(runMain(&bytes), 7);
35af6cf 1430
}
35af6cf 1431
35af6cf 1432
#[test]
3d6f280 1433
fn multiSubjectMatchWithGenericEnumVariantRunsCorrectly() {
35af6cf 1434
    // Exercises the monomorphize.rs fix: each subject's own generic-enum
35af6cf 1435
    // specialization (`Some$Int`) must be mangled independently per position.
35af6cf 1436
    let src = "\
35af6cf 1437
enum Option =
d958617 1438
  | Some[T]
35af6cf 1439
  | None
35af6cf 1440
b48d3a3 1441
fun both(a: Option, b: Option) -> Int =
35af6cf 1442
  match a, b
35af6cf 1443
    Some(x), Some(y) => x + y
35af6cf 1444
    _, _ => 0
35af6cf 1445
b48d3a3 1446
fun main() -> Int =
35af6cf 1447
  both(Some(3), Some(4))
35af6cf 1448
";
35af6cf 1449
    let source = parse(src);
3d6f280 1450
    let bytes = compileSource(&source).expect("compile failed");
3d6f280 1451
    assert_eq!(runMain(&bytes), 7);
35af6cf 1452
}
01f9be3 1453
01f9be3 1454
#[test]
3d6f280 1455
fn fieldAssignmentTargetRunsCorrectly() {
01f9be3 1456
    let src = "\
01f9be3 1457
type Counter =
01f9be3 1458
  value: Int
01f9be3 1459
805d96d 1460
  fun bump() =
805d96d 1461
    self.value = self.value + 1
01f9be3 1462
b48d3a3 1463
fun main() -> Int =
01f9be3 1464
  c = Counter(value: 41)
01f9be3 1465
  c.bump()
01f9be3 1466
  c.value
01f9be3 1467
";
01f9be3 1468
    let source = parse(src);
3d6f280 1469
    let bytes = compileSource(&source).expect("compile failed");
3d6f280 1470
    assert_eq!(runMain(&bytes), 42);
01f9be3 1471
}
01f9be3 1472
01f9be3 1473
#[test]
3d6f280 1474
fn chainedFieldAssignmentTargetRunsCorrectly() {
01f9be3 1475
    let src = "\
01f9be3 1476
type Inner =
01f9be3 1477
  value: Int
01f9be3 1478
01f9be3 1479
type Outer =
01f9be3 1480
  inner: Inner
01f9be3 1481
805d96d 1482
  fun bump() =
805d96d 1483
    self.inner.value = self.inner.value + 1
01f9be3 1484
b48d3a3 1485
fun main() -> Int =
01f9be3 1486
  o = Outer(inner: Inner(value: 9))
01f9be3 1487
  o.bump()
01f9be3 1488
  o.inner.value
01f9be3 1489
";
01f9be3 1490
    let source = parse(src);
3d6f280 1491
    let bytes = compileSource(&source).expect("compile failed");
3d6f280 1492
    assert_eq!(runMain(&bytes), 10);
01f9be3 1493
}
01f9be3 1494
01f9be3 1495
#[test]
3d6f280 1496
fn mixedMultiAssignWithFieldTargetRunsCorrectly() {
01f9be3 1497
    let src = "\
01f9be3 1498
type Counter =
01f9be3 1499
  value: Int
01f9be3 1500
b48d3a3 1501
fun main() -> Int =
01f9be3 1502
  c = Counter(value: 5)
01f9be3 1503
  a, c.value = 100, 7
01f9be3 1504
  a + c.value
01f9be3 1505
";
01f9be3 1506
    let source = parse(src);
3d6f280 1507
    let bytes = compileSource(&source).expect("compile failed");
3d6f280 1508
    assert_eq!(runMain(&bytes), 107);
01f9be3 1509
}
d6b1f95 1510
d6b1f95 1511
#[test]
3d6f280 1512
fn variadicCallWithVaryingTrailingArgCountsRunsCorrectly() {
d6b1f95 1513
    let src = "\
b48d3a3 1514
fun combine(prefix: Int, rest: ...Int) -> Int =
d6b1f95 1515
  prefix
d6b1f95 1516
b48d3a3 1517
fun main() -> Int =
d6b1f95 1518
  a = combine(10)
d6b1f95 1519
  b = combine(20, 1)
d6b1f95 1520
  c = combine(30, 1, 2, 3)
d6b1f95 1521
  a + b + c
d6b1f95 1522
";
d6b1f95 1523
    let source = parse(src);
3d6f280 1524
    let bytes = compileSource(&source).expect("compile failed");
3d6f280 1525
    assert_eq!(runMain(&bytes), 60);
d6b1f95 1526
}
da1c377 1527
da1c377 1528
#[test]
3d6f280 1529
fn sumAllVariadicIntRunsCorrectly() {
da1c377 1530
    let src = "\
b48d3a3 1531
fun sumAll(nums: ...Int) -> Int =
da1c377 1532
  total = 0
0000000 1533
  for v := range nums
da1c377 1534
    total = total + v
da1c377 1535
  total
da1c377 1536
b48d3a3 1537
fun main() -> Int =
da1c377 1538
  sumAll(1, 2, 3, 4)
da1c377 1539
";
da1c377 1540
    let source = parse(src);
3d6f280 1541
    let bytes = compileSource(&source).expect("compile failed");
3d6f280 1542
    assert_eq!(runMain(&bytes), 10);
da1c377 1543
}
da1c377 1544
da1c377 1545
#[test]
3d6f280 1546
fn sumAllVariadicIntWithZeroArgsRunsCorrectly() {
da1c377 1547
    let src = "\
b48d3a3 1548
fun sumAll(nums: ...Int) -> Int =
da1c377 1549
  total = 0
0000000 1550
  for v := range nums
da1c377 1551
    total = total + v
da1c377 1552
  total
da1c377 1553
b48d3a3 1554
fun main() -> Int =
da1c377 1555
  sumAll()
da1c377 1556
";
da1c377 1557
    let source = parse(src);
3d6f280 1558
    let bytes = compileSource(&source).expect("compile failed");
3d6f280 1559
    assert_eq!(runMain(&bytes), 0);
da1c377 1560
}
5ef4579 1561
5ef4579 1562
#[test]
5ef4579 1563
fn wasmtimeGcConfigCanRunAHandEncodedGcModule() {
5ef4579 1564
    // Throwaway empirical check (plan Task 1, Step 1): confirm wasmtime 28's GC
5ef4579 1565
    // support actually works end to end before building a type-emitter on top of
5ef4579 1566
    // it. Hand-encodes the smallest possible module with one GC struct type and
5ef4579 1567
    // one function that does struct.new_default and returns it, bypassing plum
5ef4579 1568
    // entirely, so a failure here is unambiguously about wasmtime/wasm-encoder,
5ef4579 1569
    // not about anything plum-specific.
5ef4579 1570
    use wasm_encoder::*;
5ef4579 1571
5ef4579 1572
    let mut module = Module::new();
5ef4579 1573
5ef4579 1574
    let mut types = TypeSection::new();
5ef4579 1575
    // type 0: struct { i32 }
5ef4579 1576
    types.ty().struct_(vec![FieldType { element_type: StorageType::Val(ValType::I32), mutable: true }]);
5ef4579 1577
    // type 1: () -> (ref null 0)
5ef4579 1578
    let struct_ref = ValType::Ref(RefType { nullable: true, heap_type: HeapType::Concrete(0) });
5ef4579 1579
    types.ty().function(vec![], vec![struct_ref]);
5ef4579 1580
    module.section(&types);
5ef4579 1581
5ef4579 1582
    let mut funcs = FunctionSection::new();
5ef4579 1583
    funcs.function(1);
5ef4579 1584
    module.section(&funcs);
5ef4579 1585
5ef4579 1586
    let mut exports = ExportSection::new();
5ef4579 1587
    exports.export("main", ExportKind::Func, 0);
5ef4579 1588
    module.section(&exports);
5ef4579 1589
5ef4579 1590
    let mut code = CodeSection::new();
5ef4579 1591
    let mut f = Function::new(vec![]);
5ef4579 1592
    f.instruction(&Instruction::StructNewDefault(0));
5ef4579 1593
    f.instruction(&Instruction::End);
5ef4579 1594
    code.function(&f);
5ef4579 1595
    module.section(&code);
5ef4579 1596
5ef4579 1597
    let bytes = module.finish();
5ef4579 1598
5ef4579 1599
    let mut config = wasmtime::Config::new();
5ef4579 1600
    config.wasm_gc(true);
5ef4579 1601
    config.wasm_function_references(true);
5ef4579 1602
    let engine = wasmtime::Engine::new(&config).expect("engine with GC config should construct");
5ef4579 1603
5ef4579 1604
    let wasm_module = wasmtime::Module::new(&engine, &bytes).expect("hand-encoded GC module should be loadable");
5ef4579 1605
    let mut store = wasmtime::Store::new(&engine, ());
5ef4579 1606
    let instance = wasmtime::Instance::new(&mut store, &wasm_module, &[]).expect("module should instantiate");
5ef4579 1607
    let main = instance
5ef4579 1608
        .get_func(&mut store, "main")
5ef4579 1609
        .expect("main should be exported");
5ef4579 1610
    let mut results = [wasmtime::Val::I32(0)];
5ef4579 1611
    main.call(&mut store, &[], &mut results).expect("main should not trap");
5ef4579 1612
}
5ef4579 1613
5ef4579 1614
5ef4579 1615
#[test]
5ef4579 1616
fn wasmtimeGcConfigSupportsSubtypingRefTestAndNullableSelfReferentialFields() {
5ef4579 1617
    // Deeper empirical check: enum-variant subtyping (abstract supertype + concrete
5ef4579 1618
    // subtypes in one `rec` group), ref.test-based dispatch, ref.cast to narrow to a
5ef4579 1619
    // subtype, and a nullable field that references the struct's OWN type (the
5ef4579 1620
    // Node.next: Option[Node] shape List needs) — all in one hand-encoded module,
5ef4579 1621
    // bypassing plum entirely.
5ef4579 1622
    use wasm_encoder::*;
5ef4579 1623
5ef4579 1624
    let mut module = Module::new();
5ef4579 1625
    let mut types = TypeSection::new();
5ef4579 1626
5ef4579 1627
    // rec group: type 0 = abstract enum supertype (empty struct, non-final so it can
5ef4579 1628
    // be subtyped); type 1 = concrete "Some"-like subtype with one i32 payload field;
5ef4579 1629
    // type 2 = concrete "None"-like subtype (empty, no payload).
5ef4579 1630
    types.ty().rec(vec![
5ef4579 1631
        SubType {
5ef4579 1632
            is_final: false,
5ef4579 1633
            supertype_idx: None,
5ef4579 1634
            composite_type: CompositeType { inner: CompositeInnerType::Struct(StructType { fields: vec![].into() }), shared: false },
5ef4579 1635
        },
5ef4579 1636
        SubType {
5ef4579 1637
            is_final: true,
5ef4579 1638
            supertype_idx: Some(0),
5ef4579 1639
            composite_type: CompositeType {
5ef4579 1640
                inner: CompositeInnerType::Struct(StructType { fields: vec![
5ef4579 1641
                    FieldType { element_type: StorageType::Val(ValType::I32), mutable: false },
5ef4579 1642
                ].into() }),
5ef4579 1643
                shared: false,
5ef4579 1644
            },
5ef4579 1645
        },
5ef4579 1646
        SubType {
5ef4579 1647
            is_final: true,
5ef4579 1648
            supertype_idx: Some(0),
5ef4579 1649
            composite_type: CompositeType { inner: CompositeInnerType::Struct(StructType { fields: vec![].into() }), shared: false },
5ef4579 1650
        },
5ef4579 1651
    ]);
5ef4579 1652
5ef4579 1653
    // type 3: a self-referential Node struct — { value: i32, next: ref null $Node }.
5ef4579 1654
    // Must be declared in its own rec group (or alone) referencing its own index (3)
5ef4579 1655
    // for the nullable self-reference to resolve.
5ef4579 1656
    types.ty().rec(vec![
5ef4579 1657
        SubType {
5ef4579 1658
            is_final: true,
5ef4579 1659
            supertype_idx: None,
5ef4579 1660
            composite_type: CompositeType {
5ef4579 1661
                inner: CompositeInnerType::Struct(StructType { fields: vec![
5ef4579 1662
                    FieldType { element_type: StorageType::Val(ValType::I32), mutable: false },
5ef4579 1663
                    FieldType { element_type: StorageType::Val(ValType::Ref(RefType { nullable: true, heap_type: HeapType::Concrete(3) })), mutable: true },
5ef4579 1664
                ].into() }),
5ef4579 1665
                shared: false,
5ef4579 1666
            },
5ef4579 1667
        },
5ef4579 1668
    ]);
5ef4579 1669
5ef4579 1670
    // type 4: () -> i32 — constructs a "Some"-like subtype (type 1) holding 42,
5ef4579 1671
    // stores it as the supertype (type 0), ref.tests it against type 1, then
5ef4579 1672
    // ref.casts and struct.gets the payload back out. Also builds a 2-node linked
5ef4579 1673
    // list (type 3) and confirms unlinking (overwriting `next` with ref.null) and
5ef4579 1674
    // reading back the remaining node's value both work.
5ef4579 1675
    let super_ref = ValType::Ref(RefType { nullable: true, heap_type: HeapType::Concrete(0) });
5ef4579 1676
    types.ty().function(vec![], vec![ValType::I32]);
5ef4579 1677
    module.section(&types);
5ef4579 1678
5ef4579 1679
    let mut funcs = FunctionSection::new();
5ef4579 1680
    funcs.function(4);
5ef4579 1681
    module.section(&funcs);
5ef4579 1682
5ef4579 1683
    let mut exports = ExportSection::new();
5ef4579 1684
    exports.export("main", ExportKind::Func, 0);
5ef4579 1685
    module.section(&exports);
5ef4579 1686
5ef4579 1687
    let mut code = CodeSection::new();
5ef4579 1688
    let mut f = Function::new(vec![(1, super_ref.clone()), (1, ValType::Ref(RefType { nullable: true, heap_type: HeapType::Concrete(3) }))]);
5ef4579 1689
    let locals_super = 0u32;
5ef4579 1690
    let locals_node = 1u32;
5ef4579 1691
5ef4579 1692
    // local_super = Some(42) (as the supertype)
5ef4579 1693
    f.instruction(&Instruction::I32Const(42));
5ef4579 1694
    f.instruction(&Instruction::StructNew(1));
5ef4579 1695
    f.instruction(&Instruction::LocalSet(locals_super));
5ef4579 1696
5ef4579 1697
    // local_node = Node { value: 1, next: null }
5ef4579 1698
    f.instruction(&Instruction::I32Const(1));
5ef4579 1699
    f.instruction(&Instruction::RefNull(HeapType::Concrete(3)));
5ef4579 1700
    f.instruction(&Instruction::StructNew(3));
5ef4579 1701
    f.instruction(&Instruction::LocalSet(locals_node));
5ef4579 1702
5ef4579 1703
    // if ref.test(local_super, type 1) { result = ref.cast(local_super, type1).field0 } else { result = -1 }
5ef4579 1704
    f.instruction(&Instruction::LocalGet(locals_super));
5ef4579 1705
    f.instruction(&Instruction::RefTestNonNull(HeapType::Concrete(1)));
5ef4579 1706
    f.instruction(&Instruction::If(BlockType::Result(ValType::I32)));
5ef4579 1707
    f.instruction(&Instruction::LocalGet(locals_super));
5ef4579 1708
    f.instruction(&Instruction::RefCastNonNull(HeapType::Concrete(1)));
5ef4579 1709
    f.instruction(&Instruction::StructGet { struct_type_index: 1, field_index: 0 });
5ef4579 1710
    f.instruction(&Instruction::Else);
5ef4579 1711
    f.instruction(&Instruction::I32Const(-1));
5ef4579 1712
    f.instruction(&Instruction::End);
5ef4579 1713
5ef4579 1714
    // unlink: local_node.next = ref.null (already null, but exercise the store path)
5ef4579 1715
    f.instruction(&Instruction::LocalGet(locals_node));
5ef4579 1716
    f.instruction(&Instruction::RefNull(HeapType::Concrete(3)));
5ef4579 1717
    f.instruction(&Instruction::StructSet { struct_type_index: 3, field_index: 1 });
5ef4579 1718
5ef4579 1719
    // add local_node.value to the ref.test result and return
5ef4579 1720
    f.instruction(&Instruction::LocalGet(locals_node));
5ef4579 1721
    f.instruction(&Instruction::StructGet { struct_type_index: 3, field_index: 0 });
5ef4579 1722
    f.instruction(&Instruction::I32Add);
5ef4579 1723
    f.instruction(&Instruction::End);
5ef4579 1724
    code.function(&f);
5ef4579 1725
    module.section(&code);
5ef4579 1726
5ef4579 1727
    let bytes = module.finish();
5ef4579 1728
5ef4579 1729
    let mut config = wasmtime::Config::new();
5ef4579 1730
    config.wasm_gc(true);
5ef4579 1731
    config.wasm_function_references(true);
5ef4579 1732
    let engine = wasmtime::Engine::new(&config).expect("engine with GC config should construct");
5ef4579 1733
5ef4579 1734
    let wasm_module = wasmtime::Module::new(&engine, &bytes).unwrap_or_else(|e| panic!("module should be loadable: {e}"));
5ef4579 1735
    let mut store = wasmtime::Store::new(&engine, ());
5ef4579 1736
    let instance = wasmtime::Instance::new(&mut store, &wasm_module, &[]).expect("module should instantiate");
5ef4579 1737
    let main = instance
5ef4579 1738
        .get_typed_func::<(), i32>(&mut store, "main")
5ef4579 1739
        .expect("main should have signature () -> i32");
5ef4579 1740
    let result = main.call(&mut store, ()).expect("main should not trap");
5ef4579 1741
    assert_eq!(result, 43, "expected ref.test/ref.cast payload (42) + node.value (1) = 43");
5ef4579 1742
}
5ef4579 1743
5ef4579 1744
5ef4579 1745
#[test]
5ef4579 1746
fn wasmtimeGcConfigAllowsStructNewInGlobalConstExpr() {
5ef4579 1747
    // Decision 2 of the wasm-gc migration plan pre-allocates payload-free enum
5ef4579 1748
    // variants (True/False/None/...) once as globals. Confirm a global's
5ef4579 1749
    // initializer expression can directly use struct.new (not just i32.const/
5ef4579 1750
    // ref.null), or the plan needs a `start` function fallback instead.
5ef4579 1751
    use wasm_encoder::*;
5ef4579 1752
5ef4579 1753
    let mut module = Module::new();
5ef4579 1754
    let mut types = TypeSection::new();
5ef4579 1755
    types.ty().struct_(vec![FieldType { element_type: StorageType::Val(ValType::I32), mutable: false }]);
5ef4579 1756
    types.ty().function(vec![], vec![ValType::I32]);
5ef4579 1757
    module.section(&types);
5ef4579 1758
5ef4579 1759
    let mut funcs = FunctionSection::new();
5ef4579 1760
    funcs.function(1);
5ef4579 1761
    module.section(&funcs);
5ef4579 1762
5ef4579 1763
    let mut globals = GlobalSection::new();
5ef4579 1764
    let struct_ref_ty = ValType::Ref(RefType { nullable: true, heap_type: HeapType::Concrete(0) });
5ef4579 1765
    let mut init = Vec::new();
5ef4579 1766
    Instruction::I32Const(7).encode(&mut init);
5ef4579 1767
    Instruction::StructNew(0).encode(&mut init);
5ef4579 1768
    Instruction::End.encode(&mut init);
5ef4579 1769
    globals.global(
5ef4579 1770
        GlobalType { val_type: struct_ref_ty, mutable: false, shared: false },
5ef4579 1771
        &ConstExpr::raw(init),
5ef4579 1772
    );
5ef4579 1773
    module.section(&globals);
5ef4579 1774
5ef4579 1775
    let mut exports = ExportSection::new();
5ef4579 1776
    exports.export("main", ExportKind::Func, 0);
5ef4579 1777
    module.section(&exports);
5ef4579 1778
5ef4579 1779
    let mut code = CodeSection::new();
5ef4579 1780
    let mut f = Function::new(vec![]);
5ef4579 1781
    f.instruction(&Instruction::GlobalGet(0));
5ef4579 1782
    f.instruction(&Instruction::StructGet { struct_type_index: 0, field_index: 0 });
5ef4579 1783
    f.instruction(&Instruction::End);
5ef4579 1784
    code.function(&f);
5ef4579 1785
    module.section(&code);
5ef4579 1786
5ef4579 1787
    let bytes = module.finish();
5ef4579 1788
5ef4579 1789
    let mut config = wasmtime::Config::new();
5ef4579 1790
    config.wasm_gc(true);
5ef4579 1791
    config.wasm_function_references(true);
5ef4579 1792
    let engine = wasmtime::Engine::new(&config).expect("engine with GC config should construct");
5ef4579 1793
5ef4579 1794
    let wasm_module = match wasmtime::Module::new(&engine, &bytes) {
5ef4579 1795
        Ok(m) => m,
5ef4579 1796
        Err(e) => {
5ef4579 1797
            println!("struct.new in a global const-expr is NOT supported by this wasmtime/config: {e}");
5ef4579 1798
            println!("plan implication: Task 2 Step 2 (2a) must use a `start` function instead of a const global initializer.");
5ef4579 1799
            return;
5ef4579 1800
        }
5ef4579 1801
    };
5ef4579 1802
    let mut store = wasmtime::Store::new(&engine, ());
5ef4579 1803
    let instance = wasmtime::Instance::new(&mut store, &wasm_module, &[]).expect("module should instantiate");
5ef4579 1804
    let main = instance.get_typed_func::<(), i32>(&mut store, "main").expect("main should have signature () -> i32");
5ef4579 1805
    let result = main.call(&mut store, ()).expect("main should not trap");
5ef4579 1806
    assert_eq!(result, 7);
5ef4579 1807
    println!("struct.new IS supported directly in a global const-expr initializer.");
5ef4579 1808
}
5ef4579 1809
5ef4579 1810
5ef4579 1811
#[test]
5ef4579 1812
fn wasmtimeGcConfigSupportsStartFunctionInitializingGcGlobals() {
5ef4579 1813
    // Follow-up to the previous test: struct.new isn't allowed in a global
5ef4579 1814
    // const-expr, so confirm the `start` function fallback works instead —
5ef4579 1815
    // a mutable global initialized to ref.null, populated by struct.new inside
5ef4579 1816
    // a `start` function that runs once at instantiation before any export.
5ef4579 1817
    use wasm_encoder::*;
5ef4579 1818
5ef4579 1819
    let mut module = Module::new();
5ef4579 1820
    let mut types = TypeSection::new();
5ef4579 1821
    types.ty().struct_(vec![FieldType { element_type: StorageType::Val(ValType::I32), mutable: false }]);
5ef4579 1822
    types.ty().function(vec![], vec![]); // start fn: () -> ()
5ef4579 1823
    types.ty().function(vec![], vec![ValType::I32]); // main: () -> i32
5ef4579 1824
    module.section(&types);
5ef4579 1825
5ef4579 1826
    let mut funcs = FunctionSection::new();
5ef4579 1827
    funcs.function(1); // func 0: start
5ef4579 1828
    funcs.function(2); // func 1: main
5ef4579 1829
    module.section(&funcs);
5ef4579 1830
5ef4579 1831
    let mut globals = GlobalSection::new();
5ef4579 1832
    let struct_ref_ty = ValType::Ref(RefType { nullable: true, heap_type: HeapType::Concrete(0) });
5ef4579 1833
    globals.global(
5ef4579 1834
        GlobalType { val_type: struct_ref_ty.clone(), mutable: true, shared: false },
5ef4579 1835
        &ConstExpr::ref_null(HeapType::Concrete(0)),
5ef4579 1836
    );
5ef4579 1837
    module.section(&globals);
5ef4579 1838
5ef4579 1839
    let mut exports = ExportSection::new();
5ef4579 1840
    exports.export("main", ExportKind::Func, 1);
5ef4579 1841
    module.section(&exports);
5ef4579 1842
5ef4579 1843
    let start = StartSection { function_index: 0 };
5ef4579 1844
    module.section(&start);
5ef4579 1845
5ef4579 1846
    let mut code = CodeSection::new();
5ef4579 1847
    let mut start_fn = Function::new(vec![]);
5ef4579 1848
    start_fn.instruction(&Instruction::I32Const(99));
5ef4579 1849
    start_fn.instruction(&Instruction::StructNew(0));
5ef4579 1850
    start_fn.instruction(&Instruction::GlobalSet(0));
5ef4579 1851
    start_fn.instruction(&Instruction::End);
5ef4579 1852
    code.function(&start_fn);
5ef4579 1853
5ef4579 1854
    let mut main_fn = Function::new(vec![]);
5ef4579 1855
    main_fn.instruction(&Instruction::GlobalGet(0));
5ef4579 1856
    main_fn.instruction(&Instruction::StructGet { struct_type_index: 0, field_index: 0 });
5ef4579 1857
    main_fn.instruction(&Instruction::End);
5ef4579 1858
    code.function(&main_fn);
5ef4579 1859
    module.section(&code);
5ef4579 1860
5ef4579 1861
    let bytes = module.finish();
5ef4579 1862
5ef4579 1863
    let mut config = wasmtime::Config::new();
5ef4579 1864
    config.wasm_gc(true);
5ef4579 1865
    config.wasm_function_references(true);
5ef4579 1866
    let engine = wasmtime::Engine::new(&config).expect("engine with GC config should construct");
5ef4579 1867
5ef4579 1868
    let wasm_module = wasmtime::Module::new(&engine, &bytes).unwrap_or_else(|e| panic!("module should be loadable: {e}"));
5ef4579 1869
    let mut store = wasmtime::Store::new(&engine, ());
5ef4579 1870
    let instance = wasmtime::Instance::new(&mut store, &wasm_module, &[]).expect("module should instantiate (start fn should run automatically)");
5ef4579 1871
    let main = instance.get_typed_func::<(), i32>(&mut store, "main").expect("main should have signature () -> i32");
5ef4579 1872
    let result = main.call(&mut store, ()).expect("main should not trap");
5ef4579 1873
    assert_eq!(result, 99, "start fn should have populated the global before main ran");
5ef4579 1874
}
5ef4579 1875
5ef4579 1876
5ef4579 1877
#[test]
5ef4579 1878
fn wasmtimeGcConfigSupportsArrayNewDataFromPassiveSegmentWithNoMemorySection() {
5ef4579 1879
    // Decision 4 of the wasm-gc migration plan: static string data lives in a
5ef4579 1880
    // PASSIVE data segment (no active memory offset), consumed via array.new_data
5ef4579 1881
    // — confirming this needs no `memory` section in the module at all, which is
5ef4579 1882
    // what lets the whole memory section disappear once bump allocation is retired.
5ef4579 1883
    use wasm_encoder::*;
5ef4579 1884
5ef4579 1885
    let mut module = Module::new();
5ef4579 1886
    let mut types = TypeSection::new();
5ef4579 1887
    types.ty().array(&StorageType::I8, false);
5ef4579 1888
    let arr_ref = ValType::Ref(RefType { nullable: true, heap_type: HeapType::Concrete(0) });
5ef4579 1889
    types.ty().function(vec![], vec![ValType::I32]);
5ef4579 1890
    module.section(&types);
5ef4579 1891
5ef4579 1892
    let mut funcs = FunctionSection::new();
5ef4579 1893
    funcs.function(1);
5ef4579 1894
    module.section(&funcs);
5ef4579 1895
5ef4579 1896
    let mut exports = ExportSection::new();
5ef4579 1897
    exports.export("main", ExportKind::Func, 0);
5ef4579 1898
    module.section(&exports);
5ef4579 1899
5ef4579 1900
    // Required whenever the module uses array.new_data/memory.init/data.drop —
5ef4579 1901
    // the validator needs the passive-segment count before the code section.
5ef4579 1902
    module.section(&DataCountSection { count: 1 });
5ef4579 1903
5ef4579 1904
    let mut code = CodeSection::new();
5ef4579 1905
    let mut f = Function::new(vec![(1, arr_ref)]);
5ef4579 1906
    // local 0 = array.new_data(type 0, data segment 0) with offset=0, len=5 ("hello")
5ef4579 1907
    f.instruction(&Instruction::I32Const(0)); // data offset
5ef4579 1908
    f.instruction(&Instruction::I32Const(5)); // length
5ef4579 1909
    f.instruction(&Instruction::ArrayNewData { array_type_index: 0, array_data_index: 0 });
5ef4579 1910
    f.instruction(&Instruction::LocalSet(0));
5ef4579 1911
    // return array.get(local0, 0) — the byte 'h' = 104
5ef4579 1912
    f.instruction(&Instruction::LocalGet(0));
5ef4579 1913
    f.instruction(&Instruction::I32Const(0));
5ef4579 1914
    f.instruction(&Instruction::ArrayGetU(0));
5ef4579 1915
    f.instruction(&Instruction::End);
5ef4579 1916
    code.function(&f);
5ef4579 1917
    module.section(&code);
5ef4579 1918
5ef4579 1919
    // NOTE: deliberately no MemorySection at all.
5ef4579 1920
    let mut data = DataSection::new();
5ef4579 1921
    data.passive(b"hello".iter().copied());
5ef4579 1922
    module.section(&data);
5ef4579 1923
5ef4579 1924
    let bytes = module.finish();
5ef4579 1925
5ef4579 1926
    let mut config = wasmtime::Config::new();
5ef4579 1927
    config.wasm_gc(true);
5ef4579 1928
    config.wasm_function_references(true);
5ef4579 1929
    let engine = wasmtime::Engine::new(&config).expect("engine with GC config should construct");
5ef4579 1930
5ef4579 1931
    let wasm_module = wasmtime::Module::new(&engine, &bytes).unwrap_or_else(|e| panic!("module with no memory section + passive data should be loadable: {e}"));
5ef4579 1932
    let mut store = wasmtime::Store::new(&engine, ());
5ef4579 1933
    let instance = wasmtime::Instance::new(&mut store, &wasm_module, &[]).expect("module should instantiate");
5ef4579 1934
    let main = instance.get_typed_func::<(), i32>(&mut store, "main").expect("main should have signature () -> i32");
5ef4579 1935
    let result = main.call(&mut store, ()).expect("main should not trap");
5ef4579 1936
    assert_eq!(result, b'h' as i32);
5ef4579 1937
}
5ef4579 1938
5ef4579 1939
5ef4579 1940
#[test]
5ef4579 1941
fn wasmtimeGcConfigSupportsWideningConcreteStructRefToAnyrefAndCastingBack() {
5ef4579 1942
    // Decision 3 of the wasm-gc migration plan: closure env pointers are `anyref`
5ef4579 1943
    // in the shared call_indirect signature, with each closure's body ref.cast-ing
5ef4579 1944
    // back to its own concrete env struct type. Confirm a concrete struct ref can
5ef4579 1945
    // be stored where anyref is expected (implicit widening, no instruction needed)
5ef4579 1946
    // and RefCastNonNull(Concrete(_)) recovers the concrete type correctly.
5ef4579 1947
    use wasm_encoder::*;
5ef4579 1948
5ef4579 1949
    let mut module = Module::new();
5ef4579 1950
    let mut types = TypeSection::new();
5ef4579 1951
    types.ty().struct_(vec![FieldType { element_type: StorageType::Val(ValType::I32), mutable: false }]);
5ef4579 1952
    // "identity-ish" function: (anyref) -> i32, casts back to concrete type 0 and reads field 0.
5ef4579 1953
    types.ty().function(vec![ValType::Ref(RefType::ANYREF)], vec![ValType::I32]);
5ef4579 1954
    // main: () -> i32, builds a concrete struct, passes it (widened) to func 1.
5ef4579 1955
    types.ty().function(vec![], vec![ValType::I32]);
5ef4579 1956
    module.section(&types);
5ef4579 1957
5ef4579 1958
    let mut funcs = FunctionSection::new();
5ef4579 1959
    funcs.function(1); // func 0: the anyref-accepting fn
5ef4579 1960
    funcs.function(2); // func 1: main
5ef4579 1961
    module.section(&funcs);
5ef4579 1962
5ef4579 1963
    let mut exports = ExportSection::new();
5ef4579 1964
    exports.export("main", ExportKind::Func, 1);
5ef4579 1965
    module.section(&exports);
5ef4579 1966
5ef4579 1967
    let mut code = CodeSection::new();
5ef4579 1968
    let mut cast_fn = Function::new(vec![]);
5ef4579 1969
    cast_fn.instruction(&Instruction::LocalGet(0));
5ef4579 1970
    cast_fn.instruction(&Instruction::RefCastNonNull(HeapType::Concrete(0)));
5ef4579 1971
    cast_fn.instruction(&Instruction::StructGet { struct_type_index: 0, field_index: 0 });
5ef4579 1972
    cast_fn.instruction(&Instruction::End);
5ef4579 1973
    code.function(&cast_fn);
5ef4579 1974
5ef4579 1975
    let mut main_fn = Function::new(vec![]);
5ef4579 1976
    main_fn.instruction(&Instruction::I32Const(55));
5ef4579 1977
    main_fn.instruction(&Instruction::StructNew(0)); // pushes (ref 0) — implicitly a subtype of anyref
5ef4579 1978
    main_fn.instruction(&Instruction::Call(0)); // call expects anyref param — implicit widening at the call site
5ef4579 1979
    main_fn.instruction(&Instruction::End);
5ef4579 1980
    code.function(&main_fn);
5ef4579 1981
    module.section(&code);
5ef4579 1982
5ef4579 1983
    let bytes = module.finish();
5ef4579 1984
5ef4579 1985
    let mut config = wasmtime::Config::new();
5ef4579 1986
    config.wasm_gc(true);
5ef4579 1987
    config.wasm_function_references(true);
5ef4579 1988
    let engine = wasmtime::Engine::new(&config).expect("engine with GC config should construct");
5ef4579 1989
5ef4579 1990
    let wasm_module = wasmtime::Module::new(&engine, &bytes).unwrap_or_else(|e| panic!("module should be loadable: {e}"));
5ef4579 1991
    let mut store = wasmtime::Store::new(&engine, ());
5ef4579 1992
    let instance = wasmtime::Instance::new(&mut store, &wasm_module, &[]).expect("module should instantiate");
5ef4579 1993
    let main = instance.get_typed_func::<(), i32>(&mut store, "main").expect("main should have signature () -> i32");
5ef4579 1994
    let result = main.call(&mut store, ()).expect("main should not trap");
5ef4579 1995
    assert_eq!(result, 55, "concrete struct ref should widen to anyref implicitly and cast back correctly");
5ef4579 1996
}
5ef4579 1997
5ef4579 1998
#[test]
5ef4579 1999
fn gcTypeRegistryProducesAWellFormedTypeSectionAlongsideBumpAllocatorCodegen() {
5ef4579 2000
    // Task 1 Step 5 of the wasm-gc migration plan: the new (currently unconsumed)
5ef4579 2001
    // wasm-gc type registry declares a well-formed type section — a struct type per
5ef4579 2002
    // class, a supertype+subtypes set per enum (including the built-in Bool), and a
5ef4579 2003
    // shared Str array type — even though every OTHER part of this compiled module
5ef4579 2004
    // still uses the old bump-allocator representation. Exercises a class, an enum
5ef4579 2005
    // with both a payload and a payload-free variant, and Str, so all three GC type
5ef4579 2006
    // shapes actually get emitted.
5ef4579 2007
    let src = "\
5ef4579 2008
type Cat =
5ef4579 2009
  name: Str
5ef4579 2010
  age: Int
5ef4579 2011
5ef4579 2012
enum Option =
5ef4579 2013
  | Some[Int]
5ef4579 2014
  | None
5ef4579 2015
5ef4579 2016
fun main() -> Int =
5ef4579 2017
  c = Cat(name: \"x\", age: 7)
5ef4579 2018
  c.age
5ef4579 2019
";
5ef4579 2020
    let source = parse(src);
5ef4579 2021
    let bytes = compileSource(&source).expect("compile failed");
5ef4579 2022
    let result = wasmparser::validate(&bytes);
5ef4579 2023
    assert!(result.is_ok(), "wasm validation failed: {:?}", result.err());
5ef4579 2024
    // And the still-untouched bump-allocator codegen must still actually run correctly —
5ef4579 2025
    // this task is purely additive, nothing behavioral should have changed.
5ef4579 2026
    assert_eq!(runMain(&bytes), 7);
5ef4579 2027
}
02b3582 2028
02b3582 2029
/// Task 3 of the wasm-gc migration plan: `libs/std/list.plum`'s `add`/`unlink`/`set`/
02b3582 2030
/// `removeAt`/`remove`/`clear`/`reverse`, ported field-for-field/statement-for-statement
02b3582 2031
/// from the real file (see that file's identical method bodies), against the
02b3582 2032
/// struct.new/struct.get/struct.set representation.
02b3582 2033
///
02b3582 2034
/// This uses `NodeLink`/`Int` in place of the real file's `Option[Node]`/generic `T`:
02b3582 2035
/// `plum-checker`'s monomorphizer mangles a generic type's name once it's specialized
02b3582 2036
/// (`Option` -> `Option$Int`) but does NOT rewrite `ClassEnv`/`EnumVariants`' OWN
02b3582 2037
/// declared field types to match (`plumTypeFromAst` drops type arguments entirely,
02b3582 2038
/// recording a class field typed `Option[Node]` as the bare, now-dangling `TNamed("Option")`)
02b3582 2039
/// — a real, pre-existing gap in the checker's generics support, unrelated to and
02b3582 2040
/// discovered while working on this migration, that currently blocks the REAL
02b3582 2041
/// `libs/std/list.plum` (and its already-existing, unrelated `get`/`each`/`map`
02b3582 2042
/// methods) from compiling at all. `NodeLink`/`Node` here are deliberately NOT
02b3582 2043
/// generic, sidestepping that gap, so this test still exercises the exact wasm-gc
02b3582 2044
/// struct/array mechanics (self-referential nullable-via-enum fields, `struct.set`
02b3582 2045
/// mutation through an aliased reference, `ref.test` dispatch) Task 2 built.
02b3582 2046
const LIST_SOURCE_PREFIX: &str = "\
02b3582 2047
enum NodeLink =
02b3582 2048
  | HasNode[Node]
02b3582 2049
  | NoNode
02b3582 2050
02b3582 2051
enum Option =
02b3582 2052
  | Some[Int]
02b3582 2053
  | None
02b3582 2054
02b3582 2055
type Node =
02b3582 2056
  value: Int
02b3582 2057
  prev: NodeLink
02b3582 2058
  next: NodeLink
02b3582 2059
02b3582 2060
type List =
02b3582 2061
  head: NodeLink
02b3582 2062
  tail: NodeLink
02b3582 2063
  size: Int
02b3582 2064
02b3582 2065
  fun get(self, i: Int) -> Option =
02b3582 2066
    current = self.head
02b3582 2067
    index = 0
02b3582 2068
    while current != NoNode
02b3582 2069
      match current
02b3582 2070
        HasNode(node) =>
02b3582 2071
          if index == i
02b3582 2072
            return Some(node.value)
02b3582 2073
          current = node.next
02b3582 2074
          index = index + 1
02b3582 2075
        NoNode =>
02b3582 2076
          break
02b3582 2077
    None
02b3582 2078
02b3582 2079
  fun length(self) -> Int =
02b3582 2080
    self.size
02b3582 2081
02b3582 2082
  fun add(self, values: ...Int) =
0000000 2083
    for v := range values
02b3582 2084
      node = Node(value: v, prev: self.tail, next: NoNode)
02b3582 2085
      match self.tail
02b3582 2086
        HasNode(t) =>
02b3582 2087
          t.next = HasNode(node)
02b3582 2088
        NoNode =>
02b3582 2089
          self.head = HasNode(node)
02b3582 2090
      self.tail = HasNode(node)
02b3582 2091
      self.size = self.size + 1
02b3582 2092
02b3582 2093
  fun unlink(self, node: Node) =
02b3582 2094
    match node.prev
02b3582 2095
      HasNode(p) =>
02b3582 2096
        p.next = node.next
02b3582 2097
      NoNode =>
02b3582 2098
        self.head = node.next
02b3582 2099
    match node.next
02b3582 2100
      HasNode(n) =>
02b3582 2101
        n.prev = node.prev
02b3582 2102
      NoNode =>
02b3582 2103
        self.tail = node.prev
02b3582 2104
    self.size = self.size - 1
02b3582 2105
02b3582 2106
  fun set(self, i: Int, v: Int) -> Option =
02b3582 2107
    current = self.head
02b3582 2108
    index = 0
02b3582 2109
    while current != NoNode
02b3582 2110
      match current
02b3582 2111
        HasNode(node) =>
02b3582 2112
          if index == i
02b3582 2113
            old = node.value
02b3582 2114
            node.value = v
02b3582 2115
            return Some(old)
02b3582 2116
          current = node.next
02b3582 2117
          index = index + 1
02b3582 2118
        NoNode =>
02b3582 2119
          break
02b3582 2120
    None
02b3582 2121
02b3582 2122
  fun removeAt(self, i: Int) =
02b3582 2123
    current = self.head
02b3582 2124
    index = 0
02b3582 2125
    while current != NoNode
02b3582 2126
      match current
02b3582 2127
        HasNode(node) =>
02b3582 2128
          if index == i
02b3582 2129
            self.unlink(node)
02b3582 2130
            return
02b3582 2131
          current = node.next
02b3582 2132
          index = index + 1
02b3582 2133
        NoNode =>
02b3582 2134
          break
02b3582 2135
02b3582 2136
  fun remove(self, v: Int) =
02b3582 2137
    current = self.head
02b3582 2138
    while current != NoNode
02b3582 2139
      match current
02b3582 2140
        HasNode(node) =>
02b3582 2141
          if node.value == v
02b3582 2142
            self.unlink(node)
02b3582 2143
            return
02b3582 2144
          current = node.next
02b3582 2145
        NoNode =>
02b3582 2146
          break
02b3582 2147
02b3582 2148
  fun clear(self) =
02b3582 2149
    self.head = NoNode
02b3582 2150
    self.tail = NoNode
02b3582 2151
    self.size = 0
02b3582 2152
02b3582 2153
  fun reverse(self) -> List =
02b3582 2154
    current = self.head
02b3582 2155
    while current != NoNode
02b3582 2156
      match current
02b3582 2157
        HasNode(node) =>
02b3582 2158
          next = node.next
02b3582 2159
          node.next = node.prev
02b3582 2160
          node.prev = next
02b3582 2161
          current = next
02b3582 2162
        NoNode =>
02b3582 2163
          break
02b3582 2164
    oldHead = self.head
02b3582 2165
    self.head = self.tail
02b3582 2166
    self.tail = oldHead
02b3582 2167
    self
02b3582 2168
02b3582 2169
fun optSum(o: Option) -> Int =
02b3582 2170
  match o
02b3582 2171
    Some(v) =>
02b3582 2172
      v
02b3582 2173
    None =>
02b3582 2174
      -1000
02b3582 2175
";
02b3582 2176
02b3582 2177
#[test]
02b3582 2178
fn listAddSetRemoveAtRemoveClearReverseAllWorkCorrectly() {
02b3582 2179
    let src = format!("{LIST_SOURCE_PREFIX}\
02b3582 2180
fun main() -> Int =
02b3582 2181
  l = List(head: NoNode, tail: NoNode, size: 0)
02b3582 2182
  l.add(1, 2, 3, 4, 5)
02b3582 2183
  a = l.length()
02b3582 2184
  b = optSum(l.get(0))
02b3582 2185
  c = optSum(l.get(4))
02b3582 2186
  oldVal = optSum(l.set(2, 30))
02b3582 2187
  d = optSum(l.get(2))
02b3582 2188
  l.removeAt(0)
02b3582 2189
  e = l.length()
02b3582 2190
  f = optSum(l.get(0))
02b3582 2191
  l.remove(30)
02b3582 2192
  g = l.length()
02b3582 2193
  l.reverse()
02b3582 2194
  h = optSum(l.get(0))
02b3582 2195
  l.clear()
02b3582 2196
  i = l.length()
02b3582 2197
  a + b + c + oldVal + d + e + f + g + h + i
02b3582 2198
");
02b3582 2199
    let source = parse(&src);
02b3582 2200
    let bytes = compileSource(&source).expect("compile failed");
02b3582 2201
    let result = wasmparser::validate(&bytes);
02b3582 2202
    assert!(result.is_ok(), "wasm validation failed: {:?}", result.err());
02b3582 2203
    // add(1,2,3,4,5): a=length=5, b=get(0)=1, c=get(4)=5
02b3582 2204
    // set(2,30): oldVal=3, d=get(2)=30 -> list [1,2,30,4,5]
02b3582 2205
    // removeAt(0): e=length=4, f=get(0)=2 -> list [2,30,4,5]
02b3582 2206
    // remove(30): g=length=3 -> list [2,4,5]
02b3582 2207
    // reverse(): h=get(0)=5 -> list [5,4,2]
02b3582 2208
    // clear(): i=length=0
02b3582 2209
    // 5+1+5+3+30+4+2+3+5+0 = 58
02b3582 2210
    assert_eq!(runMain(&bytes), 58);
02b3582 2211
}
02b3582 2212
02b3582 2213
/// Proves `removeAt`/`clear` actually detach nodes from the list (not just decrement
02b3582 2214
/// `size`) by removing every node one at a time via repeated `removeAt(0)` and
02b3582 2215
/// confirming the list ends up correctly empty and reports zero length — the removed
02b3582 2216
/// `Node`s (and their `NodeLink` links to each other) become unreachable and eligible
02b3582 2217
/// for collection once nothing in the list still points to them, since there's no
02b3582 2218
/// direct "assert this was garbage collected" hook available from a compiled
02b3582 2219
/// program's own execution.
02b3582 2220
#[test]
02b3582 2221
fn removingEveryNodeInALoopLeavesAnEmptyCorrectlyFunctioningList() {
02b3582 2222
    let src = format!("{LIST_SOURCE_PREFIX}\
02b3582 2223
fun main() -> Int =
02b3582 2224
  l = List(head: NoNode, tail: NoNode, size: 0)
02b3582 2225
  l.add(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
02b3582 2226
  i = 0
02b3582 2227
  while i < 10
02b3582 2228
    l.removeAt(0)
02b3582 2229
    i = i + 1
02b3582 2230
  afterLoopLength = l.length()
02b3582 2231
  isEmpty = optSum(l.get(0))
02b3582 2232
  l.add(42)
02b3582 2233
  afterReAdd = optSum(l.get(0))
02b3582 2234
  afterLoopLength + isEmpty + afterReAdd
02b3582 2235
");
02b3582 2236
    let source = parse(&src);
02b3582 2237
    let bytes = compileSource(&source).expect("compile failed");
02b3582 2238
    let result = wasmparser::validate(&bytes);
02b3582 2239
    assert!(result.is_ok(), "wasm validation failed: {:?}", result.err());
02b3582 2240
    // afterLoopLength=0, isEmpty(get(0) on empty list)=-1000, afterReAdd=42
02b3582 2241
    // 0 + -1000 + 42 = -958
02b3582 2242
    assert_eq!(runMain(&bytes), -958);
02b3582 2243
}
02b3582 2244
02b3582 2245