tova 0.7.0 → 0.8.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/bin/tova.js +192 -10
  2. package/package.json +2 -7
  3. package/src/analyzer/analyzer.js +134 -2
  4. package/src/analyzer/deploy-analyzer.js +44 -0
  5. package/src/codegen/base-codegen.js +1159 -10
  6. package/src/codegen/codegen.js +20 -0
  7. package/src/codegen/deploy-codegen.js +49 -0
  8. package/src/codegen/shared-codegen.js +5 -0
  9. package/src/codegen/wasm-codegen.js +6 -0
  10. package/src/config/edit-toml.js +6 -2
  11. package/src/config/git-resolver.js +128 -0
  12. package/src/config/lock-file.js +57 -0
  13. package/src/config/module-cache.js +58 -0
  14. package/src/config/module-entry.js +37 -0
  15. package/src/config/module-path.js +31 -0
  16. package/src/config/pkg-errors.js +62 -0
  17. package/src/config/resolve.js +17 -0
  18. package/src/config/resolver.js +139 -0
  19. package/src/config/search.js +28 -0
  20. package/src/config/semver.js +72 -0
  21. package/src/config/toml.js +48 -5
  22. package/src/deploy/deploy.js +217 -0
  23. package/src/deploy/infer.js +218 -0
  24. package/src/deploy/provision.js +311 -0
  25. package/src/lsp/server.js +482 -0
  26. package/src/parser/ast.js +24 -0
  27. package/src/parser/concurrency-ast.js +15 -0
  28. package/src/parser/concurrency-parser.js +236 -0
  29. package/src/parser/deploy-ast.js +37 -0
  30. package/src/parser/deploy-parser.js +132 -0
  31. package/src/parser/parser.js +21 -3
  32. package/src/parser/select-ast.js +39 -0
  33. package/src/registry/plugins/concurrency-plugin.js +32 -0
  34. package/src/registry/plugins/deploy-plugin.js +33 -0
  35. package/src/registry/register-all.js +4 -0
  36. package/src/stdlib/inline.js +35 -3
  37. package/src/stdlib/runtime-bridge.js +152 -0
  38. package/src/version.js +1 -1
@@ -1,7 +1,7 @@
1
1
  // Base code generation utilities shared across all codegen targets
2
2
  import { RESULT_OPTION, PROPAGATE, BUILTIN_NAMES, STDLIB_DEPS } from '../stdlib/inline.js';
3
3
  import { PIPE_TARGET } from '../parser/ast.js';
4
- import { compileWasmFunction, compileWasmModule, generateWasmGlue, generateMultiWasmGlue } from './wasm-codegen.js';
4
+ import { compileWasmFunction, compileWasmModule, generateWasmGlue, generateMultiWasmGlue, generateWasmBytesExport } from './wasm-codegen.js';
5
5
 
6
6
  export class BaseCodegen {
7
7
  constructor() {
@@ -25,6 +25,9 @@ export class BaseCodegen {
25
25
  this._sourceMappings = []; // {sourceLine, sourceCol, outputLine, outputCol, sourceFile?}
26
26
  this._outputLineCount = 0;
27
27
  this._sourceFile = null; // current source file for multi-file source maps
28
+ // @wasm function tracking for concurrent block WASM routing
29
+ this._wasmFunctions = new Map(); // funcName -> { node, wasmBytes }
30
+ this._needsRuntimeBridge = false; // set true when concurrent block uses WASM tasks
28
31
  // @fast mode for TypedArray optimization
29
32
  this._fastMode = false;
30
33
  this._typedArrayParams = new Map(); // paramName -> 'Float64Array' | 'Int32Array' | 'Uint8Array'
@@ -279,6 +282,8 @@ export class BaseCodegen {
279
282
  case 'TypeAlias': result = this.genTypeAlias(node); break;
280
283
  case 'DeferStatement': result = this.genDeferStatement(node); break;
281
284
  case 'WithStatement': result = this.genWithStatement(node); break;
285
+ case 'ConcurrentBlock': result = this.genConcurrentBlock(node); break;
286
+ case 'SelectStatement': result = this.genSelectStatement(node); break;
282
287
  case 'ExternDeclaration': result = `${this.i()}// extern: ${node.name}`; break;
283
288
  // Config declarations handled at block level — emit nothing in statement context
284
289
  case 'AiConfigDeclaration': result = ''; break;
@@ -353,6 +358,7 @@ export class BaseCodegen {
353
358
  case 'ColumnExpression': return this.genColumnExpression(node);
354
359
  case 'ColumnAssignment': return this.genColumnAssignment(node);
355
360
  case 'NegatedColumnExpression': return `{ __exclude: ${JSON.stringify(node.name)} }`;
361
+ case 'SpawnExpression': return this.genSpawnExpression(node);
356
362
  default:
357
363
  throw new Error(`Codegen: unknown expression type '${node.type}'`);
358
364
  }
@@ -491,7 +497,9 @@ export class BaseCodegen {
491
497
  return `${this.i()}const { ${props} } = ${this.genExpression(node.value)};`;
492
498
  }
493
499
  if (node.pattern.type === 'ArrayPattern' || node.pattern.type === 'TuplePattern') {
494
- for (const e of node.pattern.elements) if (e) this.declareVar(e);
500
+ for (const e of node.pattern.elements) {
501
+ if (e) this.declareVar(e.startsWith('...') ? e.slice(3) : e);
502
+ }
495
503
  const els = node.pattern.elements.map(e => e || '').join(', ');
496
504
  return `${this.i()}const [${els}] = ${this.genExpression(node.value)};`;
497
505
  }
@@ -572,9 +580,14 @@ export class BaseCodegen {
572
580
  // Track as user-defined to suppress stdlib version
573
581
  if (BUILTIN_NAMES.has(node.name)) this._userDefinedNames.add(node.name);
574
582
  const wasmBytes = compileWasmFunction(node);
575
- const glue = generateWasmGlue(node, wasmBytes);
583
+ // Track this WASM function for concurrent block routing
584
+ this._wasmFunctions.set(node.name, { node, wasmBytes });
576
585
  const exportPrefix = node.isPublic ? 'export ' : '';
577
- return `${this.i()}${exportPrefix}${glue}`;
586
+ // Emit bytes constant first, then glue that references it (avoids duplication)
587
+ const bytesExport = generateWasmBytesExport(node.name, wasmBytes);
588
+ const name = node.name;
589
+ const glue = `const ${name} = new WebAssembly.Instance(new WebAssembly.Module(__wasm_bytes_${name})).exports.${name};`;
590
+ return `${this.i()}${bytesExport}\n${this.i()}${exportPrefix}${glue}`;
578
591
  } catch (e) {
579
592
  // Fall back to JS if WASM compilation fails
580
593
  console.error(`Warning: @wasm compilation failed for '${node.name}': ${e.message}. Falling back to JS.`);
@@ -981,13 +994,22 @@ export class BaseCodegen {
981
994
  }
982
995
  }
983
996
 
997
+ // Pre-scan for scalar-replaceable Result/Option variables
998
+ const scalarMap = this._preAnalyzeScalarResults(regularStmts);
999
+ const prevScalar = this._scalarReplacements;
1000
+ this._scalarReplacements = scalarMap.size > 0 ? new Map([...(prevScalar || new Map()), ...scalarMap]) : prevScalar;
1001
+
984
1002
  for (let idx = 0; idx < regularStmts.length; idx++) {
985
1003
  if (bodySkipSet.has(idx)) continue;
986
1004
  const stmt = regularStmts[idx];
987
1005
  const isLast = idx === regularStmts.length - 1;
1006
+ // Scalar replacement: emit boolean+value pair instead of Result/Option allocation
1007
+ if (stmt.type === 'Assignment' && stmt.targets.length === 1 && typeof stmt.targets[0] === 'string' &&
1008
+ scalarMap.has(stmt.targets[0]) && scalarMap.get(stmt.targets[0]).assignIdx === idx) {
1009
+ lines.push(this._genScalarAssignment(stmt, scalarMap.get(stmt.targets[0])));
988
1010
  // Implicit return: last expression in function body
989
1011
  // Skip implicit return for known void/side-effect-only calls (print, assert, etc.)
990
- if (isLast && stmt.type === 'ExpressionStatement' && !this._isVoidCall(stmt.expression)) {
1012
+ } else if (isLast && stmt.type === 'ExpressionStatement' && !this._isVoidCall(stmt.expression)) {
991
1013
  // IIFE elimination: match/if as last expression in function body → direct returns
992
1014
  const expr = stmt.expression;
993
1015
  if (expr.type === 'MatchExpression' && !this._isSimpleMatch(expr)) {
@@ -1006,6 +1028,8 @@ export class BaseCodegen {
1006
1028
  }
1007
1029
  }
1008
1030
 
1031
+ this._scalarReplacements = prevScalar;
1032
+
1009
1033
  if (deferBodies.length > 0) {
1010
1034
  this.indent--;
1011
1035
  lines.push(`${this.i()}} finally {`);
@@ -1130,13 +1154,24 @@ export class BaseCodegen {
1130
1154
  lines.push(fillResult);
1131
1155
  }
1132
1156
  }
1157
+ // Pre-scan for scalar-replaceable Result/Option variables
1158
+ const scalarMap = this._preAnalyzeScalarResults(stmts);
1159
+ const prevScalar = this._scalarReplacements;
1160
+ this._scalarReplacements = scalarMap.size > 0 ? new Map([...(prevScalar || new Map()), ...scalarMap]) : prevScalar;
1133
1161
  for (let i = 0; i < stmts.length; i++) {
1134
1162
  if (skipSet.has(i)) continue;
1135
1163
  const s = stmts[i];
1136
- lines.push(this.generateStatement(s));
1164
+ // Scalar replacement: emit boolean+value pair instead of Result/Option allocation
1165
+ if (s.type === 'Assignment' && s.targets.length === 1 && typeof s.targets[0] === 'string' &&
1166
+ scalarMap.has(s.targets[0]) && scalarMap.get(s.targets[0]).assignIdx === i) {
1167
+ lines.push(this._genScalarAssignment(s, scalarMap.get(s.targets[0])));
1168
+ } else {
1169
+ lines.push(this.generateStatement(s));
1170
+ }
1137
1171
  // Dead code elimination: stop after unconditional return/break/continue
1138
1172
  if (s.type === 'ReturnStatement' || s.type === 'BreakStatement' || s.type === 'ContinueStatement') break;
1139
1173
  }
1174
+ this._scalarReplacements = prevScalar;
1140
1175
  return lines.join('\n');
1141
1176
  }
1142
1177
 
@@ -1433,6 +1468,41 @@ export class BaseCodegen {
1433
1468
  const fusedResult = this._tryFuseMapChain(node);
1434
1469
  if (fusedResult !== null) return fusedResult;
1435
1470
 
1471
+ // Compile-time devirtualization: Ok(x).unwrap() → x, Err(e).isOk() → false, etc.
1472
+ const devirt = this._tryDevirtualizeResultOption(node);
1473
+ if (devirt !== null) return devirt;
1474
+
1475
+ // Scalar replacement: intercept method calls on scalar-replaced Result/Option variables
1476
+ if (this._scalarReplacements && this._scalarReplacements.size > 0 &&
1477
+ node.callee.type === 'MemberExpression' && !node.callee.computed &&
1478
+ node.callee.object && node.callee.object.type === 'Identifier') {
1479
+ const varName = node.callee.object.name;
1480
+ const scalarInfo = this._scalarReplacements.get(varName);
1481
+ if (scalarInfo) {
1482
+ const okVar = `${varName}__ok`;
1483
+ const vVar = `${varName}__v`;
1484
+ const method = node.callee.property;
1485
+
1486
+ if (scalarInfo.kind === 'result') {
1487
+ switch (method) {
1488
+ case 'isOk': return okVar;
1489
+ case 'isErr': return `!${okVar}`;
1490
+ case 'unwrap': return vVar;
1491
+ case 'unwrapOr': return node.arguments.length > 0 ? `(${okVar} ? ${vVar} : ${this.genExpression(node.arguments[0])})` : vVar;
1492
+ case 'expect': return vVar;
1493
+ }
1494
+ } else if (scalarInfo.kind === 'option') {
1495
+ switch (method) {
1496
+ case 'isSome': return okVar;
1497
+ case 'isNone': return `!${okVar}`;
1498
+ case 'unwrap': return vVar;
1499
+ case 'unwrapOr': return node.arguments.length > 0 ? `(${okVar} ? ${vVar} : ${this.genExpression(node.arguments[0])})` : vVar;
1500
+ case 'expect': return vVar;
1501
+ }
1502
+ }
1503
+ }
1504
+ }
1505
+
1436
1506
  // Transform Foo.new(...) → new Foo(...)
1437
1507
  if (node.callee.type === 'MemberExpression' && !node.callee.computed && node.callee.property === 'new') {
1438
1508
  const obj = this.genExpression(node.callee.object);
@@ -1583,6 +1653,207 @@ export class BaseCodegen {
1583
1653
  return result;
1584
1654
  }
1585
1655
 
1656
+ // ─── Compile-time devirtualization for Result/Option ──────────
1657
+ // When the codegen sees Ok(val).method(), Err(val).method(), Some(val).method(),
1658
+ // or None.method(), it knows the exact type at compile time. Instead of allocating
1659
+ // a Result/Option object and calling a method, inline the method body directly.
1660
+
1661
+ _tryDevirtualizeResultOption(node) {
1662
+ // Must be a CallExpression where callee is a non-computed MemberExpression
1663
+ if (node.callee.type !== 'MemberExpression' || node.callee.computed) return null;
1664
+ const method = node.callee.property;
1665
+ const obj = node.callee.object;
1666
+ const args = node.arguments;
1667
+
1668
+ // Case 1: None.method()
1669
+ if (obj.type === 'Identifier' && obj.name === 'None') {
1670
+ return this._devirtNone(method, args);
1671
+ }
1672
+
1673
+ // Case 2: Ok(val).method(), Err(val).method(), Some(val).method()
1674
+ if (obj.type === 'CallExpression' && obj.callee.type === 'Identifier' && obj.arguments.length === 1) {
1675
+ const ctor = obj.callee.name;
1676
+ const val = obj.arguments[0];
1677
+ if (ctor === 'Ok') return this._devirtOk(val, method, args);
1678
+ if (ctor === 'Err') return this._devirtErr(val, method, args);
1679
+ if (ctor === 'Some') return this._devirtSome(val, method, args);
1680
+ }
1681
+
1682
+ return null;
1683
+ }
1684
+
1685
+ _devirtOk(val, method, args) {
1686
+ switch (method) {
1687
+ case 'unwrap':
1688
+ case 'expect':
1689
+ return this.genExpression(val);
1690
+ case 'unwrapOr':
1691
+ return this.genExpression(val); // Ok ignores default
1692
+ case 'isOk':
1693
+ return 'true';
1694
+ case 'isErr':
1695
+ return 'false';
1696
+ case 'map': {
1697
+ if (!this._isSimpleLambda(args[0])) return null;
1698
+ const parts = this._extractLambdaParts(args[0]);
1699
+ if (!parts) return null;
1700
+ this._needsResultOption = true;
1701
+ return `Ok(${this._substituteParam(parts.bodyExpr, parts.paramName, this.genExpression(val))})`;
1702
+ }
1703
+ case 'flatMap': {
1704
+ if (!this._isSimpleLambda(args[0])) return null;
1705
+ const parts = this._extractLambdaParts(args[0]);
1706
+ if (!parts) return null;
1707
+ return this._substituteParam(parts.bodyExpr, parts.paramName, this.genExpression(val));
1708
+ }
1709
+ case 'mapErr':
1710
+ this._needsResultOption = true;
1711
+ return `Ok(${this.genExpression(val)})`; // passes through
1712
+ case 'or':
1713
+ this._needsResultOption = true;
1714
+ return `Ok(${this.genExpression(val)})`; // passes through
1715
+ case 'and':
1716
+ if (args[0]) return this.genExpression(args[0]);
1717
+ return null;
1718
+ default:
1719
+ return null;
1720
+ }
1721
+ }
1722
+
1723
+ _devirtErr(val, method, args) {
1724
+ switch (method) {
1725
+ case 'unwrapOr':
1726
+ if (args[0]) return this.genExpression(args[0]);
1727
+ return null;
1728
+ case 'isOk':
1729
+ return 'false';
1730
+ case 'isErr':
1731
+ return 'true';
1732
+ case 'unwrapErr':
1733
+ return this.genExpression(val);
1734
+ case 'map':
1735
+ this._needsResultOption = true;
1736
+ return `Err(${this.genExpression(val)})`; // passes through
1737
+ case 'flatMap':
1738
+ this._needsResultOption = true;
1739
+ return `Err(${this.genExpression(val)})`;
1740
+ case 'mapErr': {
1741
+ if (!this._isSimpleLambda(args[0])) return null;
1742
+ const parts = this._extractLambdaParts(args[0]);
1743
+ if (!parts) return null;
1744
+ this._needsResultOption = true;
1745
+ return `Err(${this._substituteParam(parts.bodyExpr, parts.paramName, this.genExpression(val))})`;
1746
+ }
1747
+ case 'or':
1748
+ if (args[0]) return this.genExpression(args[0]);
1749
+ return null;
1750
+ case 'and':
1751
+ this._needsResultOption = true;
1752
+ return `Err(${this.genExpression(val)})`;
1753
+ default:
1754
+ return null;
1755
+ }
1756
+ }
1757
+
1758
+ _devirtSome(val, method, args) {
1759
+ switch (method) {
1760
+ case 'unwrap':
1761
+ case 'expect':
1762
+ return this.genExpression(val);
1763
+ case 'unwrapOr':
1764
+ return this.genExpression(val); // Some ignores default
1765
+ case 'isSome':
1766
+ return 'true';
1767
+ case 'isNone':
1768
+ return 'false';
1769
+ case 'map': {
1770
+ if (!this._isSimpleLambda(args[0])) return null;
1771
+ const parts = this._extractLambdaParts(args[0]);
1772
+ if (!parts) return null;
1773
+ this._needsResultOption = true;
1774
+ return `Some(${this._substituteParam(parts.bodyExpr, parts.paramName, this.genExpression(val))})`;
1775
+ }
1776
+ case 'flatMap': {
1777
+ if (!this._isSimpleLambda(args[0])) return null;
1778
+ const parts = this._extractLambdaParts(args[0]);
1779
+ if (!parts) return null;
1780
+ return this._substituteParam(parts.bodyExpr, parts.paramName, this.genExpression(val));
1781
+ }
1782
+ case 'filter': {
1783
+ if (!this._isSimpleLambda(args[0])) return null;
1784
+ const parts = this._extractLambdaParts(args[0]);
1785
+ if (!parts) return null;
1786
+ this._needsResultOption = true;
1787
+ const valCode = this.genExpression(val);
1788
+ const predCode = this._substituteParam(parts.bodyExpr, parts.paramName, valCode);
1789
+ return `(${predCode} ? Some(${valCode}) : None)`;
1790
+ }
1791
+ case 'or':
1792
+ this._needsResultOption = true;
1793
+ return `Some(${this.genExpression(val)})`; // passes through
1794
+ case 'and':
1795
+ if (args[0]) return this.genExpression(args[0]);
1796
+ return null;
1797
+ default:
1798
+ return null;
1799
+ }
1800
+ }
1801
+
1802
+ _devirtNone(method, args) {
1803
+ switch (method) {
1804
+ case 'unwrapOr':
1805
+ if (args[0]) return this.genExpression(args[0]);
1806
+ return null;
1807
+ case 'isSome':
1808
+ return 'false';
1809
+ case 'isNone':
1810
+ return 'true';
1811
+ case 'map':
1812
+ this._needsResultOption = true;
1813
+ return 'None';
1814
+ case 'flatMap':
1815
+ this._needsResultOption = true;
1816
+ return 'None';
1817
+ case 'filter':
1818
+ this._needsResultOption = true;
1819
+ return 'None';
1820
+ case 'or':
1821
+ if (args[0]) return this.genExpression(args[0]);
1822
+ return null;
1823
+ case 'and':
1824
+ this._needsResultOption = true;
1825
+ return 'None';
1826
+ default:
1827
+ return null;
1828
+ }
1829
+ }
1830
+
1831
+ _isSimpleLambda(node) {
1832
+ if (!node) return false;
1833
+ if (node.type !== 'FunctionExpression' && node.type !== 'ArrowFunction' && node.type !== 'LambdaExpression') return false;
1834
+ const params = node.params || [];
1835
+ if (params.length !== 1) return false;
1836
+ return true;
1837
+ }
1838
+
1839
+ _extractLambdaParts(lambda) {
1840
+ const params = lambda.params || [];
1841
+ const paramName = typeof params[0] === 'string' ? params[0] : (params[0].name || null);
1842
+ if (!paramName) return null;
1843
+ let bodyExpr = lambda.body;
1844
+ if (bodyExpr && bodyExpr.type === 'BlockStatement') {
1845
+ if (bodyExpr.body.length === 1) {
1846
+ const s = bodyExpr.body[0];
1847
+ if (s.type === 'ExpressionStatement') bodyExpr = s.expression;
1848
+ else if (s.type === 'ReturnStatement' && s.value) bodyExpr = s.value;
1849
+ else return null;
1850
+ } else {
1851
+ return null;
1852
+ }
1853
+ }
1854
+ return { paramName, bodyExpr };
1855
+ }
1856
+
1586
1857
  // Inline known builtins to direct method calls, eliminating wrapper overhead.
1587
1858
  // Returns the inlined code string, or null if not inlineable.
1588
1859
  _tryInlineBuiltin(node) {
@@ -2185,15 +2456,23 @@ export class BaseCodegen {
2185
2456
 
2186
2457
  if (arm.pattern.type === 'WildcardPattern' || arm.pattern.type === 'BindingPattern') {
2187
2458
  if (idx === node.arms.length - 1 && !arm.guard) {
2188
- // Default case
2459
+ // Default case — wrap in else block if preceded by if/else-if arms
2460
+ if (idx > 0) {
2461
+ p.push(`${this.i()}else {\n`);
2462
+ this.indent++;
2463
+ }
2189
2464
  if (arm.pattern.type === 'BindingPattern') {
2190
2465
  p.push(`${this.i()}const ${arm.pattern.name} = ${tempVar};\n`);
2191
2466
  }
2192
2467
  if (arm.body.type === 'BlockStatement') {
2193
- p.push(this.genBlockBody(arm.body) + '\n');
2468
+ p.push(this._genBlockBodyReturns(arm.body) + '\n');
2194
2469
  } else {
2195
2470
  p.push(`${this.i()}return ${this.genExpression(arm.body)};\n`);
2196
2471
  }
2472
+ if (idx > 0) {
2473
+ this.indent--;
2474
+ p.push(`${this.i()}}\n`);
2475
+ }
2197
2476
  break;
2198
2477
  }
2199
2478
  }
@@ -2206,7 +2485,7 @@ export class BaseCodegen {
2206
2485
  p.push(this.genPatternBindings(arm.pattern, tempVar));
2207
2486
 
2208
2487
  if (arm.body.type === 'BlockStatement') {
2209
- p.push(this.genBlockBody(arm.body) + '\n');
2488
+ p.push(this._genBlockBodyReturns(arm.body) + '\n');
2210
2489
  } else {
2211
2490
  p.push(`${this.i()}return ${this.genExpression(arm.body)};\n`);
2212
2491
  }
@@ -2613,13 +2892,53 @@ export class BaseCodegen {
2613
2892
  if (pattern.type === 'BindingPattern') {
2614
2893
  cond = `((${pattern.name}) => ${this.genExpression(guard)})(${subject})`;
2615
2894
  } else {
2616
- cond = `(${cond}) && (${this.genExpression(guard)})`;
2895
+ // Collect bindings from sub-patterns (e.g. [1, n] if n < 2)
2896
+ const bindings = this._collectPatternBindings(pattern, subject);
2897
+ if (bindings.length > 0) {
2898
+ const params = bindings.map(b => b.name).join(', ');
2899
+ const args = bindings.map(b => b.accessor).join(', ');
2900
+ cond = `(${cond}) && ((${params}) => ${this.genExpression(guard)})(${args})`;
2901
+ } else {
2902
+ cond = `(${cond}) && (${this.genExpression(guard)})`;
2903
+ }
2617
2904
  }
2618
2905
  }
2619
2906
 
2620
2907
  return cond;
2621
2908
  }
2622
2909
 
2910
+ _collectPatternBindings(pattern, subject) {
2911
+ const bindings = [];
2912
+ switch (pattern.type) {
2913
+ case 'BindingPattern':
2914
+ bindings.push({ name: pattern.name, accessor: subject });
2915
+ break;
2916
+ case 'ArrayPattern':
2917
+ case 'TuplePattern':
2918
+ for (let i = 0; i < pattern.elements.length; i++) {
2919
+ const el = pattern.elements[i];
2920
+ if (el) bindings.push(...this._collectPatternBindings(el, `${subject}[${i}]`));
2921
+ }
2922
+ break;
2923
+ case 'VariantPattern': {
2924
+ const declaredFields = this._variantFields[pattern.name] || [];
2925
+ for (let i = 0; i < pattern.fields.length; i++) {
2926
+ const f = pattern.fields[i];
2927
+ const fieldName = typeof f === 'string' ? f : (f.type === 'BindingPattern' ? f.name : null);
2928
+ const propName = declaredFields[i] || fieldName || 'value';
2929
+ const accessor = `${subject}.${propName}`;
2930
+ if (typeof f === 'string') {
2931
+ bindings.push({ name: f, accessor });
2932
+ } else {
2933
+ bindings.push(...this._collectPatternBindings(f, accessor));
2934
+ }
2935
+ }
2936
+ break;
2937
+ }
2938
+ }
2939
+ return bindings;
2940
+ }
2941
+
2623
2942
  genPatternBindings(pattern, subject) {
2624
2943
  switch (pattern.type) {
2625
2944
  case 'BindingPattern':
@@ -3013,6 +3332,395 @@ export class BaseCodegen {
3013
3332
  return p.join('\n');
3014
3333
  }
3015
3334
 
3335
+ genConcurrentBlock(node) {
3336
+ const lines = [];
3337
+ const tasks = []; // { callCode, calleeName, spawn, isWasm }
3338
+ const assignments = [];
3339
+
3340
+ // Collect spawn tasks from body, preserving spawn node info
3341
+ for (const stmt of node.body) {
3342
+ if (stmt.type === 'Assignment' && stmt.values && stmt.values[0] && stmt.values[0].type === 'SpawnExpression') {
3343
+ const spawn = stmt.values[0];
3344
+ const calleeName = spawn.callee && spawn.callee.type === 'Identifier' ? spawn.callee.name : null;
3345
+ const callCode = `${this.genExpression(spawn.callee)}(${spawn.arguments.map(a => this.genExpression(a)).join(', ')})`;
3346
+ const isWasm = calleeName && this._wasmFunctions.has(calleeName);
3347
+ tasks.push({ callCode, calleeName, spawn, isWasm });
3348
+ assignments.push(stmt.targets[0]); // string variable name
3349
+ } else if (stmt.type === 'ExpressionStatement' && stmt.expression && stmt.expression.type === 'SpawnExpression') {
3350
+ const spawn = stmt.expression;
3351
+ const calleeName = spawn.callee && spawn.callee.type === 'Identifier' ? spawn.callee.name : null;
3352
+ const callCode = `${this.genExpression(spawn.callee)}(${spawn.arguments.map(a => this.genExpression(a)).join(', ')})`;
3353
+ const isWasm = calleeName && this._wasmFunctions.has(calleeName);
3354
+ tasks.push({ callCode, calleeName, spawn, isWasm });
3355
+ assignments.push(null); // fire-and-forget
3356
+ } else {
3357
+ // Non-spawn statement — generate normally
3358
+ lines.push(this.generateStatement(stmt));
3359
+ }
3360
+ }
3361
+
3362
+ this._needsResultOption = true;
3363
+
3364
+ if (tasks.length === 0) {
3365
+ return lines.join('\n');
3366
+ }
3367
+
3368
+ const base = this._concurrentCounter = (this._concurrentCounter || 0) + 1;
3369
+ const tempVars = tasks.map((_, i) => `__c${base}_${i}`);
3370
+
3371
+ // Classify tasks
3372
+ const wasmTasks = tasks.filter(t => t.isWasm);
3373
+ const allWasm = wasmTasks.length === tasks.length;
3374
+ const hasWasm = wasmTasks.length > 0;
3375
+
3376
+ // Check if all WASM tasks share the same function (shared module optimization)
3377
+ const allSameWasm = allWasm && wasmTasks.every(t => t.calleeName === wasmTasks[0].calleeName);
3378
+
3379
+ // --- WASM path: all tasks are @wasm functions ---
3380
+ if (allWasm && (node.mode === 'all' || node.mode === 'first' || node.mode === 'cancel_on_error' || (node.mode === 'timeout' && node.timeout))) {
3381
+ this._needsRuntimeBridge = true;
3382
+ const rtVar = `__tova_rt`;
3383
+ // Build task descriptors
3384
+ const taskDescs = tasks.map(t => {
3385
+ const argsCode = t.spawn.arguments.map(a => this.genExpression(a)).join(', ');
3386
+ return `{ wasm: __wasm_bytes_${t.calleeName}, func: ${JSON.stringify(t.calleeName)}, args: [${argsCode}] }`;
3387
+ });
3388
+ const taskArrayCode = `[${taskDescs.join(', ')}]`;
3389
+
3390
+ // Choose runtime function based on mode
3391
+ let rtFunc;
3392
+ let rtCallSuffix = '';
3393
+ if (node.mode === 'first') {
3394
+ rtFunc = 'concurrentWasmFirst';
3395
+ } else if (node.mode === 'cancel_on_error') {
3396
+ rtFunc = 'concurrentWasmCancelOnError';
3397
+ } else if (node.mode === 'timeout' && node.timeout) {
3398
+ rtFunc = 'concurrentWasmTimeout';
3399
+ rtCallSuffix = `, ${this.genExpression(node.timeout)}`;
3400
+ } else {
3401
+ rtFunc = allSameWasm ? 'concurrentWasmShared' : 'concurrentWasm';
3402
+ }
3403
+
3404
+ if (node.mode === 'first') {
3405
+ // First mode: single result from WASM, fallback to Promise.race
3406
+ const firstVar = `__first${base}`;
3407
+ lines.push(`${this.i()}let ${firstVar};`);
3408
+ lines.push(`${this.i()}if (typeof ${rtVar} !== 'undefined' && ${rtVar} && ${rtVar}.isRuntimeAvailable()) {`);
3409
+ lines.push(`${this.i()} ${firstVar} = { __result: new Ok(await ${rtVar}.${rtFunc}(${taskArrayCode})) };`);
3410
+ lines.push(`${this.i()}} else {`);
3411
+ // Fallback: Promise.race
3412
+ const acVar = `__ac${base}`;
3413
+ lines.push(`${this.i()} const ${acVar} = new AbortController();`);
3414
+ const fallbackFirst = tasks.map((t, i) =>
3415
+ `(async () => { try { const __r = await ${t.callCode}; ${acVar}.abort(); return { __idx: ${i}, __result: new Ok(__r) }; } catch(__e) { return { __idx: ${i}, __result: new Err(__e) }; } })()`
3416
+ );
3417
+ lines.push(`${this.i()} ${firstVar} = await Promise.race([`);
3418
+ for (let i = 0; i < fallbackFirst.length; i++) {
3419
+ lines.push(`${this.i()} ${fallbackFirst[i]}${i < fallbackFirst.length - 1 ? ',' : ''}`);
3420
+ }
3421
+ lines.push(`${this.i()} ]);`);
3422
+ lines.push(`${this.i()}}`);
3423
+ // Assign winner to all named variables
3424
+ for (let i = 0; i < assignments.length; i++) {
3425
+ if (assignments[i]) {
3426
+ this.declareVar(assignments[i]);
3427
+ lines.push(`${this.i()}const ${assignments[i]} = ${firstVar}.__result;`);
3428
+ }
3429
+ }
3430
+ return lines.join('\n');
3431
+ }
3432
+
3433
+ // All other WASM modes: array result
3434
+ lines.push(`${this.i()}let ${tempVars.join(', ')};`);
3435
+ lines.push(`${this.i()}if (typeof ${rtVar} !== 'undefined' && ${rtVar} && ${rtVar}.isRuntimeAvailable()) {`);
3436
+ if (node.mode === 'cancel_on_error') {
3437
+ // cancel_on_error: WASM function throws on first task error — wrap with try/catch
3438
+ // to provide per-task Err results consistent with JS fallback path
3439
+ lines.push(`${this.i()} try {`);
3440
+ lines.push(`${this.i()} const __wasm_results = await ${rtVar}.${rtFunc}(${taskArrayCode});`);
3441
+ lines.push(`${this.i()} [${tempVars.join(', ')}] = __wasm_results.map(__v => new Ok(__v));`);
3442
+ lines.push(`${this.i()} } catch (__wasm_err) {`);
3443
+ lines.push(`${this.i()} [${tempVars.join(', ')}] = Array(${tasks.length}).fill(new Err(__wasm_err));`);
3444
+ lines.push(`${this.i()} }`);
3445
+ } else if (node.mode === 'timeout' && node.timeout) {
3446
+ // timeout: WASM function throws on timeout — wrap with try/catch
3447
+ lines.push(`${this.i()} try {`);
3448
+ lines.push(`${this.i()} const __wasm_results = await ${rtVar}.${rtFunc}(${taskArrayCode}${rtCallSuffix});`);
3449
+ lines.push(`${this.i()} [${tempVars.join(', ')}] = __wasm_results.map(__v => new Ok(__v));`);
3450
+ lines.push(`${this.i()} } catch (__wasm_err) {`);
3451
+ lines.push(`${this.i()} [${tempVars.join(', ')}] = Array(${tasks.length}).fill(new Err(__wasm_err));`);
3452
+ lines.push(`${this.i()} }`);
3453
+ } else {
3454
+ lines.push(`${this.i()} const __wasm_results = await ${rtVar}.${rtFunc}(${taskArrayCode}${rtCallSuffix});`);
3455
+ lines.push(`${this.i()} [${tempVars.join(', ')}] = __wasm_results.map(__v => new Ok(__v));`);
3456
+ }
3457
+ lines.push(`${this.i()}} else {`);
3458
+ // Fallback: standard Promise.all path
3459
+ if (node.mode === 'cancel_on_error') {
3460
+ const acVar = `__ac${base}`;
3461
+ const abortedVar = `__aborted${base}`;
3462
+ lines.push(`${this.i()} const ${acVar} = new AbortController();`);
3463
+ lines.push(`${this.i()} const ${abortedVar} = new Promise((_, reject) => { ${acVar}.signal.addEventListener('abort', () => reject(new Error('cancelled')), { once: true }); });`);
3464
+ const fallbackExprs = tasks.map(t =>
3465
+ `(async () => { try { return new Ok(await Promise.race([${t.callCode}, ${abortedVar}])); } catch(__e) { if (!${acVar}.signal.aborted) ${acVar}.abort(); return new Err(__e); } })()`
3466
+ );
3467
+ lines.push(`${this.i()} [${tempVars.join(', ')}] = await Promise.all([`);
3468
+ for (let i = 0; i < fallbackExprs.length; i++) {
3469
+ lines.push(`${this.i()} ${fallbackExprs[i]}${i < fallbackExprs.length - 1 ? ',' : ''}`);
3470
+ }
3471
+ lines.push(`${this.i()} ]);`);
3472
+ } else if (node.mode === 'timeout' && node.timeout) {
3473
+ const timeoutMs = this.genExpression(node.timeout);
3474
+ const acVar = `__ac${base}`;
3475
+ const abortedVar = `__aborted${base}`;
3476
+ const timerVar = `__timer${base}`;
3477
+ lines.push(`${this.i()} const ${acVar} = new AbortController();`);
3478
+ lines.push(`${this.i()} const ${abortedVar} = new Promise((_, reject) => { ${acVar}.signal.addEventListener('abort', () => reject(new Error('concurrent timeout')), { once: true }); });`);
3479
+ lines.push(`${this.i()} const ${timerVar} = setTimeout(() => ${acVar}.abort(), ${timeoutMs});`);
3480
+ const fallbackExprs = tasks.map(t =>
3481
+ `(async () => { try { return new Ok(await Promise.race([${t.callCode}, ${abortedVar}])); } catch(__e) { return new Err(__e); } })()`
3482
+ );
3483
+ lines.push(`${this.i()} [${tempVars.join(', ')}] = await Promise.all([`);
3484
+ for (let i = 0; i < fallbackExprs.length; i++) {
3485
+ lines.push(`${this.i()} ${fallbackExprs[i]}${i < fallbackExprs.length - 1 ? ',' : ''}`);
3486
+ }
3487
+ lines.push(`${this.i()} ]);`);
3488
+ lines.push(`${this.i()} clearTimeout(${timerVar});`);
3489
+ } else {
3490
+ const fallbackExprs = tasks.map(t =>
3491
+ `(async () => { try { return new Ok(await ${t.callCode}); } catch(__e) { return new Err(__e); } })()`
3492
+ );
3493
+ lines.push(`${this.i()} [${tempVars.join(', ')}] = await Promise.all([`);
3494
+ for (let i = 0; i < fallbackExprs.length; i++) {
3495
+ lines.push(`${this.i()} ${fallbackExprs[i]}${i < fallbackExprs.length - 1 ? ',' : ''}`);
3496
+ }
3497
+ lines.push(`${this.i()} ]);`);
3498
+ }
3499
+ lines.push(`${this.i()}}`);
3500
+ } else {
3501
+ // --- JS path (with per-task WASM routing for mixed blocks) ---
3502
+ const taskExprs = tasks.map(t => {
3503
+ if (t.isWasm && hasWasm) {
3504
+ // Route individual WASM task through runtime with fallback
3505
+ this._needsRuntimeBridge = true;
3506
+ const argsCode = t.spawn.arguments.map(a => this.genExpression(a)).join(', ');
3507
+ return `(async () => { try { if (typeof __tova_rt !== 'undefined' && __tova_rt.isRuntimeAvailable()) { return new Ok(await __tova_rt.execWasm(__wasm_bytes_${t.calleeName}, ${JSON.stringify(t.calleeName)}, [${argsCode}])); } return new Ok(await ${t.callCode}); } catch(__e) { return new Err(__e); } })()`;
3508
+ }
3509
+ return `(async () => { try { return new Ok(await ${t.callCode}); } catch(__e) { return new Err(__e); } })()`;
3510
+ });
3511
+
3512
+ if (node.mode === 'timeout' && node.timeout) {
3513
+ const timeoutMs = this.genExpression(node.timeout);
3514
+ const acVar = `__ac${base}`;
3515
+ const abortedVar = `__aborted${base}`;
3516
+ const timerVar = `__timer${base}`;
3517
+ lines.push(`${this.i()}const ${acVar} = new AbortController();`);
3518
+ lines.push(`${this.i()}const ${abortedVar} = new Promise((_, reject) => { ${acVar}.signal.addEventListener('abort', () => reject(new Error('concurrent timeout')), { once: true }); });`);
3519
+ lines.push(`${this.i()}const ${timerVar} = setTimeout(() => ${acVar}.abort(), ${timeoutMs});`);
3520
+ const taskExprsTimeout = tasks.map(t => {
3521
+ if (t.isWasm && hasWasm) {
3522
+ this._needsRuntimeBridge = true;
3523
+ const argsCode = t.spawn.arguments.map(a => this.genExpression(a)).join(', ');
3524
+ return `(async () => { try { if (typeof __tova_rt !== 'undefined' && __tova_rt.isRuntimeAvailable()) { return new Ok(await Promise.race([__tova_rt.execWasm(__wasm_bytes_${t.calleeName}, ${JSON.stringify(t.calleeName)}, [${argsCode}]), ${abortedVar}])); } return new Ok(await Promise.race([${t.callCode}, ${abortedVar}])); } catch(__e) { return new Err(__e); } })()`;
3525
+ }
3526
+ return `(async () => { try { return new Ok(await Promise.race([${t.callCode}, ${abortedVar}])); } catch(__e) { return new Err(__e); } })()`;
3527
+ });
3528
+ lines.push(`${this.i()}const [${tempVars.join(', ')}] = await Promise.all([`);
3529
+ for (let i = 0; i < taskExprsTimeout.length; i++) {
3530
+ lines.push(`${this.i()} ${taskExprsTimeout[i]}${i < taskExprsTimeout.length - 1 ? ',' : ''}`);
3531
+ }
3532
+ lines.push(`${this.i()}]);`);
3533
+ lines.push(`${this.i()}clearTimeout(${timerVar});`);
3534
+ } else if (node.mode === 'cancel_on_error') {
3535
+ const acVar = `__ac${base}`;
3536
+ const abortedVar = `__aborted${base}`;
3537
+ lines.push(`${this.i()}const ${acVar} = new AbortController();`);
3538
+ lines.push(`${this.i()}const ${abortedVar} = new Promise((_, reject) => { ${acVar}.signal.addEventListener('abort', () => reject(new Error('cancelled')), { once: true }); });`);
3539
+ const taskExprsAbort = tasks.map(t => {
3540
+ if (t.isWasm && hasWasm) {
3541
+ this._needsRuntimeBridge = true;
3542
+ const argsCode = t.spawn.arguments.map(a => this.genExpression(a)).join(', ');
3543
+ return `(async () => { try { if (typeof __tova_rt !== 'undefined' && __tova_rt.isRuntimeAvailable()) { return new Ok(await Promise.race([__tova_rt.execWasm(__wasm_bytes_${t.calleeName}, ${JSON.stringify(t.calleeName)}, [${argsCode}]), ${abortedVar}])); } return new Ok(await Promise.race([${t.callCode}, ${abortedVar}])); } catch(__e) { if (!${acVar}.signal.aborted) ${acVar}.abort(); return new Err(__e); } })()`;
3544
+ }
3545
+ return `(async () => { try { return new Ok(await Promise.race([${t.callCode}, ${abortedVar}])); } catch(__e) { if (!${acVar}.signal.aborted) ${acVar}.abort(); return new Err(__e); } })()`;
3546
+ });
3547
+ lines.push(`${this.i()}const [${tempVars.join(', ')}] = await Promise.all([`);
3548
+ for (let i = 0; i < taskExprsAbort.length; i++) {
3549
+ lines.push(`${this.i()} ${taskExprsAbort[i]}${i < taskExprsAbort.length - 1 ? ',' : ''}`);
3550
+ }
3551
+ lines.push(`${this.i()}]);`);
3552
+ } else if (node.mode === 'first') {
3553
+ const acVar = `__ac${base}`;
3554
+ const firstVar = `__first${base}`;
3555
+ lines.push(`${this.i()}const ${acVar} = new AbortController();`);
3556
+ const taskExprsFirst = tasks.map((t, i) => {
3557
+ if (t.isWasm && hasWasm) {
3558
+ this._needsRuntimeBridge = true;
3559
+ const argsCode = t.spawn.arguments.map(a => this.genExpression(a)).join(', ');
3560
+ return `(async () => { try { let __r; if (typeof __tova_rt !== 'undefined' && __tova_rt.isRuntimeAvailable()) { __r = await __tova_rt.execWasm(__wasm_bytes_${t.calleeName}, ${JSON.stringify(t.calleeName)}, [${argsCode}]); } else { __r = await ${t.callCode}; } ${acVar}.abort(); return { __idx: ${i}, __result: new Ok(__r) }; } catch(__e) { return { __idx: ${i}, __result: new Err(__e) }; } })()`;
3561
+ }
3562
+ return `(async () => { try { const __r = await ${t.callCode}; ${acVar}.abort(); return { __idx: ${i}, __result: new Ok(__r) }; } catch(__e) { return { __idx: ${i}, __result: new Err(__e) }; } })()`;
3563
+ });
3564
+ lines.push(`${this.i()}const ${firstVar} = await Promise.race([`);
3565
+ for (let i = 0; i < taskExprsFirst.length; i++) {
3566
+ lines.push(`${this.i()} ${taskExprsFirst[i]}${i < taskExprsFirst.length - 1 ? ',' : ''}`);
3567
+ }
3568
+ lines.push(`${this.i()}]);`);
3569
+ // For first mode, assign the winner's result to all named variables
3570
+ for (let i = 0; i < assignments.length; i++) {
3571
+ if (assignments[i]) {
3572
+ this.declareVar(assignments[i]);
3573
+ lines.push(`${this.i()}const ${assignments[i]} = ${firstVar}.__result;`);
3574
+ }
3575
+ }
3576
+ return lines.join('\n'); // early return — skip the normal assignment loop
3577
+ } else {
3578
+ lines.push(`${this.i()}const [${tempVars.join(', ')}] = await Promise.all([`);
3579
+ for (let i = 0; i < taskExprs.length; i++) {
3580
+ lines.push(`${this.i()} ${taskExprs[i]}${i < taskExprs.length - 1 ? ',' : ''}`);
3581
+ }
3582
+ lines.push(`${this.i()}]);`);
3583
+ }
3584
+ }
3585
+
3586
+ // Assign results to named variables
3587
+ for (let i = 0; i < assignments.length; i++) {
3588
+ if (assignments[i]) {
3589
+ this.declareVar(assignments[i]);
3590
+ lines.push(`${this.i()}const ${assignments[i]} = ${tempVars[i]};`);
3591
+ }
3592
+ }
3593
+
3594
+ return lines.join('\n');
3595
+ }
3596
+
3597
+ genSelectStatement(node) {
3598
+ this._needsResultOption = true; // for Some/None in _tryReceive
3599
+ const base = this._selectCounter = (this._selectCounter || 0) + 1;
3600
+
3601
+ const hasDefault = node.cases.some(c => c.kind === 'default');
3602
+
3603
+ if (hasDefault) {
3604
+ return this._genSelectWithDefault(node, base);
3605
+ }
3606
+ return this._genSelectWithRace(node, base);
3607
+ }
3608
+
3609
+ _genSelectWithRace(node, base) {
3610
+ const lines = [];
3611
+ const selVar = `__sel${base}`;
3612
+ const promises = [];
3613
+
3614
+ for (let i = 0; i < node.cases.length; i++) {
3615
+ const c = node.cases[i];
3616
+ if (c.kind === 'receive') {
3617
+ const ch = this.genExpression(c.channel);
3618
+ promises.push(`${ch}.receive().then(__v => ({ __case: ${i}, __value: __v }))`);
3619
+ } else if (c.kind === 'send') {
3620
+ const ch = this.genExpression(c.channel);
3621
+ const val = this.genExpression(c.value);
3622
+ promises.push(`${ch}.send(${val}).then(() => ({ __case: ${i} }))`);
3623
+ } else if (c.kind === 'timeout') {
3624
+ const ms = this.genExpression(c.value);
3625
+ promises.push(`new Promise(__r => setTimeout(() => __r({ __case: ${i} }), ${ms}))`);
3626
+ }
3627
+ }
3628
+
3629
+ lines.push(`${this.i()}const ${selVar} = await Promise.race([`);
3630
+ for (let i = 0; i < promises.length; i++) {
3631
+ lines.push(`${this.i()} ${promises[i]}${i < promises.length - 1 ? ',' : ''}`);
3632
+ }
3633
+ lines.push(`${this.i()}]);`);
3634
+
3635
+ // Generate if/else chain (NOT switch — avoids break conflicts in loops)
3636
+ for (let i = 0; i < node.cases.length; i++) {
3637
+ const c = node.cases[i];
3638
+ const prefix = i === 0 ? 'if' : ' else if';
3639
+ lines.push(`${this.i()}${prefix} (${selVar}.__case === ${i}) {`);
3640
+ this.indent++;
3641
+ if (c.kind === 'receive' && c.binding) {
3642
+ this.declareVar(c.binding);
3643
+ lines.push(`${this.i()}const ${c.binding} = ${selVar}.__value.value;`);
3644
+ }
3645
+ for (const stmt of c.body) {
3646
+ lines.push(this.generateStatement(stmt));
3647
+ }
3648
+ this.indent--;
3649
+ lines.push(`${this.i()}}`);
3650
+ }
3651
+
3652
+ return lines.join('\n');
3653
+ }
3654
+
3655
+ _genSelectWithDefault(node, base) {
3656
+ const lines = [];
3657
+ const nonDefault = node.cases.filter(c => c.kind !== 'default');
3658
+ const defaultCase = node.cases.find(c => c.kind === 'default');
3659
+
3660
+ lines.push(`${this.i()}{`);
3661
+ this.indent++;
3662
+
3663
+ let depth = 0;
3664
+ for (let i = 0; i < nonDefault.length; i++) {
3665
+ const c = nonDefault[i];
3666
+ const tryVar = `__try${base}_${i}`;
3667
+
3668
+ if (c.kind === 'receive') {
3669
+ const ch = this.genExpression(c.channel);
3670
+ lines.push(`${this.i()}const ${tryVar} = ${ch}._tryReceive();`);
3671
+ lines.push(`${this.i()}if (${tryVar}.__tag === 'Some') {`);
3672
+ this.indent++;
3673
+ if (c.binding) {
3674
+ this.declareVar(c.binding);
3675
+ lines.push(`${this.i()}const ${c.binding} = ${tryVar}.value;`);
3676
+ }
3677
+ for (const stmt of c.body) {
3678
+ lines.push(this.generateStatement(stmt));
3679
+ }
3680
+ this.indent--;
3681
+ lines.push(`${this.i()}} else {`);
3682
+ this.indent++;
3683
+ depth++;
3684
+ } else if (c.kind === 'send') {
3685
+ const ch = this.genExpression(c.channel);
3686
+ const val = this.genExpression(c.value);
3687
+ lines.push(`${this.i()}const ${tryVar} = ${ch}._trySend(${val});`);
3688
+ lines.push(`${this.i()}if (${tryVar}) {`);
3689
+ this.indent++;
3690
+ for (const stmt of c.body) {
3691
+ lines.push(this.generateStatement(stmt));
3692
+ }
3693
+ this.indent--;
3694
+ lines.push(`${this.i()}} else {`);
3695
+ this.indent++;
3696
+ depth++;
3697
+ }
3698
+ }
3699
+
3700
+ // Default case body
3701
+ for (const stmt of defaultCase.body) {
3702
+ lines.push(this.generateStatement(stmt));
3703
+ }
3704
+
3705
+ // Close all else blocks
3706
+ for (let i = 0; i < depth; i++) {
3707
+ this.indent--;
3708
+ lines.push(`${this.i()}}`);
3709
+ }
3710
+
3711
+ this.indent--;
3712
+ lines.push(`${this.i()}}`);
3713
+
3714
+ return lines.join('\n');
3715
+ }
3716
+
3717
+ genSpawnExpression(node) {
3718
+ // Standalone spawn expression (not inside concurrent block codegen path)
3719
+ // This happens when spawn is used directly in an expression context
3720
+ const callCode = `${this.genExpression(node.callee)}(${node.arguments.map(a => this.genExpression(a)).join(', ')})`;
3721
+ return `(async () => { try { return new Ok(await ${callCode}); } catch(__e) { return new Err(__e); } })()`;
3722
+ }
3723
+
3016
3724
  // Check if a function body contains yield expressions (for generator detection)
3017
3725
  _containsYield(node) {
3018
3726
  if (!node) return false;
@@ -3036,4 +3744,445 @@ export class BaseCodegen {
3036
3744
  this._yieldCache.set(node, result);
3037
3745
  return result;
3038
3746
  }
3747
+
3748
+ // ─── Scalar replacement codegen for Result/Option ───────────
3749
+ // Generates boolean+value pairs instead of allocating Result/Option objects.
3750
+
3751
+ /**
3752
+ * Generates scalar-replaced code for an assignment like:
3753
+ * r = if cond { Ok(val) } else { Err(err) }
3754
+ * Output: let r__ok, r__v; if (cond) { r__ok = true; r__v = val; } else { ... }
3755
+ */
3756
+ _genScalarAssignment(node, info) {
3757
+ const varName = node.targets[0];
3758
+ const okVar = `${varName}__ok`;
3759
+ const vVar = `${varName}__v`;
3760
+ const ifExpr = node.values[0];
3761
+
3762
+ // Mark all three names as declared so later code doesn't redeclare
3763
+ this.declareVar(varName);
3764
+ this.declareVar(okVar);
3765
+ this.declareVar(vVar);
3766
+
3767
+ const parts = [];
3768
+ parts.push(`${this.i()}let ${okVar}, ${vVar};`);
3769
+
3770
+ // Build if/else chain
3771
+ let ifCode = `${this.i()}if (${this.genExpression(ifExpr.condition)}) {\n`;
3772
+ this.indent++;
3773
+ ifCode += this._genScalarBranch(ifExpr.consequent, okVar, vVar, info.kind);
3774
+ this.indent--;
3775
+ ifCode += `\n${this.i()}}`;
3776
+
3777
+ if (ifExpr.alternates) {
3778
+ for (const alt of ifExpr.alternates) {
3779
+ ifCode += ` else if (${this.genExpression(alt.condition)}) {\n`;
3780
+ this.indent++;
3781
+ ifCode += this._genScalarBranch(alt.body, okVar, vVar, info.kind);
3782
+ this.indent--;
3783
+ ifCode += `\n${this.i()}}`;
3784
+ }
3785
+ }
3786
+
3787
+ if (ifExpr.elseBody) {
3788
+ ifCode += ` else {\n`;
3789
+ this.indent++;
3790
+ ifCode += this._genScalarBranch(ifExpr.elseBody, okVar, vVar, info.kind);
3791
+ this.indent--;
3792
+ ifCode += `\n${this.i()}}`;
3793
+ }
3794
+
3795
+ parts.push(ifCode);
3796
+ return parts.join('\n');
3797
+ }
3798
+
3799
+ /**
3800
+ * Generates scalar assignments for one branch of an if expression.
3801
+ * Extracts the Ok/Err/Some/None constructor call from the last expression
3802
+ * and emits okVar = true/false; vVar = value;
3803
+ */
3804
+ _genScalarBranch(block, okVar, vVar, kind) {
3805
+ if (!block) return '';
3806
+ const stmts = block.type === 'BlockStatement' ? block.body : [block];
3807
+ const lines = [];
3808
+
3809
+ // Generate any statements before the last one (side effects)
3810
+ for (let i = 0; i < stmts.length - 1; i++) {
3811
+ lines.push(this.generateStatement(stmts[i]));
3812
+ }
3813
+
3814
+ // Last statement has the constructor
3815
+ const last = stmts[stmts.length - 1];
3816
+ let expr = last;
3817
+ if (last.type === 'ExpressionStatement') expr = last.expression;
3818
+ if (last.type === 'ReturnStatement' && last.value) expr = last.value;
3819
+
3820
+ if (expr.type === 'CallExpression' && expr.callee && expr.callee.type === 'Identifier') {
3821
+ const name = expr.callee.name;
3822
+ if (name === 'Ok' || name === 'Some') {
3823
+ lines.push(`${this.i()}${okVar} = true; ${vVar} = ${this.genExpression(expr.arguments[0])};`);
3824
+ } else if (name === 'Err') {
3825
+ lines.push(`${this.i()}${okVar} = false; ${vVar} = ${this.genExpression(expr.arguments[0])};`);
3826
+ }
3827
+ } else if (expr.type === 'Identifier' && expr.name === 'None') {
3828
+ lines.push(`${this.i()}${okVar} = false; ${vVar} = undefined;`);
3829
+ }
3830
+
3831
+ return lines.join('\n');
3832
+ }
3833
+
3834
+ // ─── Scalar replacement analysis for Result/Option ──────────
3835
+ // Detects local variables assigned from if-expressions that produce
3836
+ // Ok/Err or Some/None, where all subsequent usages are safe method
3837
+ // calls (isOk, unwrap, unwrapOr, etc.). Such variables can be
3838
+ // replaced with a boolean + value pair (zero allocation).
3839
+
3840
+ /**
3841
+ * Scans an array of statements for scalar-replaceable Result/Option
3842
+ * assignments. Returns Map<varName, {kind, assignIdx}>.
3843
+ */
3844
+ _preAnalyzeScalarResults(stmts) {
3845
+ const map = new Map();
3846
+ for (let i = 0; i < stmts.length; i++) {
3847
+ const stmt = stmts[i];
3848
+ // Must be a single-target, single-value assignment
3849
+ if (stmt.type !== 'Assignment' || stmt.targets.length !== 1 || stmt.values.length !== 1) continue;
3850
+ const target = stmt.targets[0];
3851
+ if (typeof target !== 'string') continue; // skip MemberExpression targets
3852
+ const value = stmt.values[0];
3853
+ // Value must be an IfExpression (or IfStatement used as expression)
3854
+ if (value.type !== 'IfExpression' && value.type !== 'IfStatement') continue;
3855
+ const kind = this._detectResultOptionIf(value);
3856
+ if (kind === null) continue;
3857
+ // Check all subsequent statements for safe usage
3858
+ let allSafe = true;
3859
+ let anyUsed = false;
3860
+ for (let j = i + 1; j < stmts.length; j++) {
3861
+ const usage = this._checkScalarSafeUsage(stmts[j], target, kind);
3862
+ if (usage === 'unsafe') { allSafe = false; break; }
3863
+ if (usage === 'used') anyUsed = true;
3864
+ }
3865
+ if (allSafe && anyUsed) {
3866
+ map.set(target, { kind, assignIdx: i });
3867
+ }
3868
+ }
3869
+ return map;
3870
+ }
3871
+
3872
+ /**
3873
+ * Checks if ALL branches of an if expression return Ok/Err or Some/None.
3874
+ * Returns 'result' | 'option' | null.
3875
+ */
3876
+ _detectResultOptionIf(ifExpr) {
3877
+ // Must have an else branch (otherwise result could be undefined)
3878
+ if (!ifExpr.elseBody) return null;
3879
+
3880
+ const types = [];
3881
+
3882
+ // Check consequent block
3883
+ types.push(this._getBlockResultType(ifExpr.consequent));
3884
+
3885
+ // Check elif alternates
3886
+ if (ifExpr.alternates) {
3887
+ for (const alt of ifExpr.alternates) {
3888
+ types.push(this._getBlockResultType(alt.body));
3889
+ }
3890
+ }
3891
+
3892
+ // Check else block
3893
+ types.push(this._getBlockResultType(ifExpr.elseBody));
3894
+
3895
+ // All must be non-null
3896
+ if (types.some(t => t === null)) return null;
3897
+
3898
+ // Check if all are Result constructors (Ok/Err)
3899
+ if (types.every(t => t === 'Ok' || t === 'Err')) return 'result';
3900
+
3901
+ // Check if all are Option constructors (Some/None)
3902
+ if (types.every(t => t === 'Some' || t === 'None')) return 'option';
3903
+
3904
+ return null;
3905
+ }
3906
+
3907
+ /**
3908
+ * Gets the constructor name from the last expression in a block.
3909
+ * Returns 'Ok' | 'Err' | 'Some' | 'None' | null.
3910
+ */
3911
+ _getBlockResultType(block) {
3912
+ if (!block) return null;
3913
+ const stmts = block.type === 'BlockStatement' ? block.body : [block];
3914
+ if (stmts.length === 0) return null;
3915
+ const last = stmts[stmts.length - 1];
3916
+ // Could be ExpressionStatement or bare expression (implicit return)
3917
+ let expr = last;
3918
+ if (last.type === 'ExpressionStatement') expr = last.expression;
3919
+ if (last.type === 'ReturnStatement' && last.value) expr = last.value;
3920
+ // Check for Ok(val), Err(val), Some(val) call
3921
+ if (expr.type === 'CallExpression' && expr.callee && expr.callee.type === 'Identifier') {
3922
+ const name = expr.callee.name;
3923
+ if (['Ok', 'Err', 'Some'].includes(name) && expr.arguments.length === 1) return name;
3924
+ }
3925
+ // Check for bare None identifier
3926
+ if (expr.type === 'Identifier' && expr.name === 'None') return 'None';
3927
+ return null;
3928
+ }
3929
+
3930
+ /**
3931
+ * Walks a statement checking all references to varName.
3932
+ * Returns 'used' | 'unsafe' | 'none'.
3933
+ */
3934
+ _checkScalarSafeUsage(stmt, varName, kind) {
3935
+ let found = false;
3936
+ let safe = true;
3937
+
3938
+ const SAFE_RESULT = new Set(['isOk', 'isErr', 'unwrap', 'unwrapOr', 'expect']);
3939
+ const SAFE_OPTION = new Set(['isSome', 'isNone', 'unwrap', 'unwrapOr', 'expect']);
3940
+ const safeSet = kind === 'result' ? SAFE_RESULT : SAFE_OPTION;
3941
+
3942
+ // Custom expression walker that handles method calls specially
3943
+ const walkExpr = (expr) => {
3944
+ if (!expr || !safe) return;
3945
+ // Check for varName.method() pattern FIRST
3946
+ if (expr.type === 'CallExpression' &&
3947
+ expr.callee && expr.callee.type === 'MemberExpression' &&
3948
+ !expr.callee.computed &&
3949
+ expr.callee.object && expr.callee.object.type === 'Identifier' &&
3950
+ expr.callee.object.name === varName) {
3951
+ if (safeSet.has(expr.callee.property)) {
3952
+ found = true;
3953
+ // Walk only the arguments (NOT the callee.object to avoid bare ref detection)
3954
+ for (const arg of expr.arguments) walkExpr(arg);
3955
+ return;
3956
+ }
3957
+ // Unsafe method call on the variable
3958
+ safe = false;
3959
+ return;
3960
+ }
3961
+ // Check for bare varName reference (not part of a safe method call)
3962
+ if (expr.type === 'Identifier' && expr.name === varName) {
3963
+ safe = false;
3964
+ return;
3965
+ }
3966
+ // Walk children
3967
+ switch (expr.type) {
3968
+ case 'BinaryExpression':
3969
+ case 'LogicalExpression':
3970
+ walkExpr(expr.left);
3971
+ walkExpr(expr.right);
3972
+ break;
3973
+ case 'UnaryExpression':
3974
+ walkExpr(expr.operand);
3975
+ break;
3976
+ case 'CallExpression':
3977
+ walkExpr(expr.callee);
3978
+ for (const arg of expr.arguments) walkExpr(arg);
3979
+ break;
3980
+ case 'MemberExpression':
3981
+ walkExpr(expr.object);
3982
+ if (expr.computed) walkExpr(expr.property);
3983
+ break;
3984
+ case 'ConditionalExpression':
3985
+ walkExpr(expr.condition);
3986
+ walkExpr(expr.consequent);
3987
+ walkExpr(expr.alternate);
3988
+ break;
3989
+ case 'ArrayLiteral':
3990
+ if (expr.elements) for (const el of expr.elements) walkExpr(el);
3991
+ break;
3992
+ case 'ObjectLiteral':
3993
+ if (expr.properties) for (const prop of expr.properties) walkExpr(prop.value);
3994
+ break;
3995
+ case 'TemplateLiteral':
3996
+ if (expr.expressions) for (const e of expr.expressions) walkExpr(e);
3997
+ break;
3998
+ // Lambdas/functions — don't descend (separate scope)
3999
+ case 'FunctionExpression':
4000
+ case 'ArrowFunction':
4001
+ case 'LambdaExpression':
4002
+ break;
4003
+ // Assignment expression
4004
+ case 'AssignmentExpression':
4005
+ walkExpr(expr.right);
4006
+ break;
4007
+ }
4008
+ };
4009
+
4010
+ // Walk statement dispatching to expression walker
4011
+ this._walkStmtForScalar(stmt, walkExpr);
4012
+
4013
+ if (!safe) return 'unsafe';
4014
+ return found ? 'used' : 'none';
4015
+ }
4016
+
4017
+ /**
4018
+ * Walks all expressions within a statement, calling walkExpr for each
4019
+ * top-level expression found. Used by _checkScalarSafeUsage.
4020
+ */
4021
+ _walkStmtForScalar(stmt, walkExpr) {
4022
+ if (!stmt) return;
4023
+ switch (stmt.type) {
4024
+ case 'ExpressionStatement':
4025
+ walkExpr(stmt.expression);
4026
+ break;
4027
+ case 'Assignment':
4028
+ case 'VarDeclaration':
4029
+ if (stmt.values) for (const v of stmt.values) walkExpr(v);
4030
+ if (stmt.targets) {
4031
+ for (const t of stmt.targets) {
4032
+ if (typeof t === 'object') walkExpr(t);
4033
+ }
4034
+ }
4035
+ break;
4036
+ case 'ReturnStatement':
4037
+ if (stmt.value) walkExpr(stmt.value);
4038
+ break;
4039
+ case 'IfStatement':
4040
+ case 'IfExpression':
4041
+ walkExpr(stmt.condition);
4042
+ this._walkBlockForScalar(stmt.consequent, walkExpr);
4043
+ if (stmt.alternates) {
4044
+ for (const alt of stmt.alternates) {
4045
+ walkExpr(alt.condition);
4046
+ this._walkBlockForScalar(alt.body, walkExpr);
4047
+ }
4048
+ }
4049
+ if (stmt.elseBody) this._walkBlockForScalar(stmt.elseBody, walkExpr);
4050
+ break;
4051
+ case 'ForStatement':
4052
+ walkExpr(stmt.iterable);
4053
+ this._walkBlockForScalar(stmt.body, walkExpr);
4054
+ break;
4055
+ case 'WhileStatement':
4056
+ walkExpr(stmt.condition);
4057
+ this._walkBlockForScalar(stmt.body, walkExpr);
4058
+ break;
4059
+ case 'CallExpression':
4060
+ walkExpr(stmt);
4061
+ break;
4062
+ default:
4063
+ if (stmt.expression) walkExpr(stmt.expression);
4064
+ break;
4065
+ }
4066
+ }
4067
+
4068
+ /**
4069
+ * Walks all statements in a block, dispatching to _walkStmtForScalar.
4070
+ */
4071
+ _walkBlockForScalar(block, walkExpr) {
4072
+ if (!block) return;
4073
+ const stmts = block.type === 'BlockStatement' ? block.body : [block];
4074
+ for (const s of stmts) this._walkStmtForScalar(s, walkExpr);
4075
+ }
4076
+
4077
+ /**
4078
+ * General-purpose expression walker. Calls callback(node) for every
4079
+ * expression node in the tree.
4080
+ */
4081
+ _walkExpressions(node, callback) {
4082
+ if (!node) return;
4083
+ callback(node);
4084
+ switch (node.type) {
4085
+ case 'BinaryExpression':
4086
+ case 'LogicalExpression':
4087
+ this._walkExpressions(node.left, callback);
4088
+ this._walkExpressions(node.right, callback);
4089
+ break;
4090
+ case 'UnaryExpression':
4091
+ this._walkExpressions(node.operand, callback);
4092
+ break;
4093
+ case 'CallExpression':
4094
+ this._walkExpressions(node.callee, callback);
4095
+ for (const arg of node.arguments) this._walkExpressions(arg, callback);
4096
+ break;
4097
+ case 'MemberExpression':
4098
+ this._walkExpressions(node.object, callback);
4099
+ if (node.computed) this._walkExpressions(node.property, callback);
4100
+ break;
4101
+ case 'ConditionalExpression':
4102
+ this._walkExpressions(node.condition, callback);
4103
+ this._walkExpressions(node.consequent, callback);
4104
+ this._walkExpressions(node.alternate, callback);
4105
+ break;
4106
+ case 'ArrayLiteral':
4107
+ if (node.elements) for (const el of node.elements) this._walkExpressions(el, callback);
4108
+ break;
4109
+ case 'ObjectLiteral':
4110
+ if (node.properties) for (const prop of node.properties) this._walkExpressions(prop.value, callback);
4111
+ break;
4112
+ case 'TemplateLiteral':
4113
+ if (node.expressions) for (const expr of node.expressions) this._walkExpressions(expr, callback);
4114
+ break;
4115
+ // Lambdas/functions — don't descend (separate scope)
4116
+ case 'FunctionExpression':
4117
+ case 'ArrowFunction':
4118
+ case 'LambdaExpression':
4119
+ break;
4120
+ // Leaf nodes — no children
4121
+ case 'Identifier':
4122
+ case 'NumberLiteral':
4123
+ case 'StringLiteral':
4124
+ case 'BooleanLiteral':
4125
+ case 'NilLiteral':
4126
+ break;
4127
+ }
4128
+ }
4129
+
4130
+ /**
4131
+ * Walks all expressions within a statement.
4132
+ */
4133
+ _walkStatementExpressions(stmt, callback) {
4134
+ if (!stmt) return;
4135
+ switch (stmt.type) {
4136
+ case 'ExpressionStatement':
4137
+ this._walkExpressions(stmt.expression, callback);
4138
+ break;
4139
+ case 'Assignment':
4140
+ case 'VarDeclaration':
4141
+ if (stmt.values) for (const v of stmt.values) this._walkExpressions(v, callback);
4142
+ if (stmt.targets) {
4143
+ for (const t of stmt.targets) {
4144
+ if (typeof t === 'object') this._walkExpressions(t, callback);
4145
+ }
4146
+ }
4147
+ break;
4148
+ case 'ReturnStatement':
4149
+ if (stmt.value) this._walkExpressions(stmt.value, callback);
4150
+ break;
4151
+ case 'IfStatement':
4152
+ case 'IfExpression':
4153
+ this._walkExpressions(stmt.condition, callback);
4154
+ this._walkStatementBlock(stmt.consequent, callback);
4155
+ if (stmt.alternates) {
4156
+ for (const alt of stmt.alternates) {
4157
+ this._walkExpressions(alt.condition, callback);
4158
+ this._walkStatementBlock(alt.body, callback);
4159
+ }
4160
+ }
4161
+ if (stmt.elseBody) this._walkStatementBlock(stmt.elseBody, callback);
4162
+ break;
4163
+ case 'ForStatement':
4164
+ this._walkExpressions(stmt.iterable, callback);
4165
+ this._walkStatementBlock(stmt.body, callback);
4166
+ break;
4167
+ case 'WhileStatement':
4168
+ this._walkExpressions(stmt.condition, callback);
4169
+ this._walkStatementBlock(stmt.body, callback);
4170
+ break;
4171
+ case 'CallExpression':
4172
+ this._walkExpressions(stmt, callback);
4173
+ break;
4174
+ default:
4175
+ if (stmt.expression) this._walkExpressions(stmt.expression, callback);
4176
+ break;
4177
+ }
4178
+ }
4179
+
4180
+ /**
4181
+ * Walks all statements in a block for _walkStatementExpressions.
4182
+ */
4183
+ _walkStatementBlock(block, callback) {
4184
+ if (!block) return;
4185
+ const stmts = block.type === 'BlockStatement' ? block.body : [block];
4186
+ for (const s of stmts) this._walkStatementExpressions(s, callback);
4187
+ }
3039
4188
  }