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
#![allow(non_snake_case)]
use plum_wasm_codegen::compileSource;
use plum_core::AstParser;
use wasm_encoder::Encode;

fn parse(src: &str) -> plum_core::ast::Source {
    let mut parser = tree_sitter::Parser::new();
    parser.set_language(&tree_sitter_plum::LANGUAGE.into()).unwrap();
    let tree = parser.parse(src, None).unwrap();
    let ap = AstParser::new(src);
    ap.parseSource(tree.root_node())
}

/// The wasm-gc migration (docs/superpowers/plans/2026-07-25-wasm-gc-migration.md)
/// needs both `wasm_gc` and `wasm_function_references` enabled — confirmed
/// empirically (see the `wasmtimeGcConfig*` tests below) rather than assumed from
/// docs, since wasmtime's own doc comment on `Config::wasm_gc` warns its GC support
/// is still in progress. Every test that instantiates/runs compiled output uses
/// this shared engine so the whole harness stays on one config.
fn gcEngine() -> wasmtime::Engine {
    let mut config = wasmtime::Config::new();
    config.wasm_gc(true);
    config.wasm_function_references(true);
    wasmtime::Engine::new(&config).expect("engine with GC config should construct")
}

#[test]
fn compilesToValidWasm() {
    let src = "fun add(a: Int, b: Int) -> Int =\n  a + b\n";
    let source = parse(src);
    let bytes = compileSource(&source).expect("compile failed");
    // Valid WASM starts with the magic number
    assert_eq!(&bytes[0..4], b"\0asm");
    assert_eq!(&bytes[4..8], &[1, 0, 0, 0]); // version 1
}

#[test]
fn outputValidates() {
    let src = "fun add(a: Int, b: Int) -> Int =\n  a + b\n";
    let source = parse(src);
    let bytes = compileSource(&source).expect("compile failed");
    // wasmparser should accept the output
    let result = wasmparser::validate(&bytes);
    assert!(result.is_ok(), "wasm validation failed: {:?}", result.err());
}

#[test]
fn factorialCompiles() {
    let src = "\
fun factorial(x: Int) -> Int =
  if x < 2
    return 1
  return x * factorial(x - 1)
";
    let source = parse(src);
    let bytes = compileSource(&source).expect("factorial should compile");
    assert_eq!(&bytes[0..4], b"\0asm");
    let result = wasmparser::validate(&bytes);
    assert!(result.is_ok(), "wasm validation failed: {:?}", result.err());
}

#[test]
fn give42CompilesAndExports() {
    let src = "fun give42() -> Int =\n  42\n";
    let source = parse(src);
    let bytes = compileSource(&source).expect("compile failed");
    assert_eq!(&bytes[0..4], b"\0asm");
    let result = wasmparser::validate(&bytes);
    assert!(result.is_ok(), "wasm validation failed: {:?}", result.err());
}

fn assertValid(src: &str) -> Vec<u8> {
    let source = parse(src);
    let bytes = compileSource(&source).unwrap_or_else(|e| panic!("compile failed for {:?}: {}", src, e));
    let result = wasmparser::validate(&bytes);
    assert!(result.is_ok(), "wasm validation failed: {:?}", result.err());
    bytes
}

#[test]
fn boolLiteralsCompile() {
    assertValid("fun main() -> Bool =\n  True\n");
    assertValid("fun main() -> Bool =\n  False\n");
}

#[test]
fn stringLiteralCompiles() {
    assertValid("fun main() -> Str =\n  \"hello\"\n");
}

#[test]
fn emptyStringLiteralCompiles() {
    assertValid("fun main() -> Str =\n  \"\"\n");
}

#[test]
fn stringInterpolationOfAnIntRunsCorrectly() {
    let src = "fun main() -> Str =\n  x = 42\n  \"{x}\"\n";
    let source = parse(src);
    let bytes = compileSource(&source).expect("compile failed");
    assert_eq!(runMainStr(&bytes), "42");
}

#[test]
fn stringInterpolationOfANegativeIntRunsCorrectly() {
    let src = "fun main() -> Str =\n  x = -7\n  \"{x}\"\n";
    let source = parse(src);
    let bytes = compileSource(&source).expect("compile failed");
    assert_eq!(runMainStr(&bytes), "-7");
}

#[test]
fn stringInterpolationOfZeroRunsCorrectly() {
    let src = "fun main() -> Str =\n  x = 0\n  \"{x}\"\n";
    let source = parse(src);
    let bytes = compileSource(&source).expect("compile failed");
    assert_eq!(runMainStr(&bytes), "0");
}

#[test]
fn stringInterpolationWithSurroundingTextAndMultipleInterpsRunsCorrectly() {
    let src = "fun main() -> Str =\n  count = 3\n  total = 10\n  \"{count} of {total} complete\"\n";
    let source = parse(src);
    let bytes = compileSource(&source).expect("compile failed");
    assert_eq!(runMainStr(&bytes), "3 of 10 complete");
}

#[test]
fn stringInterpolationOfAStrRunsCorrectly() {
    let src = "fun greet(name: Str) -> Str =\n  \"Hello, {name}!\"\n\nfun main() -> Str =\n  greet(\"World\")\n";
    let source = parse(src);
    let bytes = compileSource(&source).expect("compile failed");
    assert_eq!(runMainStr(&bytes), "Hello, World!");
}

#[test]
fn stringInterpolationOfABoolRunsCorrectly() {
    let src = "fun main() -> Str =\n  b = True\n  \"is {b}\"\n";
    let source = parse(src);
    let bytes = compileSource(&source).expect("compile failed");
    assert_eq!(runMainStr(&bytes), "is True");
}

#[test]
fn stringInterpolationOfAFloatIsAClearError() {
    // Float-to-decimal-string formatting is a substantial separate undertaking
    // (correct rounding needs something like Grisu/Ryu); scoped out for now with
    // an explicit error rather than emitting an incorrect conversion.
    let src = "fun main() -> Str =\n  x = 1.5\n  \"{x}\"\n";
    let source = parse(src);
    let err = compileSource(&source).expect_err("float interpolation should not silently compile");
    assert!(err.contains("Float"), "got: {}", err);
}

#[test]
fn floatArithmeticAndNegationCompile() {
    assertValid("fun main(x: Float) -> Float =\n  y = -x\n  y + 1.5\n");
}

#[test]
fn classFieldAndMethodCompile() {
    let src = "\
type Cat =
  name: Str
  age: Int

  fun getAge() -> Int =
    self.age

fun makeCat() -> Int =
  c = Cat(name: \"x\", age: 3)
  c.getAge()
";
    assertValid(src);
}

#[test]
fn nestedClassCallCompiles() {
    let src = "\
type Pair =
  a: Int
  b: Int

type Wrapper =
  inner: Pair
  tag: Int

fun make() -> Int =
  w = Wrapper(inner: Pair(a: 1, b: 2), tag: 9)
  w.tag
";
    assertValid(src);
}

#[test]
fn matchWithIntAndWildcardCompiles() {
    let src = "fun main(a: Int) -> Int =\n  match a\n    1 =>\n      return 10\n    _ =>\n      return 0\n";
    assertValid(src);
}

#[test]
fn matchBindingPatternCompiles() {
    let src = "fun main(a: Int) -> Int =\n  match a\n    x =>\n      return x\n";
    assertValid(src);
}

#[test]
fn matchInlineCaseBodyCompiles() {
    // Case bodies can be a single inline expression, not just an indented block.
    let src = "fun main(a: Int) =\n  match a\n    1 => 10\n    _ => 0\n";
    assertValid(src);
}

#[test]
fn matchBoolVariantPatternCompiles() {
    let src = "fun main(a: Bool) -> Int =\n  match a\n    True =>\n      return 1\n    False =>\n      return 0\n";
    assertValid(src);
}

#[test]
fn matchStringPatternIsAClearError() {
    let src = "fun main(a: Str) -> Int =\n  match a\n    \"x\" =>\n      1\n    _ =>\n      0\n";
    let source = parse(src);
    let err = compileSource(&source).expect_err("string match patterns are not yet supported");
    assert!(err.contains("string match"), "got: {}", err);
}

#[test]
fn nestedConstructorPatternMatchesAndBindsRunsCorrectly() {
    let src = "\
enum Option =
  | Some[Int]
  | None

enum Nested =
  | Wrap[Option]
  | Empty

fun f(n: Nested) -> Int =
  match n
    Wrap(Some(v)) =>
      return v
    Wrap(None) =>
      return -1
    Empty =>
      return 0

fun main() -> Int =
  f(Wrap(Some(5)))
";
    let source = parse(src);
    let bytes = compileSource(&source).expect("compile failed");
    assert_eq!(runMain(&bytes), 5);
}

#[test]
fn nestedConstructorPatternMismatchFallsThroughToNextCaseRunsCorrectly() {
    // `Wrap(Some(v))` shouldn't match a runtime `Wrap(None)` — codegen must fall
    // through to the next *top-level* case, not just fail to bind `v`.
    let src = "\
enum Option =
  | Some[Int]
  | None

enum Nested =
  | Wrap[Option]
  | Empty

fun f(n: Nested) -> Int =
  match n
    Wrap(Some(v)) =>
      return v
    Wrap(None) =>
      return -1
    Empty =>
      return 0

fun main() -> Int =
  f(Wrap(None))
";
    let source = parse(src);
    let bytes = compileSource(&source).expect("compile failed");
    assert_eq!(runMain(&bytes), -1);
}

#[test]
fn nestedConstructorPatternAgainstASpecializedGenericEnumRunsCorrectly() {
    // Exercises monomorphize.rs's recursive mangling: the outer `Full(...)` pattern
    // matches against `Box`'s own specialization, but the *inner* `Some(v)`/`None`
    // sub-pattern matches against `Box`'s generic field type (`Option`, itself
    // specialized to `Option$Int`) — each level needs its own mangling table, not
    // just the outermost one.
    let src = "\
enum Option =
  | Some[T]
  | None

enum Box =
  | Full[T]
  | Empty

fun unwrap(b: Box) -> Int =
  match b
    Full(Some(v)) => v
    Full(None) => -1
    Empty => 0

fun main() -> Int =
  unwrap(Full(Some(7)))
";
    let source = parse(src);
    let bytes = compileSource(&source).expect("compile failed");
    assert_eq!(runMain(&bytes), 7);
}

#[test]
fn doublyNestedConstructorPatternRunsCorrectly() {
    // `Some(Some(v))` — two levels of nesting, proving the recursion isn't just
    // one level deep.
    let src = "\
enum Option =
  | Some[Option]
  | None

fun unwrapTwice(o: Option) -> Int =
  match o
    Some(Some(None)) => 1
    Some(None) => 2
    None => 3
    _ => 0

fun main() -> Int =
  unwrapTwice(Some(Some(None)))
";
    let source = parse(src);
    let bytes = compileSource(&source).expect("compile failed");
    assert_eq!(runMain(&bytes), 1);
}

/// Runs `main`'s wasm bytes and returns its i64 result. wasm's own validator
/// (via wasmparser, above) only proves the module is well-formed — it can't catch
/// wrong *values*, so these tests actually execute the compiled output.
fn runMain(bytes: &[u8]) -> i64 {
    let engine = gcEngine();
    let module = wasmtime::Module::new(&engine, bytes).expect("module should be loadable");
    let mut store = wasmtime::Store::new(&engine, ());
    let instance = wasmtime::Instance::new(&mut store, &module, &[]).expect("module should instantiate");
    let main = instance
        .get_typed_func::<(), i64>(&mut store, "main")
        .expect("main should have signature () -> i64");
    main.call(&mut store, ()).expect("main should not trap")
}

fn runMainF64(bytes: &[u8]) -> f64 {
    let engine = gcEngine();
    let module = wasmtime::Module::new(&engine, bytes).expect("module should be loadable");
    let mut store = wasmtime::Store::new(&engine, ());
    let instance = wasmtime::Instance::new(&mut store, &module, &[]).expect("module should instantiate");
    let main = instance
        .get_typed_func::<(), f64>(&mut store, "main")
        .expect("main should have signature () -> f64");
    main.call(&mut store, ()).expect("main should not trap")
}

/// Runs a `() -> Str`-returning `main`, reading the returned `array<i8>` GC value
/// back out byte-by-byte via wasmtime's host-side GC ref API (`Str` has no length
/// prefix of its own now — `array.len` is native, see Decision 4 of the wasm-gc
/// migration plan) — untyped `Func::call` is used because `main`'s wasm return type
/// is a concrete `(ref $Str)`, not one `get_typed_func` can name directly.
fn runMainStr(bytes: &[u8]) -> String {
    let engine = gcEngine();
    let module = wasmtime::Module::new(&engine, bytes).expect("module should be loadable");
    let mut store = wasmtime::Store::new(&engine, ());
    let instance = wasmtime::Instance::new(&mut store, &module, &[]).expect("module should instantiate");
    let main = instance.get_func(&mut store, "main").expect("module should export main");
    let mut results = [wasmtime::Val::null_any_ref()];
    main.call(&mut store, &[], &mut results).expect("main should not trap");
    let any_ref = match &results[0] {
        wasmtime::Val::AnyRef(Some(r)) => *r,
        other => panic!("main should return a non-null anyref (Str), got {:?}", other),
    };
    let array = any_ref.unwrap_array(&store).expect("Str's returned anyref should be a GC array");
    let len = array.len(&store).expect("array.len should succeed");
    let mut bytes_out = Vec::with_capacity(len as usize);
    for i in 0..len {
        let byte = match array.get(&mut store, i).expect("array.get should succeed") {
            wasmtime::Val::I32(b) => b as u8,
            other => panic!("Str array element should be i32, got {:?}", other),
        };
        bytes_out.push(byte);
    }
    String::from_utf8(bytes_out).expect("string bytes should be valid utf8")
}

#[test]
fn factorialRunsCorrectly() {
    let src = "\
fun factorial(x: Int) -> Int =
  if x < 2
    return 1
  return x * factorial(x - 1)

fun main() -> Int =
  factorial(5)
";
    let source = parse(src);
    let bytes = compileSource(&source).expect("compile failed");
    assert_eq!(runMain(&bytes), 120);
}

#[test]
fn classFieldAndMethodRunCorrectly() {
    let src = "\
type Cat =
  name: Str
  age: Int

  fun getAge() -> Int =
    self.age

fun main() -> Int =
  c = Cat(name: \"x\", age: 7)
  c.getAge()
";
    let source = parse(src);
    let bytes = compileSource(&source).expect("compile failed");
    assert_eq!(runMain(&bytes), 7);
}

#[test]
fn nestedMethodDeclarationRunsCorrectly() {
    let src = "\
type Cat =
  name: Str
  age: Int

  fun getAge(self) -> Int =
    self.age

fun main() -> Int =
  c = Cat(name: \"x\", age: 7)
  c.getAge()
";
    let source = parse(src);
    let bytes = compileSource(&source).expect("compile failed");
    assert_eq!(runMain(&bytes), 7);
}

#[test]
fn enumDiscriminantValueFieldAccessRunsCorrectlyForEachVariant() {
    let src = "\
enum Step(n: Int) =
  | ReadMin(10)
  | ReadMax(20)

  fun toNumber(self) -> Int =
    self.n

fun main() -> Int =
  ReadMin.toNumber() * 100 + ReadMax.toNumber()
";
    let source = parse(src);
    let bytes = compileSource(&source).expect("compile failed");
    assert_eq!(runMain(&bytes), 1020);
}

#[test]
fn enumDiscriminantValueMatchesByVariantNameCorrectly() {
    let src = "\
enum Step(n: Int) =
  | ReadMin(10)
  | ReadMax(20)

fun toNumber(s: Step) -> Int =
  match s
    ReadMin => 1
    ReadMax => 2

fun main() -> Int =
  toNumber(ReadMin) * 10 + toNumber(ReadMax)
";
    let source = parse(src);
    let bytes = compileSource(&source).expect("compile failed");
    assert_eq!(runMain(&bytes), 12);
}

#[test]
fn nestedClassCallRunsCorrectly() {
    let src = "\
type Pair =
  a: Int
  b: Int

type Wrapper =
  inner: Pair
  tag: Int

fun main() -> Int =
  w = Wrapper(inner: Pair(a: 11, b: 22), tag: 99)
  w.inner.b
";
    let source = parse(src);
    let bytes = compileSource(&source).expect("compile failed");
    assert_eq!(runMain(&bytes), 22);
}

#[test]
fn repeatedClassCallInALoopDoesNotAlias() {
    // Regression test: class instances are bump-allocated at *runtime* (via a
    // mutable wasm global), not at a compile-time-fixed address — otherwise every
    // iteration's `Box(...)` would alias the same memory and this would sum to 5*4=20
    // instead of 0+1+2+3+4=10.
    let src = "\
type Box =
  v: Int

fun sumBoxes() -> Int =
  total = 0
  for i := range 5
    b = Box(v: i)
    total = total + b.v
  return total

fun main() -> Int =
  sumBoxes()
";
    let source = parse(src);
    let bytes = compileSource(&source).expect("compile failed");
    assert_eq!(runMain(&bytes), 10);
}

#[test]
fn matchIntAndWildcardRunCorrectly() {
    let src = "\
fun classify(a: Int) -> Int =
  match a
    1 =>
      return 100
    2 =>
      return 200
    _ =>
      return 0

fun main() -> Int =
  classify(2)
";
    let source = parse(src);
    let bytes = compileSource(&source).expect("compile failed");
    assert_eq!(runMain(&bytes), 200);
}

#[test]
fn matchBoolVariantPatternRunsCorrectly() {
    // Regression test: True/False are built-in Bool variants and must be treated as
    // tag comparisons, not bindings, even without an explicit `enum Bool` in this
    // source file — otherwise the first arm always "matches" (as a rebinding) and
    // `pick(False)` would wrongly return 1.
    let src = "\
fun pick(a: Bool) -> Int =
  match a
    True =>
      return 1
    False =>
      return 0

fun main() -> Int =
  pick(False)
";
    let source = parse(src);
    let bytes = compileSource(&source).expect("compile failed");
    assert_eq!(runMain(&bytes), 0);
}

#[test]
fn lowercaseSingleWordFunctionCallRunsCorrectly() {
    // Regression test: `factorial(...)` (an all-lowercase, no-uppercase, no-underscore
    // callee) used to fail to parse at all — `var_identifier` and `fn_identifier` both
    // matched its text and the grammar's lexer would nondeterministically commit to
    // `var_identifier`, breaking every such call site.
    let src = "\
fun double(n: Int) -> Int =
  n * 2

fun main() -> Int =
  double(21)
";
    let source = parse(src);
    let bytes = compileSource(&source).expect("compile failed");
    assert_eq!(runMain(&bytes), 42);
}

#[test]
fn assertTrapsOnFalseAndPassesThroughOnTrue() {
    let src_ok = "\
fun check(n: Int) -> Int =
  assert n > 0
  n

fun main() -> Int =
  check(5)
";
    let source = parse(src_ok);
    let bytes = compileSource(&source).expect("compile failed");
    assert_eq!(runMain(&bytes), 5);

    let src_trap = "\
fun check(n: Int) -> Int =
  assert n > 0
  n

fun main() -> Int =
  check(-1)
";
    let source = parse(src_trap);
    let bytes = compileSource(&source).expect("compile failed");
    let engine = gcEngine();
    let module = wasmtime::Module::new(&engine, &bytes).unwrap();
    let mut store = wasmtime::Store::new(&engine, ());
    let instance = wasmtime::Instance::new(&mut store, &module, &[]).unwrap();
    let main = instance.get_typed_func::<(), i64>(&mut store, "main").unwrap();
    let err = main.call(&mut store, ()).expect_err("a false assert should trap, not silently continue");
    assert_eq!(err.downcast_ref::<wasmtime::Trap>(), Some(&wasmtime::Trap::UnreachableCodeReached), "got: {}", err);
}

#[test]
fn todoTrapsAtRuntime() {
    // `todo` marks an unimplemented body — it must trap, not silently do nothing.
    let src = "\
fun notDoneYet() -> Int =
  todo

fun main() -> Int =
  notDoneYet()
";
    let source = parse(src);
    let bytes = compileSource(&source).expect("compile failed");
    let engine = gcEngine();
    let module = wasmtime::Module::new(&engine, &bytes).unwrap();
    let mut store = wasmtime::Store::new(&engine, ());
    let instance = wasmtime::Instance::new(&mut store, &module, &[]).unwrap();
    let main = instance.get_typed_func::<(), i64>(&mut store, "main").unwrap();
    let err = main.call(&mut store, ()).expect_err("todo should trap");
    assert_eq!(err.downcast_ref::<wasmtime::Trap>(), Some(&wasmtime::Trap::UnreachableCodeReached), "got: {}", err);
}

#[test]
fn payloadFreeVariantConstructionCompiles() {
    let src = "\
enum Color =
  | Red
  | Green
  | Blue

fun main() -> Int =\n  x = Green\n  0\n";
    assertValid(src);
}

#[test]
fn payloadVariantConstructionCompilesAndRuns() {
    let src = "\
enum Option =
  | Some[Int]
  | None

fun unwrapOr(o: Option, default: Int) -> Int =
  match o
    Some(v) =>
      return v
    None =>
      return default

fun main() -> Int =
  unwrapOr(Some(7), 0)
";
    let source = parse(src);
    let bytes = compileSource(&source).expect("compile failed");
    assert_eq!(runMain(&bytes), 7);
}

#[test]
fn multiFieldVariantConstructionCompilesAndRuns() {
    let src = "\
enum Shape =
  | Rect[Int, Int]
  | Circle[Int]

fun area(s: Shape) -> Int =
  match s
    Rect(w, h) =>
      return w * h
    Circle(r) =>
      return r * r

fun main() -> Int =
  area(Rect(3, 4))
";
    let source = parse(src);
    let bytes = compileSource(&source).expect("compile failed");
    assert_eq!(runMain(&bytes), 12);
}

#[test]
fn nonBoolBareTagPatternRunsCorrectly() {
    let src = "\
enum Color =
  | Red
  | Green
  | Blue

fun code(c: Color) -> Int =
  match c
    Red =>
      return 1
    Green =>
      return 2
    Blue =>
      return 3

fun main() -> Int =
  code(Green)
";
    let source = parse(src);
    let bytes = compileSource(&source).expect("compile failed");
    assert_eq!(runMain(&bytes), 2);
}

#[test]
fn constructorPatternWildcardFieldRunsCorrectly() {
    let src = "\
enum Option =
  | Some[Int]
  | None

fun isSome(o: Option) -> Int =
  match o
    Some(_) =>
      return 1
    None =>
      return 0

fun main() -> Int =
  isSome(Some(99))
";
    let source = parse(src);
    let bytes = compileSource(&source).expect("compile failed");
    assert_eq!(runMain(&bytes), 1);
}

#[test]
fn constructorPatternDoesNotMisfireOnPayloadFreeSibling() {
    // Regression test: `None` is a small-int tag, not a heap pointer. The
    // constructor-pattern arm for `Some(v)` must not treat a payload-free
    // sibling value as if it were a pointer to a `Some` payload.
    let src = "\
enum Option =
  | Some[Int]
  | None

fun unwrapOr(o: Option, default: Int) -> Int =
  match o
    Some(v) =>
      return v
    None =>
      return default

fun main() -> Int =
  unwrapOr(None, 5)
";
    let source = parse(src);
    let bytes = compileSource(&source).expect("compile failed");
    assert_eq!(runMain(&bytes), 5);
}

#[test]
fn enumClassFieldConstructAndDestructureRunsCorrectly() {
    // Interop check: a class with a field of an enum type, constructed with a
    // payload variant, then matched via the class field.
    let src = "\
enum Option =
  | Some[Int]
  | None

type Box =
  value: Option

  fun unwrap(default: Int) -> Int =
    match self.value
      Some(v) =>
        return v
      None =>
        return default

fun main() -> Int =
  b = Box(value: Some(42))
  b.unwrap(0)
";
    let source = parse(src);
    let bytes = compileSource(&source).expect("compile failed");
    assert_eq!(runMain(&bytes), 42);
}

#[test]
fn tailMatchWithoutReturnRunsCorrectly() {
    let src = "\
fun bindExample(n: Int) -> Int =
  match n
    x =>
      x

fun main() -> Int =
  bindExample(5)
";
    let source = parse(src);
    let bytes = compileSource(&source).expect("compile failed");
    assert_eq!(runMain(&bytes), 5);
}

#[test]
fn tailIfWithoutReturnRunsCorrectly() {
    let src = "\
fun abs(n: Int) -> Int =
  if n < 0
    -n
  else
    n

fun main() -> Int =
  abs(-7)
";
    let source = parse(src);
    let bytes = compileSource(&source).expect("compile failed");
    assert_eq!(runMain(&bytes), 7);
}

#[test]
fn tailIfNestedInsideMatchArmWithoutReturnRunsCorrectly() {
    let src = "\
fun classify(n: Int) -> Int =
  match n
    0 =>
      1
    x =>
      if x < 0
        -1
      else
        2

fun main() -> Int =
  classify(-5)
";
    let source = parse(src);
    let bytes = compileSource(&source).expect("compile failed");
    assert_eq!(runMain(&bytes), -1);
}

#[test]
fn tailMatchMixingReturnAndBareExprArmsRunsCorrectly() {
    let src = "\
fun describe(n: Int) -> Int =
  match n
    0 =>
      return 100
    x =>
      x * 2

fun main() -> Int =
  describe(21)
";
    let source = parse(src);
    let bytes = compileSource(&source).expect("compile failed");
    assert_eq!(runMain(&bytes), 42);
}

#[test]
fn tailEnumMatchWithoutReturnRunsCorrectly() {
    let src = "\
enum Option =
  | Some[Int]
  | None

fun unwrapOr(o: Option, default: Int) -> Int =
  match o
    Some(v) =>
      v
    None =>
      default

fun main() -> Int =
  unwrapOr(Some(9), 0)
";
    let source = parse(src);
    let bytes = compileSource(&source).expect("compile failed");
    assert_eq!(runMain(&bytes), 9);
}

#[test]
fn tailIfWithoutElseIsAClearError() {
    let src = "\
fun bad(n: Int) -> Int =
  if n < 0
    return 1
";
    let source = parse(src);
    let err = compileSource(&source).expect_err("if without else in value position must be a clear error, not invalid wasm");
    assert!(err.contains("doesn't produce a return value"), "got: {}", err);
}

#[test]
fn tailMatchNonExhaustiveIsAClearError() {
    let src = "\
fun bad(n: Int) -> Int =
  match n
    0 =>
      1
";
    let source = parse(src);
    let err = compileSource(&source).expect_err("non-exhaustive match in value position must be a clear error, not invalid wasm");
    assert!(err.contains("doesn't produce a return value"), "got: {}", err);
}

#[test]
fn tailMatchArmEndingInNonValueStatementIsAClearError() {
    let src = "\
fun bad(n: Int) -> Int =
  match n
    x =>
      y = x
";
    let source = parse(src);
    let err = compileSource(&source).expect_err("a match arm ending in a non-value statement must be a clear error, not invalid wasm");
    assert!(err.contains("doesn't produce a return value"), "got: {}", err);
}

#[test]
fn genericClassSpecializedAtTwoTypesDoesNotAlias() {
    let src = "\
type Box[T] =
  value: T

  fun getIntValue() -> Int =
    self.value

fun useInt() -> Int =
  b = Box(value: 7)
  b.getIntValue()

fun main() -> Int =
  useInt()
";
    let source = parse(src);
    let bytes = compileSource(&source).expect("compile failed");
    assert_eq!(runMain(&bytes), 7);
}

#[test]
fn genericFunctionCalledAtMultipleConcreteTypesRunsCorrectly() {
    let src = "\
fun identity(value: T) -> T =
  value

fun main() -> Int =
  identity(5) + identity(37)
";
    let source = parse(src);
    let bytes = compileSource(&source).expect("compile failed");
    assert_eq!(runMain(&bytes), 42);
}

#[test]
fn genericMethodOnGenericClassRunsCorrectly() {
    let src = "\
type Box[T] =
  value: T

  fun getValue() -> Int =
    self.value

fun main() -> Int =
  b = Box(value: 9)
  b.getValue()
";
    let source = parse(src);
    let bytes = compileSource(&source).expect("compile failed");
    assert_eq!(runMain(&bytes), 9);
}

#[test]
fn transitivelyGenericCallChainRunsCorrectly() {
    let src = "\
fun identity(value: T) -> T =
  value

fun doubled(value: T) -> Int =
  identity(value) + identity(value)

fun main() -> Int =
  doubled(21)
";
    let source = parse(src);
    let bytes = compileSource(&source).expect("compile failed");
    assert_eq!(runMain(&bytes), 42);
}

#[test]
fn genericEnumSpecializedAndMatchedRunsCorrectly() {
    let src = "\
enum Option =
  | Some[T]
  | None

fun unwrapOr(o: Option, default: Int) -> Int =
  match o
    Some(v) =>
      v
    None =>
      default

fun main() -> Int =
  unwrapOr(Some(13), 0)
";
    let source = parse(src);
    let bytes = compileSource(&source).expect("compile failed");
    assert_eq!(runMain(&bytes), 13);
}

#[test]
fn genericEnumMultipleInstantiationsCoexistAndRunCorrectly() {
    // `Str.length()` is not a real, working method in this codebase (no built-in
    // Str methods exist in codegen, and string-literal match patterns are an
    // explicit, documented "not yet supported" error — see
    // `matchStringPatternIsAClearError` above). So the `Some(v) => ...` arm
    // for the Str instantiation returns a fixed literal instead of deriving
    // anything from `v`'s content; the point of this test is that `Option$Str`
    // coexists with `Option$Int` and both run correctly, not string processing.
    let src = "\
enum Option =
  | Some[T]
  | None

fun unwrapIntOr(o: Option, default: Int) -> Int =
  match o
    Some(v) =>
      v
    None =>
      default

fun unwrapStrOr(o: Option, default: Int) -> Int =
  match o
    Some(v) =>
      4
    None =>
      default

fun main() -> Int =
  unwrapIntOr(Some(13), 0) + unwrapStrOr(Some(\"abcd\"), 0)
";
    let source = parse(src);
    let bytes = compileSource(&source).expect("compile failed");
    assert_eq!(runMain(&bytes), 17);
}

#[test]
fn sameBareGenericEnumParamFunctionCalledMultipleTimesRunsCorrectly() {
    // Regression test: the same generic function taking a bare generic-enum-typed param,
    // called at the same concrete instantiation multiple times, must correctly specialize
    // and reuse that specialization. This tests that the mangling logic for `unwrapOr`
    // produces identical specialized code on both call sites, not aliased/incorrect code.
    let src = "\
enum Option =
  | Some[T]
  | None

fun unwrapOr(o: Option, default: Int) -> Int =
  match o
    Some(v) =>
      v
    None =>
      default

fun main() -> Int =
  unwrapOr(Some(5), 0) + unwrapOr(Some(37), 0)
";
    let source = parse(src);
    let bytes = compileSource(&source).expect("compile failed");
    assert_eq!(runMain(&bytes), 42);
}

#[test]
fn ordinaryFunctionWithBareGenericClassParamRunsCorrectly() {
    let src = "\
type Box[T] =
  value: T

  fun getBoxValue() -> Int =
    self.value

fun sumBox(b: Box) -> Int =
  b.getBoxValue()

fun main() -> Int =
  sumBox(Box(value: 11))
";
    let source = parse(src);
    let bytes = compileSource(&source).expect("compile failed");
    assert_eq!(runMain(&bytes), 11);
}

#[test]
fn wasmModuleWithATableElementValidatesAndCallIndirectWorks() {
    // Exercises WasmModule's new table/element support directly, independent of any
    // closure-compiling logic (which doesn't exist yet) — builds a tiny module by
    // hand: one function that returns 42, registered as table element 0, called via
    // `call_indirect` from `main` using a runtime-computed (not compile-time-constant)
    // table index, to prove the table/element wiring is real, not coincidentally
    // skipped by validation.
    let mut module = plum_wasm_codegen::WasmModule::new();
    let ret42_type = module.addType(&[], &[wasm_encoder::ValType::I64]);
    let ret42_idx = module.addFunction(ret42_type, &{
        let mut body = vec![0u8]; // 0 local-decl groups
        wasm_encoder::Instruction::I64Const(42).encode(&mut body);
        wasm_encoder::Instruction::End.encode(&mut body);
        body
    });
    let table_idx = module.addTableElement(ret42_idx);
    assert_eq!(table_idx, 0);

    let main_type = module.addType(&[], &[wasm_encoder::ValType::I64]);
    let main_idx = module.addFunction(main_type, &{
        let mut body = vec![0u8]; // 0 local-decl groups
        wasm_encoder::Instruction::I32Const(0).encode(&mut body); // table index operand
        wasm_encoder::Instruction::CallIndirect { type_index: ret42_type, table_index: 0 }.encode(&mut body);
        wasm_encoder::Instruction::End.encode(&mut body);
        body
    });
    module.addExport("main", wasm_encoder::ExportKind::Func, main_idx);

    let bytes = module.finish();
    let result = wasmparser::validate(&bytes);
    assert!(result.is_ok(), "wasm validation failed: {:?}", result.err());

    let engine = gcEngine();
    let wasm_module = wasmtime::Module::new(&engine, &bytes).expect("module should be loadable");
    let mut store = wasmtime::Store::new(&engine, ());
    let instance = wasmtime::Instance::new(&mut store, &wasm_module, &[]).expect("module should instantiate");
    let main = instance.get_typed_func::<(), i64>(&mut store, "main").expect("main should have signature () -> i64");
    assert_eq!(main.call(&mut store, ()).expect("main should not trap"), 42);
}


#[test]
fn nonCapturingClosurePassedAndCalledRunsCorrectly() {
    let src = "\
fun each(cb: fn(Int) -> Int) -> Int =
  cb(5)

fun main() -> Int =
  each(|v| v)
";
    let source = parse(src);
    let bytes = compileSource(&source).expect("compile failed");
    assert_eq!(runMain(&bytes), 5);
}

#[test]
fn multiLineClosureCallArgumentWithClosingParenOnLastStatementLineRunsCorrectly() {
    // Was a documented gap: the external scanner never emitted a dedent for a
    // multi-line closure body immediately followed by `)` on the same line as the
    // body's last statement, so this shape didn't parse at all before.
    let src = "\
fun each(cb: fn(Int) -> Int) -> Int =
  cb(5)

fun main() -> Int =
  each(|v|
    x = v + 1
    x * 2)
";
    let source = parse(src);
    let bytes = compileSource(&source).expect("compile failed");
    assert_eq!(runMain(&bytes), 12);
}

#[test]
fn capturingClosureSnapshotsValueAtCreationTimeRunsCorrectly() {
    let src = "\
fun each(cb: fn(Int) -> Int) -> Int =
  cb(0)

fun useClosure() -> Int =
  x = 10
  cb = |v|
    x + v
  x = 999
  each(cb)

fun main() -> Int =
  useClosure()
";
    let source = parse(src);
    let bytes = compileSource(&source).expect("compile failed");
    // The closure must see x==10 (its value when the closure was created), not 999
    // (its value when `each(cb)` is actually called) - proving snapshot-by-value
    // capture, not a live/shared reference.
    assert_eq!(runMain(&bytes), 10);
}

#[test]
fn closureAssignedThenCalledAtFloatTypeRunsCorrectly() {
    // Was a documented gap: a closure created via assignment (not passed directly as
    // a call argument) and later called at a concrete non-Int type could hit a wasm
    // runtime trap — the checker's own closure inference gives every param a fresh
    // TVar and never unifies it against how it's used in the body, so a genuinely
    // Float param silently defaulted to Int, producing a `call_indirect` signature
    // mismatch between the compiled closure body and its call site.
    let src = "\
fun eachF(cb: fn(Float) -> Float) -> Float =
  cb(0.0)

fun useClosure() -> Float =
  offset = 2.5
  cb = |v|
    offset + v
  eachF(cb)

fun main() -> Float =
  useClosure()
";
    let source = parse(src);
    let bytes = compileSource(&source).expect("compile failed");
    assert_eq!(runMainF64(&bytes), 2.5);
}

#[test]
fn closureAssignedThenCalledAtClassTypeRunsCorrectly() {
    let src = "\
type Cat =
  age: Int

fun eachCat(cb: fn(Cat) -> Int) -> Int =
  cb(Cat(age: 7))

fun useClosure() -> Int =
  cb = |c|
    c.age
  eachCat(cb)

fun main() -> Int =
  useClosure()
";
    let source = parse(src);
    let bytes = compileSource(&source).expect("compile failed");
    assert_eq!(runMain(&bytes), 7);
}

#[test]
fn closurePassedThroughAlreadyGenericHigherOrderFunctionRunsCorrectly() {
    let src = "\
fun identity(value: T) -> T =
  value

fun each(cb: fn(Int) -> Int) -> Int =
  cb(identity(7))

fun main() -> Int =
  each(|v| v * 2)
";
    let source = parse(src);
    let bytes = compileSource(&source).expect("compile failed");
    assert_eq!(runMain(&bytes), 14);
}

#[test]
fn nestedClosureLiteralRunsCorrectly() {
    // Was a documented gap: a closure literal nested inside another closure's body
    // produced a clear compile error (the discovery pre-pass never recursed into a
    // closure's own body to find closures nested inside it). Here `inner` (nested
    // inside `outer`'s body) needs `offset` — a name from `useNested`'s scope, two
    // levels up from `inner` itself, and not referenced by `outer` directly — which
    // exercises the multi-level capture chain: `outer` must itself capture `offset`
    // purely because `inner` needs it, not because `outer` uses it.
    let src = "\
fun each(cb: fn(Int) -> Int) -> Int =
  cb(5)

fun useNested() -> Int =
  offset = 100
  outer = |v|
    inner = |w|
      w + offset
    inner(v)
  each(outer)

fun main() -> Int =
  useNested()
";
    let source = parse(src);
    let bytes = compileSource(&source).expect("compile failed");
    assert_eq!(runMain(&bytes), 105);
}

#[test]
fn nestedClosureLiteralPassedDirectlyAsCallArgumentRunsCorrectly() {
    let src = "\
fun each(cb: fn(Int) -> Int) -> Int =
  cb(5)

fun main() -> Int =
  each(|v|
    each(|w| w + v))
";
    let source = parse(src);
    let bytes = compileSource(&source).expect("compile failed");
    assert_eq!(runMain(&bytes), 10);
}

#[test]
fn namedFunctionUsedAsAClosureTypedValueRunsCorrectly() {
    // A plain top-level named function (not a `|params| body` closure literal) used
    // wherever a `fn(...)`-typed value is expected — no closure literal involved at
    // the call site at all.
    let src = "\
fun double(x: Int) -> Int =
  x * 2

fun each(cb: fn(Int) -> Int) -> Int =
  cb(21)

fun main() -> Int =
  each(double)
";
    let source = parse(src);
    let bytes = compileSource(&source).expect("compile failed");
    assert_eq!(runMain(&bytes), 42);
}

#[test]
fn namedFunctionUsedAsAValueAssignedThenCalledRunsCorrectly() {
    let src = "\
fun double(x: Int) -> Int =
  x * 2

fun main() -> Int =
  f = double
  f(21)
";
    let source = parse(src);
    let bytes = compileSource(&source).expect("compile failed");
    assert_eq!(runMain(&bytes), 42);
}

#[test]
fn namedFunctionUsedAsAValueAlongsideAClosureAtTheSameCallTypeRunsCorrectly() {
    // Proves the trampoline shares the same call_indirect type as an ordinary
    // closure of the same signature (both must resolve to the same wasm function
    // type, since both flow through the exact same `cb(...)` call site).
    let src = "\
fun double(x: Int) -> Int =
  x * 2

fun each(cb: fn(Int) -> Int) -> Int =
  cb(10)

fun main() -> Int =
  each(double) + each(|v| v + 1)
";
    let source = parse(src);
    let bytes = compileSource(&source).expect("compile failed");
    assert_eq!(runMain(&bytes), 31);
}

#[test]
fn multiSubjectMatchWithEnumTagsRunsCorrectly() {
    // Mirrors libs/std/bool.plum's `and`/`or`: `match self, o` against two Bool
    // subjects, each case naming a tag pattern per position.
    let src = "\
fun and(a: Bool, b: Bool) -> Bool =
  match a, b
    True, True => True
    True, False => False
    False, True => False
    False, False => False

fun main() -> Int =
  x = and(True, True)
  y = and(True, False)
  match x, y
    True, False => 1
    _, _ => 0
";
    let source = parse(src);
    let bytes = compileSource(&source).expect("compile failed");
    assert_eq!(runMain(&bytes), 1);
}

#[test]
fn multiSubjectMatchFallsThroughToNextCaseWhenOnlyFirstPositionMatches() {
    // The first case's position-0 pattern (`1`) matches, but position-1 (`1`)
    // doesn't (b is 2) — codegen must fall through to the *next case* (trying its
    // own position 0 again), not just "move on" within the first case.
    let src = "\
fun classify(a: Int, b: Int) -> Int =
  match a, b
    1, 1 => 100
    1, 2 => 200
    _, _ => 0

fun main() -> Int =
  classify(1, 2)
";
    let source = parse(src);
    let bytes = compileSource(&source).expect("compile failed");
    assert_eq!(runMain(&bytes), 200);
}

#[test]
fn multiSubjectMatchWithBindingAndWildcardRunsCorrectly() {
    let src = "\
fun combine(a: Int, b: Int) -> Int =
  match a, b
    0, y => y
    x, 0 => x
    x, y => x + y

fun main() -> Int =
  combine(3, 4)
";
    let source = parse(src);
    let bytes = compileSource(&source).expect("compile failed");
    assert_eq!(runMain(&bytes), 7);
}

#[test]
fn multiSubjectMatchWithGenericEnumVariantRunsCorrectly() {
    // Exercises the monomorphize.rs fix: each subject's own generic-enum
    // specialization (`Some$Int`) must be mangled independently per position.
    let src = "\
enum Option =
  | Some[T]
  | None

fun both(a: Option, b: Option) -> Int =
  match a, b
    Some(x), Some(y) => x + y
    _, _ => 0

fun main() -> Int =
  both(Some(3), Some(4))
";
    let source = parse(src);
    let bytes = compileSource(&source).expect("compile failed");
    assert_eq!(runMain(&bytes), 7);
}

#[test]
fn fieldAssignmentTargetRunsCorrectly() {
    let src = "\
type Counter =
  value: Int

  fun bump() =
    self.value = self.value + 1

fun main() -> Int =
  c = Counter(value: 41)
  c.bump()
  c.value
";
    let source = parse(src);
    let bytes = compileSource(&source).expect("compile failed");
    assert_eq!(runMain(&bytes), 42);
}

#[test]
fn chainedFieldAssignmentTargetRunsCorrectly() {
    let src = "\
type Inner =
  value: Int

type Outer =
  inner: Inner

  fun bump() =
    self.inner.value = self.inner.value + 1

fun main() -> Int =
  o = Outer(inner: Inner(value: 9))
  o.bump()
  o.inner.value
";
    let source = parse(src);
    let bytes = compileSource(&source).expect("compile failed");
    assert_eq!(runMain(&bytes), 10);
}

#[test]
fn mixedMultiAssignWithFieldTargetRunsCorrectly() {
    let src = "\
type Counter =
  value: Int

fun main() -> Int =
  c = Counter(value: 5)
  a, c.value = 100, 7
  a + c.value
";
    let source = parse(src);
    let bytes = compileSource(&source).expect("compile failed");
    assert_eq!(runMain(&bytes), 107);
}

#[test]
fn variadicCallWithVaryingTrailingArgCountsRunsCorrectly() {
    let src = "\
fun combine(prefix: Int, rest: ...Int) -> Int =
  prefix

fun main() -> Int =
  a = combine(10)
  b = combine(20, 1)
  c = combine(30, 1, 2, 3)
  a + b + c
";
    let source = parse(src);
    let bytes = compileSource(&source).expect("compile failed");
    assert_eq!(runMain(&bytes), 60);
}

#[test]
fn sumAllVariadicIntRunsCorrectly() {
    let src = "\
fun sumAll(nums: ...Int) -> Int =
  total = 0
  for v := range nums
    total = total + v
  total

fun main() -> Int =
  sumAll(1, 2, 3, 4)
";
    let source = parse(src);
    let bytes = compileSource(&source).expect("compile failed");
    assert_eq!(runMain(&bytes), 10);
}

#[test]
fn sumAllVariadicIntWithZeroArgsRunsCorrectly() {
    let src = "\
fun sumAll(nums: ...Int) -> Int =
  total = 0
  for v := range nums
    total = total + v
  total

fun main() -> Int =
  sumAll()
";
    let source = parse(src);
    let bytes = compileSource(&source).expect("compile failed");
    assert_eq!(runMain(&bytes), 0);
}

#[test]
fn wasmtimeGcConfigCanRunAHandEncodedGcModule() {
    // Throwaway empirical check (plan Task 1, Step 1): confirm wasmtime 28's GC
    // support actually works end to end before building a type-emitter on top of
    // it. Hand-encodes the smallest possible module with one GC struct type and
    // one function that does struct.new_default and returns it, bypassing plum
    // entirely, so a failure here is unambiguously about wasmtime/wasm-encoder,
    // not about anything plum-specific.
    use wasm_encoder::*;

    let mut module = Module::new();

    let mut types = TypeSection::new();
    // type 0: struct { i32 }
    types.ty().struct_(vec![FieldType { element_type: StorageType::Val(ValType::I32), mutable: true }]);
    // type 1: () -> (ref null 0)
    let struct_ref = ValType::Ref(RefType { nullable: true, heap_type: HeapType::Concrete(0) });
    types.ty().function(vec![], vec![struct_ref]);
    module.section(&types);

    let mut funcs = FunctionSection::new();
    funcs.function(1);
    module.section(&funcs);

    let mut exports = ExportSection::new();
    exports.export("main", ExportKind::Func, 0);
    module.section(&exports);

    let mut code = CodeSection::new();
    let mut f = Function::new(vec![]);
    f.instruction(&Instruction::StructNewDefault(0));
    f.instruction(&Instruction::End);
    code.function(&f);
    module.section(&code);

    let bytes = module.finish();

    let mut config = wasmtime::Config::new();
    config.wasm_gc(true);
    config.wasm_function_references(true);
    let engine = wasmtime::Engine::new(&config).expect("engine with GC config should construct");

    let wasm_module = wasmtime::Module::new(&engine, &bytes).expect("hand-encoded GC module should be loadable");
    let mut store = wasmtime::Store::new(&engine, ());
    let instance = wasmtime::Instance::new(&mut store, &wasm_module, &[]).expect("module should instantiate");
    let main = instance
        .get_func(&mut store, "main")
        .expect("main should be exported");
    let mut results = [wasmtime::Val::I32(0)];
    main.call(&mut store, &[], &mut results).expect("main should not trap");
}


#[test]
fn wasmtimeGcConfigSupportsSubtypingRefTestAndNullableSelfReferentialFields() {
    // Deeper empirical check: enum-variant subtyping (abstract supertype + concrete
    // subtypes in one `rec` group), ref.test-based dispatch, ref.cast to narrow to a
    // subtype, and a nullable field that references the struct's OWN type (the
    // Node.next: Option[Node] shape List needs) — all in one hand-encoded module,
    // bypassing plum entirely.
    use wasm_encoder::*;

    let mut module = Module::new();
    let mut types = TypeSection::new();

    // rec group: type 0 = abstract enum supertype (empty struct, non-final so it can
    // be subtyped); type 1 = concrete "Some"-like subtype with one i32 payload field;
    // type 2 = concrete "None"-like subtype (empty, no payload).
    types.ty().rec(vec![
        SubType {
            is_final: false,
            supertype_idx: None,
            composite_type: CompositeType { inner: CompositeInnerType::Struct(StructType { fields: vec![].into() }), shared: false },
        },
        SubType {
            is_final: true,
            supertype_idx: Some(0),
            composite_type: CompositeType {
                inner: CompositeInnerType::Struct(StructType { fields: vec![
                    FieldType { element_type: StorageType::Val(ValType::I32), mutable: false },
                ].into() }),
                shared: false,
            },
        },
        SubType {
            is_final: true,
            supertype_idx: Some(0),
            composite_type: CompositeType { inner: CompositeInnerType::Struct(StructType { fields: vec![].into() }), shared: false },
        },
    ]);

    // type 3: a self-referential Node struct — { value: i32, next: ref null $Node }.
    // Must be declared in its own rec group (or alone) referencing its own index (3)
    // for the nullable self-reference to resolve.
    types.ty().rec(vec![
        SubType {
            is_final: true,
            supertype_idx: None,
            composite_type: CompositeType {
                inner: CompositeInnerType::Struct(StructType { fields: vec![
                    FieldType { element_type: StorageType::Val(ValType::I32), mutable: false },
                    FieldType { element_type: StorageType::Val(ValType::Ref(RefType { nullable: true, heap_type: HeapType::Concrete(3) })), mutable: true },
                ].into() }),
                shared: false,
            },
        },
    ]);

    // type 4: () -> i32 — constructs a "Some"-like subtype (type 1) holding 42,
    // stores it as the supertype (type 0), ref.tests it against type 1, then
    // ref.casts and struct.gets the payload back out. Also builds a 2-node linked
    // list (type 3) and confirms unlinking (overwriting `next` with ref.null) and
    // reading back the remaining node's value both work.
    let super_ref = ValType::Ref(RefType { nullable: true, heap_type: HeapType::Concrete(0) });
    types.ty().function(vec![], vec![ValType::I32]);
    module.section(&types);

    let mut funcs = FunctionSection::new();
    funcs.function(4);
    module.section(&funcs);

    let mut exports = ExportSection::new();
    exports.export("main", ExportKind::Func, 0);
    module.section(&exports);

    let mut code = CodeSection::new();
    let mut f = Function::new(vec![(1, super_ref.clone()), (1, ValType::Ref(RefType { nullable: true, heap_type: HeapType::Concrete(3) }))]);
    let locals_super = 0u32;
    let locals_node = 1u32;

    // local_super = Some(42) (as the supertype)
    f.instruction(&Instruction::I32Const(42));
    f.instruction(&Instruction::StructNew(1));
    f.instruction(&Instruction::LocalSet(locals_super));

    // local_node = Node { value: 1, next: null }
    f.instruction(&Instruction::I32Const(1));
    f.instruction(&Instruction::RefNull(HeapType::Concrete(3)));
    f.instruction(&Instruction::StructNew(3));
    f.instruction(&Instruction::LocalSet(locals_node));

    // if ref.test(local_super, type 1) { result = ref.cast(local_super, type1).field0 } else { result = -1 }
    f.instruction(&Instruction::LocalGet(locals_super));
    f.instruction(&Instruction::RefTestNonNull(HeapType::Concrete(1)));
    f.instruction(&Instruction::If(BlockType::Result(ValType::I32)));
    f.instruction(&Instruction::LocalGet(locals_super));
    f.instruction(&Instruction::RefCastNonNull(HeapType::Concrete(1)));
    f.instruction(&Instruction::StructGet { struct_type_index: 1, field_index: 0 });
    f.instruction(&Instruction::Else);
    f.instruction(&Instruction::I32Const(-1));
    f.instruction(&Instruction::End);

    // unlink: local_node.next = ref.null (already null, but exercise the store path)
    f.instruction(&Instruction::LocalGet(locals_node));
    f.instruction(&Instruction::RefNull(HeapType::Concrete(3)));
    f.instruction(&Instruction::StructSet { struct_type_index: 3, field_index: 1 });

    // add local_node.value to the ref.test result and return
    f.instruction(&Instruction::LocalGet(locals_node));
    f.instruction(&Instruction::StructGet { struct_type_index: 3, field_index: 0 });
    f.instruction(&Instruction::I32Add);
    f.instruction(&Instruction::End);
    code.function(&f);
    module.section(&code);

    let bytes = module.finish();

    let mut config = wasmtime::Config::new();
    config.wasm_gc(true);
    config.wasm_function_references(true);
    let engine = wasmtime::Engine::new(&config).expect("engine with GC config should construct");

    let wasm_module = wasmtime::Module::new(&engine, &bytes).unwrap_or_else(|e| panic!("module should be loadable: {e}"));
    let mut store = wasmtime::Store::new(&engine, ());
    let instance = wasmtime::Instance::new(&mut store, &wasm_module, &[]).expect("module should instantiate");
    let main = instance
        .get_typed_func::<(), i32>(&mut store, "main")
        .expect("main should have signature () -> i32");
    let result = main.call(&mut store, ()).expect("main should not trap");
    assert_eq!(result, 43, "expected ref.test/ref.cast payload (42) + node.value (1) = 43");
}


#[test]
fn wasmtimeGcConfigAllowsStructNewInGlobalConstExpr() {
    // Decision 2 of the wasm-gc migration plan pre-allocates payload-free enum
    // variants (True/False/None/...) once as globals. Confirm a global's
    // initializer expression can directly use struct.new (not just i32.const/
    // ref.null), or the plan needs a `start` function fallback instead.
    use wasm_encoder::*;

    let mut module = Module::new();
    let mut types = TypeSection::new();
    types.ty().struct_(vec![FieldType { element_type: StorageType::Val(ValType::I32), mutable: false }]);
    types.ty().function(vec![], vec![ValType::I32]);
    module.section(&types);

    let mut funcs = FunctionSection::new();
    funcs.function(1);
    module.section(&funcs);

    let mut globals = GlobalSection::new();
    let struct_ref_ty = ValType::Ref(RefType { nullable: true, heap_type: HeapType::Concrete(0) });
    let mut init = Vec::new();
    Instruction::I32Const(7).encode(&mut init);
    Instruction::StructNew(0).encode(&mut init);
    Instruction::End.encode(&mut init);
    globals.global(
        GlobalType { val_type: struct_ref_ty, mutable: false, shared: false },
        &ConstExpr::raw(init),
    );
    module.section(&globals);

    let mut exports = ExportSection::new();
    exports.export("main", ExportKind::Func, 0);
    module.section(&exports);

    let mut code = CodeSection::new();
    let mut f = Function::new(vec![]);
    f.instruction(&Instruction::GlobalGet(0));
    f.instruction(&Instruction::StructGet { struct_type_index: 0, field_index: 0 });
    f.instruction(&Instruction::End);
    code.function(&f);
    module.section(&code);

    let bytes = module.finish();

    let mut config = wasmtime::Config::new();
    config.wasm_gc(true);
    config.wasm_function_references(true);
    let engine = wasmtime::Engine::new(&config).expect("engine with GC config should construct");

    let wasm_module = match wasmtime::Module::new(&engine, &bytes) {
        Ok(m) => m,
        Err(e) => {
            println!("struct.new in a global const-expr is NOT supported by this wasmtime/config: {e}");
            println!("plan implication: Task 2 Step 2 (2a) must use a `start` function instead of a const global initializer.");
            return;
        }
    };
    let mut store = wasmtime::Store::new(&engine, ());
    let instance = wasmtime::Instance::new(&mut store, &wasm_module, &[]).expect("module should instantiate");
    let main = instance.get_typed_func::<(), i32>(&mut store, "main").expect("main should have signature () -> i32");
    let result = main.call(&mut store, ()).expect("main should not trap");
    assert_eq!(result, 7);
    println!("struct.new IS supported directly in a global const-expr initializer.");
}


#[test]
fn wasmtimeGcConfigSupportsStartFunctionInitializingGcGlobals() {
    // Follow-up to the previous test: struct.new isn't allowed in a global
    // const-expr, so confirm the `start` function fallback works instead —
    // a mutable global initialized to ref.null, populated by struct.new inside
    // a `start` function that runs once at instantiation before any export.
    use wasm_encoder::*;

    let mut module = Module::new();
    let mut types = TypeSection::new();
    types.ty().struct_(vec![FieldType { element_type: StorageType::Val(ValType::I32), mutable: false }]);
    types.ty().function(vec![], vec![]); // start fn: () -> ()
    types.ty().function(vec![], vec![ValType::I32]); // main: () -> i32
    module.section(&types);

    let mut funcs = FunctionSection::new();
    funcs.function(1); // func 0: start
    funcs.function(2); // func 1: main
    module.section(&funcs);

    let mut globals = GlobalSection::new();
    let struct_ref_ty = ValType::Ref(RefType { nullable: true, heap_type: HeapType::Concrete(0) });
    globals.global(
        GlobalType { val_type: struct_ref_ty.clone(), mutable: true, shared: false },
        &ConstExpr::ref_null(HeapType::Concrete(0)),
    );
    module.section(&globals);

    let mut exports = ExportSection::new();
    exports.export("main", ExportKind::Func, 1);
    module.section(&exports);

    let start = StartSection { function_index: 0 };
    module.section(&start);

    let mut code = CodeSection::new();
    let mut start_fn = Function::new(vec![]);
    start_fn.instruction(&Instruction::I32Const(99));
    start_fn.instruction(&Instruction::StructNew(0));
    start_fn.instruction(&Instruction::GlobalSet(0));
    start_fn.instruction(&Instruction::End);
    code.function(&start_fn);

    let mut main_fn = Function::new(vec![]);
    main_fn.instruction(&Instruction::GlobalGet(0));
    main_fn.instruction(&Instruction::StructGet { struct_type_index: 0, field_index: 0 });
    main_fn.instruction(&Instruction::End);
    code.function(&main_fn);
    module.section(&code);

    let bytes = module.finish();

    let mut config = wasmtime::Config::new();
    config.wasm_gc(true);
    config.wasm_function_references(true);
    let engine = wasmtime::Engine::new(&config).expect("engine with GC config should construct");

    let wasm_module = wasmtime::Module::new(&engine, &bytes).unwrap_or_else(|e| panic!("module should be loadable: {e}"));
    let mut store = wasmtime::Store::new(&engine, ());
    let instance = wasmtime::Instance::new(&mut store, &wasm_module, &[]).expect("module should instantiate (start fn should run automatically)");
    let main = instance.get_typed_func::<(), i32>(&mut store, "main").expect("main should have signature () -> i32");
    let result = main.call(&mut store, ()).expect("main should not trap");
    assert_eq!(result, 99, "start fn should have populated the global before main ran");
}


#[test]
fn wasmtimeGcConfigSupportsArrayNewDataFromPassiveSegmentWithNoMemorySection() {
    // Decision 4 of the wasm-gc migration plan: static string data lives in a
    // PASSIVE data segment (no active memory offset), consumed via array.new_data
    // — confirming this needs no `memory` section in the module at all, which is
    // what lets the whole memory section disappear once bump allocation is retired.
    use wasm_encoder::*;

    let mut module = Module::new();
    let mut types = TypeSection::new();
    types.ty().array(&StorageType::I8, false);
    let arr_ref = ValType::Ref(RefType { nullable: true, heap_type: HeapType::Concrete(0) });
    types.ty().function(vec![], vec![ValType::I32]);
    module.section(&types);

    let mut funcs = FunctionSection::new();
    funcs.function(1);
    module.section(&funcs);

    let mut exports = ExportSection::new();
    exports.export("main", ExportKind::Func, 0);
    module.section(&exports);

    // Required whenever the module uses array.new_data/memory.init/data.drop —
    // the validator needs the passive-segment count before the code section.
    module.section(&DataCountSection { count: 1 });

    let mut code = CodeSection::new();
    let mut f = Function::new(vec![(1, arr_ref)]);
    // local 0 = array.new_data(type 0, data segment 0) with offset=0, len=5 ("hello")
    f.instruction(&Instruction::I32Const(0)); // data offset
    f.instruction(&Instruction::I32Const(5)); // length
    f.instruction(&Instruction::ArrayNewData { array_type_index: 0, array_data_index: 0 });
    f.instruction(&Instruction::LocalSet(0));
    // return array.get(local0, 0) — the byte 'h' = 104
    f.instruction(&Instruction::LocalGet(0));
    f.instruction(&Instruction::I32Const(0));
    f.instruction(&Instruction::ArrayGetU(0));
    f.instruction(&Instruction::End);
    code.function(&f);
    module.section(&code);

    // NOTE: deliberately no MemorySection at all.
    let mut data = DataSection::new();
    data.passive(b"hello".iter().copied());
    module.section(&data);

    let bytes = module.finish();

    let mut config = wasmtime::Config::new();
    config.wasm_gc(true);
    config.wasm_function_references(true);
    let engine = wasmtime::Engine::new(&config).expect("engine with GC config should construct");

    let wasm_module = wasmtime::Module::new(&engine, &bytes).unwrap_or_else(|e| panic!("module with no memory section + passive data should be loadable: {e}"));
    let mut store = wasmtime::Store::new(&engine, ());
    let instance = wasmtime::Instance::new(&mut store, &wasm_module, &[]).expect("module should instantiate");
    let main = instance.get_typed_func::<(), i32>(&mut store, "main").expect("main should have signature () -> i32");
    let result = main.call(&mut store, ()).expect("main should not trap");
    assert_eq!(result, b'h' as i32);
}


#[test]
fn wasmtimeGcConfigSupportsWideningConcreteStructRefToAnyrefAndCastingBack() {
    // Decision 3 of the wasm-gc migration plan: closure env pointers are `anyref`
    // in the shared call_indirect signature, with each closure's body ref.cast-ing
    // back to its own concrete env struct type. Confirm a concrete struct ref can
    // be stored where anyref is expected (implicit widening, no instruction needed)
    // and RefCastNonNull(Concrete(_)) recovers the concrete type correctly.
    use wasm_encoder::*;

    let mut module = Module::new();
    let mut types = TypeSection::new();
    types.ty().struct_(vec![FieldType { element_type: StorageType::Val(ValType::I32), mutable: false }]);
    // "identity-ish" function: (anyref) -> i32, casts back to concrete type 0 and reads field 0.
    types.ty().function(vec![ValType::Ref(RefType::ANYREF)], vec![ValType::I32]);
    // main: () -> i32, builds a concrete struct, passes it (widened) to func 1.
    types.ty().function(vec![], vec![ValType::I32]);
    module.section(&types);

    let mut funcs = FunctionSection::new();
    funcs.function(1); // func 0: the anyref-accepting fn
    funcs.function(2); // func 1: main
    module.section(&funcs);

    let mut exports = ExportSection::new();
    exports.export("main", ExportKind::Func, 1);
    module.section(&exports);

    let mut code = CodeSection::new();
    let mut cast_fn = Function::new(vec![]);
    cast_fn.instruction(&Instruction::LocalGet(0));
    cast_fn.instruction(&Instruction::RefCastNonNull(HeapType::Concrete(0)));
    cast_fn.instruction(&Instruction::StructGet { struct_type_index: 0, field_index: 0 });
    cast_fn.instruction(&Instruction::End);
    code.function(&cast_fn);

    let mut main_fn = Function::new(vec![]);
    main_fn.instruction(&Instruction::I32Const(55));
    main_fn.instruction(&Instruction::StructNew(0)); // pushes (ref 0) — implicitly a subtype of anyref
    main_fn.instruction(&Instruction::Call(0)); // call expects anyref param — implicit widening at the call site
    main_fn.instruction(&Instruction::End);
    code.function(&main_fn);
    module.section(&code);

    let bytes = module.finish();

    let mut config = wasmtime::Config::new();
    config.wasm_gc(true);
    config.wasm_function_references(true);
    let engine = wasmtime::Engine::new(&config).expect("engine with GC config should construct");

    let wasm_module = wasmtime::Module::new(&engine, &bytes).unwrap_or_else(|e| panic!("module should be loadable: {e}"));
    let mut store = wasmtime::Store::new(&engine, ());
    let instance = wasmtime::Instance::new(&mut store, &wasm_module, &[]).expect("module should instantiate");
    let main = instance.get_typed_func::<(), i32>(&mut store, "main").expect("main should have signature () -> i32");
    let result = main.call(&mut store, ()).expect("main should not trap");
    assert_eq!(result, 55, "concrete struct ref should widen to anyref implicitly and cast back correctly");
}

#[test]
fn gcTypeRegistryProducesAWellFormedTypeSectionAlongsideBumpAllocatorCodegen() {
    // Task 1 Step 5 of the wasm-gc migration plan: the new (currently unconsumed)
    // wasm-gc type registry declares a well-formed type section — a struct type per
    // class, a supertype+subtypes set per enum (including the built-in Bool), and a
    // shared Str array type — even though every OTHER part of this compiled module
    // still uses the old bump-allocator representation. Exercises a class, an enum
    // with both a payload and a payload-free variant, and Str, so all three GC type
    // shapes actually get emitted.
    let src = "\
type Cat =
  name: Str
  age: Int

enum Option =
  | Some[Int]
  | None

fun main() -> Int =
  c = Cat(name: \"x\", age: 7)
  c.age
";
    let source = parse(src);
    let bytes = compileSource(&source).expect("compile failed");
    let result = wasmparser::validate(&bytes);
    assert!(result.is_ok(), "wasm validation failed: {:?}", result.err());
    // And the still-untouched bump-allocator codegen must still actually run correctly —
    // this task is purely additive, nothing behavioral should have changed.
    assert_eq!(runMain(&bytes), 7);
}

/// Task 3 of the wasm-gc migration plan: `libs/std/list.plum`'s `add`/`unlink`/`set`/
/// `removeAt`/`remove`/`clear`/`reverse`, ported field-for-field/statement-for-statement
/// from the real file (see that file's identical method bodies), against the
/// struct.new/struct.get/struct.set representation.
///
/// This uses `NodeLink`/`Int` in place of the real file's `Option[Node]`/generic `T`:
/// `plum-checker`'s monomorphizer mangles a generic type's name once it's specialized
/// (`Option` -> `Option$Int`) but does NOT rewrite `ClassEnv`/`EnumVariants`' OWN
/// declared field types to match (`plumTypeFromAst` drops type arguments entirely,
/// recording a class field typed `Option[Node]` as the bare, now-dangling `TNamed("Option")`)
/// — a real, pre-existing gap in the checker's generics support, unrelated to and
/// discovered while working on this migration, that currently blocks the REAL
/// `libs/std/list.plum` (and its already-existing, unrelated `get`/`each`/`map`
/// methods) from compiling at all. `NodeLink`/`Node` here are deliberately NOT
/// generic, sidestepping that gap, so this test still exercises the exact wasm-gc
/// struct/array mechanics (self-referential nullable-via-enum fields, `struct.set`
/// mutation through an aliased reference, `ref.test` dispatch) Task 2 built.
const LIST_SOURCE_PREFIX: &str = "\
enum NodeLink =
  | HasNode[Node]
  | NoNode

enum Option =
  | Some[Int]
  | None

type Node =
  value: Int
  prev: NodeLink
  next: NodeLink

type List =
  head: NodeLink
  tail: NodeLink
  size: Int

  fun get(self, i: Int) -> Option =
    current = self.head
    index = 0
    while current != NoNode
      match current
        HasNode(node) =>
          if index == i
            return Some(node.value)
          current = node.next
          index = index + 1
        NoNode =>
          break
    None

  fun length(self) -> Int =
    self.size

  fun add(self, values: ...Int) =
    for v := range values
      node = Node(value: v, prev: self.tail, next: NoNode)
      match self.tail
        HasNode(t) =>
          t.next = HasNode(node)
        NoNode =>
          self.head = HasNode(node)
      self.tail = HasNode(node)
      self.size = self.size + 1

  fun unlink(self, node: Node) =
    match node.prev
      HasNode(p) =>
        p.next = node.next
      NoNode =>
        self.head = node.next
    match node.next
      HasNode(n) =>
        n.prev = node.prev
      NoNode =>
        self.tail = node.prev
    self.size = self.size - 1

  fun set(self, i: Int, v: Int) -> Option =
    current = self.head
    index = 0
    while current != NoNode
      match current
        HasNode(node) =>
          if index == i
            old = node.value
            node.value = v
            return Some(old)
          current = node.next
          index = index + 1
        NoNode =>
          break
    None

  fun removeAt(self, i: Int) =
    current = self.head
    index = 0
    while current != NoNode
      match current
        HasNode(node) =>
          if index == i
            self.unlink(node)
            return
          current = node.next
          index = index + 1
        NoNode =>
          break

  fun remove(self, v: Int) =
    current = self.head
    while current != NoNode
      match current
        HasNode(node) =>
          if node.value == v
            self.unlink(node)
            return
          current = node.next
        NoNode =>
          break

  fun clear(self) =
    self.head = NoNode
    self.tail = NoNode
    self.size = 0

  fun reverse(self) -> List =
    current = self.head
    while current != NoNode
      match current
        HasNode(node) =>
          next = node.next
          node.next = node.prev
          node.prev = next
          current = next
        NoNode =>
          break
    oldHead = self.head
    self.head = self.tail
    self.tail = oldHead
    self

fun optSum(o: Option) -> Int =
  match o
    Some(v) =>
      v
    None =>
      -1000
";

#[test]
fn listAddSetRemoveAtRemoveClearReverseAllWorkCorrectly() {
    let src = format!("{LIST_SOURCE_PREFIX}\
fun main() -> Int =
  l = List(head: NoNode, tail: NoNode, size: 0)
  l.add(1, 2, 3, 4, 5)
  a = l.length()
  b = optSum(l.get(0))
  c = optSum(l.get(4))
  oldVal = optSum(l.set(2, 30))
  d = optSum(l.get(2))
  l.removeAt(0)
  e = l.length()
  f = optSum(l.get(0))
  l.remove(30)
  g = l.length()
  l.reverse()
  h = optSum(l.get(0))
  l.clear()
  i = l.length()
  a + b + c + oldVal + d + e + f + g + h + i
");
    let source = parse(&src);
    let bytes = compileSource(&source).expect("compile failed");
    let result = wasmparser::validate(&bytes);
    assert!(result.is_ok(), "wasm validation failed: {:?}", result.err());
    // add(1,2,3,4,5): a=length=5, b=get(0)=1, c=get(4)=5
    // set(2,30): oldVal=3, d=get(2)=30 -> list [1,2,30,4,5]
    // removeAt(0): e=length=4, f=get(0)=2 -> list [2,30,4,5]
    // remove(30): g=length=3 -> list [2,4,5]
    // reverse(): h=get(0)=5 -> list [5,4,2]
    // clear(): i=length=0
    // 5+1+5+3+30+4+2+3+5+0 = 58
    assert_eq!(runMain(&bytes), 58);
}

/// Proves `removeAt`/`clear` actually detach nodes from the list (not just decrement
/// `size`) by removing every node one at a time via repeated `removeAt(0)` and
/// confirming the list ends up correctly empty and reports zero length — the removed
/// `Node`s (and their `NodeLink` links to each other) become unreachable and eligible
/// for collection once nothing in the list still points to them, since there's no
/// direct "assert this was garbage collected" hook available from a compiled
/// program's own execution.
#[test]
fn removingEveryNodeInALoopLeavesAnEmptyCorrectlyFunctioningList() {
    let src = format!("{LIST_SOURCE_PREFIX}\
fun main() -> Int =
  l = List(head: NoNode, tail: NoNode, size: 0)
  l.add(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
  i = 0
  while i < 10
    l.removeAt(0)
    i = i + 1
  afterLoopLength = l.length()
  isEmpty = optSum(l.get(0))
  l.add(42)
  afterReAdd = optSum(l.get(0))
  afterLoopLength + isEmpty + afterReAdd
");
    let source = parse(&src);
    let bytes = compileSource(&source).expect("compile failed");
    let result = wasmparser::validate(&bytes);
    assert!(result.is_ok(), "wasm validation failed: {:?}", result.err());
    // afterLoopLength=0, isEmpty(get(0) on empty list)=-1000, afterReAdd=42
    // 0 + -1000 + 42 = -958
    assert_eq!(runMain(&bytes), -958);
}