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