tova 0.7.0 → 0.9.4

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 (59) hide show
  1. package/bin/tova.js +1312 -139
  2. package/package.json +8 -1
  3. package/src/analyzer/analyzer.js +539 -11
  4. package/src/analyzer/browser-analyzer.js +56 -8
  5. package/src/analyzer/deploy-analyzer.js +44 -0
  6. package/src/analyzer/scope.js +7 -0
  7. package/src/analyzer/server-analyzer.js +33 -1
  8. package/src/codegen/base-codegen.js +1296 -23
  9. package/src/codegen/browser-codegen.js +725 -20
  10. package/src/codegen/codegen.js +87 -5
  11. package/src/codegen/deploy-codegen.js +49 -0
  12. package/src/codegen/server-codegen.js +54 -6
  13. package/src/codegen/shared-codegen.js +5 -0
  14. package/src/codegen/theme-codegen.js +69 -0
  15. package/src/codegen/wasm-codegen.js +6 -0
  16. package/src/config/edit-toml.js +6 -2
  17. package/src/config/git-resolver.js +128 -0
  18. package/src/config/lock-file.js +57 -0
  19. package/src/config/module-cache.js +58 -0
  20. package/src/config/module-entry.js +37 -0
  21. package/src/config/module-path.js +63 -0
  22. package/src/config/pkg-errors.js +62 -0
  23. package/src/config/resolve.js +26 -0
  24. package/src/config/resolver.js +139 -0
  25. package/src/config/search.js +28 -0
  26. package/src/config/semver.js +72 -0
  27. package/src/config/toml.js +61 -6
  28. package/src/deploy/deploy.js +217 -0
  29. package/src/deploy/infer.js +218 -0
  30. package/src/deploy/provision.js +315 -0
  31. package/src/diagnostics/security-scorecard.js +111 -0
  32. package/src/lexer/lexer.js +18 -3
  33. package/src/lsp/server.js +482 -0
  34. package/src/parser/animate-ast.js +45 -0
  35. package/src/parser/ast.js +39 -0
  36. package/src/parser/browser-ast.js +19 -1
  37. package/src/parser/browser-parser.js +221 -4
  38. package/src/parser/concurrency-ast.js +15 -0
  39. package/src/parser/concurrency-parser.js +236 -0
  40. package/src/parser/deploy-ast.js +37 -0
  41. package/src/parser/deploy-parser.js +132 -0
  42. package/src/parser/parser.js +42 -5
  43. package/src/parser/select-ast.js +39 -0
  44. package/src/parser/theme-ast.js +29 -0
  45. package/src/parser/theme-parser.js +70 -0
  46. package/src/registry/plugins/concurrency-plugin.js +32 -0
  47. package/src/registry/plugins/deploy-plugin.js +33 -0
  48. package/src/registry/plugins/theme-plugin.js +20 -0
  49. package/src/registry/register-all.js +6 -0
  50. package/src/runtime/charts.js +547 -0
  51. package/src/runtime/embedded.js +6 -2
  52. package/src/runtime/reactivity.js +60 -0
  53. package/src/runtime/router.js +703 -295
  54. package/src/runtime/table.js +606 -33
  55. package/src/stdlib/inline.js +365 -10
  56. package/src/stdlib/runtime-bridge.js +152 -0
  57. package/src/stdlib/string.js +84 -2
  58. package/src/stdlib/validation.js +1 -1
  59. 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'
@@ -186,10 +189,25 @@ export class BaseCodegen {
186
189
  // Track a builtin and its transitive dependencies from the stdlib dependency graph
187
190
  _trackBuiltin(name) {
188
191
  this._usedBuiltins.add(name);
189
- const deps = STDLIB_DEPS[name];
190
- if (deps) {
191
- for (const dep of deps) {
192
- this._usedBuiltins.add(dep);
192
+ // Check if this name or any transitive dependency requires Result/Option
193
+ if (name === 'Ok' || name === 'Err' || name === 'Some' || name === 'None') {
194
+ this._needsResultOption = true;
195
+ }
196
+ // Resolve transitive deps using a queue (e.g., iter -> Seq -> Some/None)
197
+ const queue = [name];
198
+ while (queue.length > 0) {
199
+ const current = queue.pop();
200
+ const deps = STDLIB_DEPS[current];
201
+ if (deps) {
202
+ for (const dep of deps) {
203
+ if (!this._usedBuiltins.has(dep)) {
204
+ this._usedBuiltins.add(dep);
205
+ queue.push(dep); // resolve further transitive deps
206
+ }
207
+ if (dep === 'Ok' || dep === 'Err' || dep === 'Some' || dep === 'None') {
208
+ this._needsResultOption = true;
209
+ }
210
+ }
193
211
  }
194
212
  }
195
213
  }
@@ -279,6 +297,8 @@ export class BaseCodegen {
279
297
  case 'TypeAlias': result = this.genTypeAlias(node); break;
280
298
  case 'DeferStatement': result = this.genDeferStatement(node); break;
281
299
  case 'WithStatement': result = this.genWithStatement(node); break;
300
+ case 'ConcurrentBlock': result = this.genConcurrentBlock(node); break;
301
+ case 'SelectStatement': result = this.genSelectStatement(node); break;
282
302
  case 'ExternDeclaration': result = `${this.i()}// extern: ${node.name}`; break;
283
303
  // Config declarations handled at block level — emit nothing in statement context
284
304
  case 'AiConfigDeclaration': result = ''; break;
@@ -311,9 +331,12 @@ export class BaseCodegen {
311
331
  return this._paramSubstitutions.get(node.name);
312
332
  }
313
333
  // Track builtin identifier usage (e.g., None used without call)
334
+ // Use _trackBuiltin to resolve transitive deps (including Result/Option)
314
335
  if (BUILTIN_NAMES.has(node.name)) {
315
- this._usedBuiltins.add(node.name);
336
+ this._trackBuiltin(node.name);
316
337
  }
338
+ // Ok/Err/Some/None are in RESULT_OPTION, not BUILTIN_FUNCTIONS,
339
+ // so they need a separate check for _needsResultOption
317
340
  if (node.name === 'Ok' || node.name === 'Err' || node.name === 'Some' || node.name === 'None') {
318
341
  this._needsResultOption = true;
319
342
  }
@@ -353,6 +376,7 @@ export class BaseCodegen {
353
376
  case 'ColumnExpression': return this.genColumnExpression(node);
354
377
  case 'ColumnAssignment': return this.genColumnAssignment(node);
355
378
  case 'NegatedColumnExpression': return `{ __exclude: ${JSON.stringify(node.name)} }`;
379
+ case 'SpawnExpression': return this.genSpawnExpression(node);
356
380
  default:
357
381
  throw new Error(`Codegen: unknown expression type '${node.type}'`);
358
382
  }
@@ -491,7 +515,9 @@ export class BaseCodegen {
491
515
  return `${this.i()}const { ${props} } = ${this.genExpression(node.value)};`;
492
516
  }
493
517
  if (node.pattern.type === 'ArrayPattern' || node.pattern.type === 'TuplePattern') {
494
- for (const e of node.pattern.elements) if (e) this.declareVar(e);
518
+ for (const e of node.pattern.elements) {
519
+ if (e) this.declareVar(e.startsWith('...') ? e.slice(3) : e);
520
+ }
495
521
  const els = node.pattern.elements.map(e => e || '').join(', ');
496
522
  return `${this.i()}const [${els}] = ${this.genExpression(node.value)};`;
497
523
  }
@@ -572,9 +598,14 @@ export class BaseCodegen {
572
598
  // Track as user-defined to suppress stdlib version
573
599
  if (BUILTIN_NAMES.has(node.name)) this._userDefinedNames.add(node.name);
574
600
  const wasmBytes = compileWasmFunction(node);
575
- const glue = generateWasmGlue(node, wasmBytes);
601
+ // Track this WASM function for concurrent block routing
602
+ this._wasmFunctions.set(node.name, { node, wasmBytes });
576
603
  const exportPrefix = node.isPublic ? 'export ' : '';
577
- return `${this.i()}${exportPrefix}${glue}`;
604
+ // Emit bytes constant first, then glue that references it (avoids duplication)
605
+ const bytesExport = generateWasmBytesExport(node.name, wasmBytes);
606
+ const name = node.name;
607
+ const glue = `const ${name} = new WebAssembly.Instance(new WebAssembly.Module(__wasm_bytes_${name})).exports.${name};`;
608
+ return `${this.i()}${bytesExport}\n${this.i()}${exportPrefix}${glue}`;
578
609
  } catch (e) {
579
610
  // Fall back to JS if WASM compilation fails
580
611
  console.error(`Warning: @wasm compilation failed for '${node.name}': ${e.message}. Falling back to JS.`);
@@ -606,7 +637,14 @@ export class BaseCodegen {
606
637
  }).join(', ');
607
638
  }
608
639
 
640
+ _rewriteImportSource(node) {
641
+ if (node.source.startsWith('tova:')) {
642
+ node.source = './runtime/' + node.source.slice(5) + '.js';
643
+ }
644
+ }
645
+
609
646
  genImport(node) {
647
+ this._rewriteImportSource(node);
610
648
  for (const s of node.specifiers) this.declareVar(s.local);
611
649
  const specs = node.specifiers.map(s => {
612
650
  if (s.imported !== s.local) return `${s.imported} as ${s.local}`;
@@ -616,11 +654,13 @@ export class BaseCodegen {
616
654
  }
617
655
 
618
656
  genImportDefault(node) {
657
+ this._rewriteImportSource(node);
619
658
  this.declareVar(node.local);
620
659
  return `${this.i()}import ${node.local} from ${JSON.stringify(node.source)};`;
621
660
  }
622
661
 
623
662
  genImportWildcard(node) {
663
+ this._rewriteImportSource(node);
624
664
  this.declareVar(node.local);
625
665
  return `${this.i()}import * as ${node.local} from ${JSON.stringify(node.source)};`;
626
666
  }
@@ -981,13 +1021,22 @@ export class BaseCodegen {
981
1021
  }
982
1022
  }
983
1023
 
1024
+ // Pre-scan for scalar-replaceable Result/Option variables
1025
+ const scalarMap = this._preAnalyzeScalarResults(regularStmts);
1026
+ const prevScalar = this._scalarReplacements;
1027
+ this._scalarReplacements = scalarMap.size > 0 ? new Map([...(prevScalar || new Map()), ...scalarMap]) : prevScalar;
1028
+
984
1029
  for (let idx = 0; idx < regularStmts.length; idx++) {
985
1030
  if (bodySkipSet.has(idx)) continue;
986
1031
  const stmt = regularStmts[idx];
987
1032
  const isLast = idx === regularStmts.length - 1;
1033
+ // Scalar replacement: emit boolean+value pair instead of Result/Option allocation
1034
+ if (stmt.type === 'Assignment' && stmt.targets.length === 1 && typeof stmt.targets[0] === 'string' &&
1035
+ scalarMap.has(stmt.targets[0]) && scalarMap.get(stmt.targets[0]).assignIdx === idx) {
1036
+ lines.push(this._genScalarAssignment(stmt, scalarMap.get(stmt.targets[0])));
988
1037
  // Implicit return: last expression in function body
989
1038
  // Skip implicit return for known void/side-effect-only calls (print, assert, etc.)
990
- if (isLast && stmt.type === 'ExpressionStatement' && !this._isVoidCall(stmt.expression)) {
1039
+ } else if (isLast && stmt.type === 'ExpressionStatement' && !this._isVoidCall(stmt.expression)) {
991
1040
  // IIFE elimination: match/if as last expression in function body → direct returns
992
1041
  const expr = stmt.expression;
993
1042
  if (expr.type === 'MatchExpression' && !this._isSimpleMatch(expr)) {
@@ -1006,6 +1055,8 @@ export class BaseCodegen {
1006
1055
  }
1007
1056
  }
1008
1057
 
1058
+ this._scalarReplacements = prevScalar;
1059
+
1009
1060
  if (deferBodies.length > 0) {
1010
1061
  this.indent--;
1011
1062
  lines.push(`${this.i()}} finally {`);
@@ -1130,13 +1181,24 @@ export class BaseCodegen {
1130
1181
  lines.push(fillResult);
1131
1182
  }
1132
1183
  }
1184
+ // Pre-scan for scalar-replaceable Result/Option variables
1185
+ const scalarMap = this._preAnalyzeScalarResults(stmts);
1186
+ const prevScalar = this._scalarReplacements;
1187
+ this._scalarReplacements = scalarMap.size > 0 ? new Map([...(prevScalar || new Map()), ...scalarMap]) : prevScalar;
1133
1188
  for (let i = 0; i < stmts.length; i++) {
1134
1189
  if (skipSet.has(i)) continue;
1135
1190
  const s = stmts[i];
1136
- lines.push(this.generateStatement(s));
1191
+ // Scalar replacement: emit boolean+value pair instead of Result/Option allocation
1192
+ if (s.type === 'Assignment' && s.targets.length === 1 && typeof s.targets[0] === 'string' &&
1193
+ scalarMap.has(s.targets[0]) && scalarMap.get(s.targets[0]).assignIdx === i) {
1194
+ lines.push(this._genScalarAssignment(s, scalarMap.get(s.targets[0])));
1195
+ } else {
1196
+ lines.push(this.generateStatement(s));
1197
+ }
1137
1198
  // Dead code elimination: stop after unconditional return/break/continue
1138
1199
  if (s.type === 'ReturnStatement' || s.type === 'BreakStatement' || s.type === 'ContinueStatement') break;
1139
1200
  }
1201
+ this._scalarReplacements = prevScalar;
1140
1202
  return lines.join('\n');
1141
1203
  }
1142
1204
 
@@ -1433,6 +1495,41 @@ export class BaseCodegen {
1433
1495
  const fusedResult = this._tryFuseMapChain(node);
1434
1496
  if (fusedResult !== null) return fusedResult;
1435
1497
 
1498
+ // Compile-time devirtualization: Ok(x).unwrap() → x, Err(e).isOk() → false, etc.
1499
+ const devirt = this._tryDevirtualizeResultOption(node);
1500
+ if (devirt !== null) return devirt;
1501
+
1502
+ // Scalar replacement: intercept method calls on scalar-replaced Result/Option variables
1503
+ if (this._scalarReplacements && this._scalarReplacements.size > 0 &&
1504
+ node.callee.type === 'MemberExpression' && !node.callee.computed &&
1505
+ node.callee.object && node.callee.object.type === 'Identifier') {
1506
+ const varName = node.callee.object.name;
1507
+ const scalarInfo = this._scalarReplacements.get(varName);
1508
+ if (scalarInfo) {
1509
+ const okVar = `${varName}__ok`;
1510
+ const vVar = `${varName}__v`;
1511
+ const method = node.callee.property;
1512
+
1513
+ if (scalarInfo.kind === 'result') {
1514
+ switch (method) {
1515
+ case 'isOk': return okVar;
1516
+ case 'isErr': return `!${okVar}`;
1517
+ case 'unwrap': return vVar;
1518
+ case 'unwrapOr': return node.arguments.length > 0 ? `(${okVar} ? ${vVar} : ${this.genExpression(node.arguments[0])})` : vVar;
1519
+ case 'expect': return vVar;
1520
+ }
1521
+ } else if (scalarInfo.kind === 'option') {
1522
+ switch (method) {
1523
+ case 'isSome': return okVar;
1524
+ case 'isNone': return `!${okVar}`;
1525
+ case 'unwrap': return vVar;
1526
+ case 'unwrapOr': return node.arguments.length > 0 ? `(${okVar} ? ${vVar} : ${this.genExpression(node.arguments[0])})` : vVar;
1527
+ case 'expect': return vVar;
1528
+ }
1529
+ }
1530
+ }
1531
+ }
1532
+
1436
1533
  // Transform Foo.new(...) → new Foo(...)
1437
1534
  if (node.callee.type === 'MemberExpression' && !node.callee.computed && node.callee.property === 'new') {
1438
1535
  const obj = this.genExpression(node.callee.object);
@@ -1452,6 +1549,10 @@ export class BaseCodegen {
1452
1549
  if (node.callee.name === 'iter') {
1453
1550
  this._needsResultOption = true; // Seq.first()/find() return Option
1454
1551
  }
1552
+ // LRUCache.get() returns Option — needs Result/Option
1553
+ if (node.callee.name === 'LRUCache') {
1554
+ this._needsResultOption = true;
1555
+ }
1455
1556
 
1456
1557
  // Inline string/collection builtins to direct method calls
1457
1558
  const inlined = this._tryInlineBuiltin(node);
@@ -1464,19 +1565,24 @@ export class BaseCodegen {
1464
1565
  BUILTIN_NAMES.has(node.callee.object.name)) {
1465
1566
  const ns = node.callee.object.name;
1466
1567
  this._trackBuiltin(ns);
1467
- // Namespaces that depend on Ok/Err need Result/Option
1568
+ // Namespaces that depend on Ok/Err/Some/None need Result/Option
1468
1569
  const deps = STDLIB_DEPS[ns];
1469
- if (deps && (deps.includes('Ok') || deps.includes('Err'))) {
1570
+ if (deps && (deps.includes('Ok') || deps.includes('Err') || deps.includes('Some') || deps.includes('None'))) {
1470
1571
  this._needsResultOption = true;
1471
1572
  }
1472
1573
  }
1473
1574
 
1474
1575
  // Check for table operation calls with column expressions
1475
1576
  const hasColumnExprs = node.arguments.some(a => this._containsColumnExpr(a));
1476
- if (hasColumnExprs || (node.callee.type === 'Identifier' && ['agg', 'table_agg'].includes(node.callee.name))) {
1577
+ if (hasColumnExprs || (node.callee.type === 'Identifier' && ['agg', 'table_agg', 'window', 'table_window'].includes(node.callee.name))) {
1477
1578
  const tableArgs = this._genTableCallArgs(node);
1478
1579
  if (tableArgs) {
1479
- const callee = this.genExpression(node.callee);
1580
+ let callee = this.genExpression(node.callee);
1581
+ // Remap window → table_window to avoid browser global conflict
1582
+ if (node.callee.type === 'Identifier' && node.callee.name === 'window') {
1583
+ callee = 'table_window';
1584
+ this._usedBuiltins.add('table_window');
1585
+ }
1480
1586
  return `${callee}(${tableArgs.join(', ')})`;
1481
1587
  }
1482
1588
  }
@@ -1485,6 +1591,15 @@ export class BaseCodegen {
1485
1591
  const hasNamedArgs = node.arguments.some(a => a.type === 'NamedArgument');
1486
1592
 
1487
1593
  if (hasNamedArgs) {
1594
+ // Check if callee is a type/variant constructor — reorder named args to positional
1595
+ const calleeName = node.callee.type === 'Identifier' ? node.callee.name : null;
1596
+ const fieldOrder = calleeName ? this._variantFields[calleeName] : null;
1597
+
1598
+ if (fieldOrder) {
1599
+ const result = this._reorderNamedArgsToPositional(node.arguments, fieldOrder);
1600
+ return `${callee}(${result.join(', ')})`;
1601
+ }
1602
+
1488
1603
  const allNamed = node.arguments.every(a => a.type === 'NamedArgument');
1489
1604
  if (allNamed) {
1490
1605
  // All named args → single object argument
@@ -1583,6 +1698,207 @@ export class BaseCodegen {
1583
1698
  return result;
1584
1699
  }
1585
1700
 
1701
+ // ─── Compile-time devirtualization for Result/Option ──────────
1702
+ // When the codegen sees Ok(val).method(), Err(val).method(), Some(val).method(),
1703
+ // or None.method(), it knows the exact type at compile time. Instead of allocating
1704
+ // a Result/Option object and calling a method, inline the method body directly.
1705
+
1706
+ _tryDevirtualizeResultOption(node) {
1707
+ // Must be a CallExpression where callee is a non-computed MemberExpression
1708
+ if (node.callee.type !== 'MemberExpression' || node.callee.computed) return null;
1709
+ const method = node.callee.property;
1710
+ const obj = node.callee.object;
1711
+ const args = node.arguments;
1712
+
1713
+ // Case 1: None.method()
1714
+ if (obj.type === 'Identifier' && obj.name === 'None') {
1715
+ return this._devirtNone(method, args);
1716
+ }
1717
+
1718
+ // Case 2: Ok(val).method(), Err(val).method(), Some(val).method()
1719
+ if (obj.type === 'CallExpression' && obj.callee.type === 'Identifier' && obj.arguments.length === 1) {
1720
+ const ctor = obj.callee.name;
1721
+ const val = obj.arguments[0];
1722
+ if (ctor === 'Ok') return this._devirtOk(val, method, args);
1723
+ if (ctor === 'Err') return this._devirtErr(val, method, args);
1724
+ if (ctor === 'Some') return this._devirtSome(val, method, args);
1725
+ }
1726
+
1727
+ return null;
1728
+ }
1729
+
1730
+ _devirtOk(val, method, args) {
1731
+ switch (method) {
1732
+ case 'unwrap':
1733
+ case 'expect':
1734
+ return this.genExpression(val);
1735
+ case 'unwrapOr':
1736
+ return this.genExpression(val); // Ok ignores default
1737
+ case 'isOk':
1738
+ return 'true';
1739
+ case 'isErr':
1740
+ return 'false';
1741
+ case 'map': {
1742
+ if (!this._isSimpleLambda(args[0])) return null;
1743
+ const parts = this._extractLambdaParts(args[0]);
1744
+ if (!parts) return null;
1745
+ this._needsResultOption = true;
1746
+ return `Ok(${this._substituteParam(parts.bodyExpr, parts.paramName, this.genExpression(val))})`;
1747
+ }
1748
+ case 'flatMap': {
1749
+ if (!this._isSimpleLambda(args[0])) return null;
1750
+ const parts = this._extractLambdaParts(args[0]);
1751
+ if (!parts) return null;
1752
+ return this._substituteParam(parts.bodyExpr, parts.paramName, this.genExpression(val));
1753
+ }
1754
+ case 'mapErr':
1755
+ this._needsResultOption = true;
1756
+ return `Ok(${this.genExpression(val)})`; // passes through
1757
+ case 'or':
1758
+ this._needsResultOption = true;
1759
+ return `Ok(${this.genExpression(val)})`; // passes through
1760
+ case 'and':
1761
+ if (args[0]) return this.genExpression(args[0]);
1762
+ return null;
1763
+ default:
1764
+ return null;
1765
+ }
1766
+ }
1767
+
1768
+ _devirtErr(val, method, args) {
1769
+ switch (method) {
1770
+ case 'unwrapOr':
1771
+ if (args[0]) return this.genExpression(args[0]);
1772
+ return null;
1773
+ case 'isOk':
1774
+ return 'false';
1775
+ case 'isErr':
1776
+ return 'true';
1777
+ case 'unwrapErr':
1778
+ return this.genExpression(val);
1779
+ case 'map':
1780
+ this._needsResultOption = true;
1781
+ return `Err(${this.genExpression(val)})`; // passes through
1782
+ case 'flatMap':
1783
+ this._needsResultOption = true;
1784
+ return `Err(${this.genExpression(val)})`;
1785
+ case 'mapErr': {
1786
+ if (!this._isSimpleLambda(args[0])) return null;
1787
+ const parts = this._extractLambdaParts(args[0]);
1788
+ if (!parts) return null;
1789
+ this._needsResultOption = true;
1790
+ return `Err(${this._substituteParam(parts.bodyExpr, parts.paramName, this.genExpression(val))})`;
1791
+ }
1792
+ case 'or':
1793
+ if (args[0]) return this.genExpression(args[0]);
1794
+ return null;
1795
+ case 'and':
1796
+ this._needsResultOption = true;
1797
+ return `Err(${this.genExpression(val)})`;
1798
+ default:
1799
+ return null;
1800
+ }
1801
+ }
1802
+
1803
+ _devirtSome(val, method, args) {
1804
+ switch (method) {
1805
+ case 'unwrap':
1806
+ case 'expect':
1807
+ return this.genExpression(val);
1808
+ case 'unwrapOr':
1809
+ return this.genExpression(val); // Some ignores default
1810
+ case 'isSome':
1811
+ return 'true';
1812
+ case 'isNone':
1813
+ return 'false';
1814
+ case 'map': {
1815
+ if (!this._isSimpleLambda(args[0])) return null;
1816
+ const parts = this._extractLambdaParts(args[0]);
1817
+ if (!parts) return null;
1818
+ this._needsResultOption = true;
1819
+ return `Some(${this._substituteParam(parts.bodyExpr, parts.paramName, this.genExpression(val))})`;
1820
+ }
1821
+ case 'flatMap': {
1822
+ if (!this._isSimpleLambda(args[0])) return null;
1823
+ const parts = this._extractLambdaParts(args[0]);
1824
+ if (!parts) return null;
1825
+ return this._substituteParam(parts.bodyExpr, parts.paramName, this.genExpression(val));
1826
+ }
1827
+ case 'filter': {
1828
+ if (!this._isSimpleLambda(args[0])) return null;
1829
+ const parts = this._extractLambdaParts(args[0]);
1830
+ if (!parts) return null;
1831
+ this._needsResultOption = true;
1832
+ const valCode = this.genExpression(val);
1833
+ const predCode = this._substituteParam(parts.bodyExpr, parts.paramName, valCode);
1834
+ return `(${predCode} ? Some(${valCode}) : None)`;
1835
+ }
1836
+ case 'or':
1837
+ this._needsResultOption = true;
1838
+ return `Some(${this.genExpression(val)})`; // passes through
1839
+ case 'and':
1840
+ if (args[0]) return this.genExpression(args[0]);
1841
+ return null;
1842
+ default:
1843
+ return null;
1844
+ }
1845
+ }
1846
+
1847
+ _devirtNone(method, args) {
1848
+ switch (method) {
1849
+ case 'unwrapOr':
1850
+ if (args[0]) return this.genExpression(args[0]);
1851
+ return null;
1852
+ case 'isSome':
1853
+ return 'false';
1854
+ case 'isNone':
1855
+ return 'true';
1856
+ case 'map':
1857
+ this._needsResultOption = true;
1858
+ return 'None';
1859
+ case 'flatMap':
1860
+ this._needsResultOption = true;
1861
+ return 'None';
1862
+ case 'filter':
1863
+ this._needsResultOption = true;
1864
+ return 'None';
1865
+ case 'or':
1866
+ if (args[0]) return this.genExpression(args[0]);
1867
+ return null;
1868
+ case 'and':
1869
+ this._needsResultOption = true;
1870
+ return 'None';
1871
+ default:
1872
+ return null;
1873
+ }
1874
+ }
1875
+
1876
+ _isSimpleLambda(node) {
1877
+ if (!node) return false;
1878
+ if (node.type !== 'FunctionExpression' && node.type !== 'ArrowFunction' && node.type !== 'LambdaExpression') return false;
1879
+ const params = node.params || [];
1880
+ if (params.length !== 1) return false;
1881
+ return true;
1882
+ }
1883
+
1884
+ _extractLambdaParts(lambda) {
1885
+ const params = lambda.params || [];
1886
+ const paramName = typeof params[0] === 'string' ? params[0] : (params[0].name || null);
1887
+ if (!paramName) return null;
1888
+ let bodyExpr = lambda.body;
1889
+ if (bodyExpr && bodyExpr.type === 'BlockStatement') {
1890
+ if (bodyExpr.body.length === 1) {
1891
+ const s = bodyExpr.body[0];
1892
+ if (s.type === 'ExpressionStatement') bodyExpr = s.expression;
1893
+ else if (s.type === 'ReturnStatement' && s.value) bodyExpr = s.value;
1894
+ else return null;
1895
+ } else {
1896
+ return null;
1897
+ }
1898
+ }
1899
+ return { paramName, bodyExpr };
1900
+ }
1901
+
1586
1902
  // Inline known builtins to direct method calls, eliminating wrapper overhead.
1587
1903
  // Returns the inlined code string, or null if not inlineable.
1588
1904
  _tryInlineBuiltin(node) {
@@ -1699,13 +2015,18 @@ export class BaseCodegen {
1699
2015
  if (right.type === 'CallExpression') {
1700
2016
  // Check for table operations with column expressions
1701
2017
  const hasColumnExprs = right.arguments.some(a => this._containsColumnExpr(a));
1702
- if (hasColumnExprs || (right.callee.type === 'Identifier' && ['agg', 'table_agg'].includes(right.callee.name))) {
2018
+ if (hasColumnExprs || (right.callee.type === 'Identifier' && ['agg', 'table_agg', 'window', 'table_window'].includes(right.callee.name))) {
1703
2019
  const tableArgs = this._genTableCallArgs(right);
1704
2020
  if (tableArgs) {
1705
- const callee = this.genExpression(right.callee);
1706
- // Track builtin usage
2021
+ let callee = this.genExpression(right.callee);
2022
+ // Remap window → table_window to avoid browser global conflict
2023
+ if (right.callee.type === 'Identifier' && right.callee.name === 'window') {
2024
+ callee = 'table_window';
2025
+ this._usedBuiltins.add('table_window');
2026
+ }
2027
+ // Track builtin usage (with dependency resolution)
1707
2028
  if (right.callee.type === 'Identifier' && BUILTIN_NAMES.has(right.callee.name)) {
1708
- this._usedBuiltins.add(right.callee.name);
2029
+ this._trackBuiltin(right.callee.name);
1709
2030
  }
1710
2031
  return `${callee}(${[left, ...tableArgs].join(', ')})`;
1711
2032
  }
@@ -1857,6 +2178,33 @@ export class BaseCodegen {
1857
2178
  return this.genExpression(node);
1858
2179
  }
1859
2180
 
2181
+ // Reorder named args to positional order for type/variant constructors
2182
+ _reorderNamedArgsToPositional(args, fieldOrder) {
2183
+ const slots = new Array(fieldOrder.length);
2184
+ let positionalIndex = 0;
2185
+ // First pass: place positional args in order
2186
+ for (const arg of args) {
2187
+ if (arg.type !== 'NamedArgument') {
2188
+ slots[positionalIndex] = this.genExpression(arg);
2189
+ positionalIndex++;
2190
+ }
2191
+ }
2192
+ // Second pass: place named args in their field's slot
2193
+ for (const arg of args) {
2194
+ if (arg.type === 'NamedArgument') {
2195
+ const idx = fieldOrder.indexOf(arg.name);
2196
+ if (idx !== -1) {
2197
+ slots[idx] = this.genExpression(arg.value);
2198
+ } else {
2199
+ // Unknown field — emit as-is (analyzer will warn)
2200
+ slots[positionalIndex] = this.genExpression(arg.value);
2201
+ positionalIndex++;
2202
+ }
2203
+ }
2204
+ }
2205
+ return slots.filter(s => s !== undefined);
2206
+ }
2207
+
1860
2208
  // Override genCallExpression to handle table operations with column expressions
1861
2209
  _genTableCallArgs(node) {
1862
2210
  const calleeName = node.callee.type === 'Identifier' ? node.callee.name : null;
@@ -1955,6 +2303,52 @@ export class BaseCodegen {
1955
2303
  return parts;
1956
2304
  }
1957
2305
 
2306
+ // window() / table_window() — partition_by/order_by/desc are meta, rest are window fns
2307
+ if (calleeName === 'window' || calleeName === 'table_window') {
2308
+ const META_KEYS = new Set(['partition_by', 'order_by', 'desc']);
2309
+ const optParts = [];
2310
+ const winParts = [];
2311
+ for (const a of node.arguments) {
2312
+ if (a.type === 'NamedArgument') {
2313
+ if (META_KEYS.has(a.name)) {
2314
+ // Meta keys: partition_by → partition, order_by → order
2315
+ const key = a.name === 'partition_by' ? 'partition' : a.name === 'order_by' ? 'order' : a.name;
2316
+ if (a.name === 'desc') {
2317
+ optParts.push(`desc: ${this.genExpression(a.value)}`);
2318
+ } else if (this._containsColumnExpr(a.value)) {
2319
+ optParts.push(`${key}: (__row) => ${this._genColumnBody(a.value)}`);
2320
+ } else {
2321
+ optParts.push(`${key}: ${this.genExpression(a.value)}`);
2322
+ }
2323
+ } else {
2324
+ // Window function column
2325
+ const val = a.value;
2326
+ if (val.type === 'CallExpression' && val.callee.type === 'Identifier') {
2327
+ const fnName = val.callee.name;
2328
+ const winFn = `win_${fnName}`;
2329
+ const WIN_FNS = ['row_number', 'rank', 'dense_rank', 'percent_rank', 'ntile',
2330
+ 'lag', 'lead', 'first_value', 'last_value',
2331
+ 'running_sum', 'running_count', 'running_avg', 'running_min', 'running_max',
2332
+ 'moving_avg'];
2333
+ if (WIN_FNS.includes(fnName)) {
2334
+ this._usedBuiltins.add(winFn);
2335
+ const innerArgs = val.arguments.map(ia => {
2336
+ if (this._containsColumnExpr(ia)) {
2337
+ return `(__row) => ${this._genColumnBody(ia)}`;
2338
+ }
2339
+ return this.genExpression(ia);
2340
+ });
2341
+ winParts.push(`${a.name}: ${winFn}(${innerArgs.join(', ')})`);
2342
+ continue;
2343
+ }
2344
+ }
2345
+ winParts.push(`${a.name}: ${this.genExpression(a.value)}`);
2346
+ }
2347
+ }
2348
+ }
2349
+ return [`{ ${optParts.join(', ')} }`, `{ ${winParts.join(', ')} }`];
2350
+ }
2351
+
1958
2352
  // drop_nil/fill_nil — column expression compiles to string or lambda
1959
2353
  if (calleeName === 'drop_nil' || calleeName === 'fill_nil') {
1960
2354
  return node.arguments.map(a => {
@@ -2185,15 +2579,23 @@ export class BaseCodegen {
2185
2579
 
2186
2580
  if (arm.pattern.type === 'WildcardPattern' || arm.pattern.type === 'BindingPattern') {
2187
2581
  if (idx === node.arms.length - 1 && !arm.guard) {
2188
- // Default case
2582
+ // Default case — wrap in else block if preceded by if/else-if arms
2583
+ if (idx > 0) {
2584
+ p.push(`${this.i()}else {\n`);
2585
+ this.indent++;
2586
+ }
2189
2587
  if (arm.pattern.type === 'BindingPattern') {
2190
2588
  p.push(`${this.i()}const ${arm.pattern.name} = ${tempVar};\n`);
2191
2589
  }
2192
2590
  if (arm.body.type === 'BlockStatement') {
2193
- p.push(this.genBlockBody(arm.body) + '\n');
2591
+ p.push(this._genBlockBodyReturns(arm.body) + '\n');
2194
2592
  } else {
2195
2593
  p.push(`${this.i()}return ${this.genExpression(arm.body)};\n`);
2196
2594
  }
2595
+ if (idx > 0) {
2596
+ this.indent--;
2597
+ p.push(`${this.i()}}\n`);
2598
+ }
2197
2599
  break;
2198
2600
  }
2199
2601
  }
@@ -2206,7 +2608,7 @@ export class BaseCodegen {
2206
2608
  p.push(this.genPatternBindings(arm.pattern, tempVar));
2207
2609
 
2208
2610
  if (arm.body.type === 'BlockStatement') {
2209
- p.push(this.genBlockBody(arm.body) + '\n');
2611
+ p.push(this._genBlockBodyReturns(arm.body) + '\n');
2210
2612
  } else {
2211
2613
  p.push(`${this.i()}return ${this.genExpression(arm.body)};\n`);
2212
2614
  }
@@ -2613,13 +3015,53 @@ export class BaseCodegen {
2613
3015
  if (pattern.type === 'BindingPattern') {
2614
3016
  cond = `((${pattern.name}) => ${this.genExpression(guard)})(${subject})`;
2615
3017
  } else {
2616
- cond = `(${cond}) && (${this.genExpression(guard)})`;
3018
+ // Collect bindings from sub-patterns (e.g. [1, n] if n < 2)
3019
+ const bindings = this._collectPatternBindings(pattern, subject);
3020
+ if (bindings.length > 0) {
3021
+ const params = bindings.map(b => b.name).join(', ');
3022
+ const args = bindings.map(b => b.accessor).join(', ');
3023
+ cond = `(${cond}) && ((${params}) => ${this.genExpression(guard)})(${args})`;
3024
+ } else {
3025
+ cond = `(${cond}) && (${this.genExpression(guard)})`;
3026
+ }
2617
3027
  }
2618
3028
  }
2619
3029
 
2620
3030
  return cond;
2621
3031
  }
2622
3032
 
3033
+ _collectPatternBindings(pattern, subject) {
3034
+ const bindings = [];
3035
+ switch (pattern.type) {
3036
+ case 'BindingPattern':
3037
+ bindings.push({ name: pattern.name, accessor: subject });
3038
+ break;
3039
+ case 'ArrayPattern':
3040
+ case 'TuplePattern':
3041
+ for (let i = 0; i < pattern.elements.length; i++) {
3042
+ const el = pattern.elements[i];
3043
+ if (el) bindings.push(...this._collectPatternBindings(el, `${subject}[${i}]`));
3044
+ }
3045
+ break;
3046
+ case 'VariantPattern': {
3047
+ const declaredFields = this._variantFields[pattern.name] || [];
3048
+ for (let i = 0; i < pattern.fields.length; i++) {
3049
+ const f = pattern.fields[i];
3050
+ const fieldName = typeof f === 'string' ? f : (f.type === 'BindingPattern' ? f.name : null);
3051
+ const propName = declaredFields[i] || fieldName || 'value';
3052
+ const accessor = `${subject}.${propName}`;
3053
+ if (typeof f === 'string') {
3054
+ bindings.push({ name: f, accessor });
3055
+ } else {
3056
+ bindings.push(...this._collectPatternBindings(f, accessor));
3057
+ }
3058
+ }
3059
+ break;
3060
+ }
3061
+ }
3062
+ return bindings;
3063
+ }
3064
+
2623
3065
  genPatternBindings(pattern, subject) {
2624
3066
  switch (pattern.type) {
2625
3067
  case 'BindingPattern':
@@ -2819,6 +3261,7 @@ export class BaseCodegen {
2819
3261
  } else {
2820
3262
  this.declareVar(node.name);
2821
3263
  const fieldNames = node.variants.map(f => f.name);
3264
+ this._variantFields[node.name] = fieldNames;
2822
3265
  const params = fieldNames.join(', ');
2823
3266
  const obj = fieldNames.map(f => `${f}`).join(', ');
2824
3267
  lines.push(`${this.i()}${exportPrefix}function ${node.name}(${params}) { return Object.assign(Object.create(${node.name}.prototype), { ${obj} }); }`);
@@ -3013,6 +3456,395 @@ export class BaseCodegen {
3013
3456
  return p.join('\n');
3014
3457
  }
3015
3458
 
3459
+ genConcurrentBlock(node) {
3460
+ const lines = [];
3461
+ const tasks = []; // { callCode, calleeName, spawn, isWasm }
3462
+ const assignments = [];
3463
+
3464
+ // Collect spawn tasks from body, preserving spawn node info
3465
+ for (const stmt of node.body) {
3466
+ if (stmt.type === 'Assignment' && stmt.values && stmt.values[0] && stmt.values[0].type === 'SpawnExpression') {
3467
+ const spawn = stmt.values[0];
3468
+ const calleeName = spawn.callee && spawn.callee.type === 'Identifier' ? spawn.callee.name : null;
3469
+ const callCode = `${this.genExpression(spawn.callee)}(${spawn.arguments.map(a => this.genExpression(a)).join(', ')})`;
3470
+ const isWasm = calleeName && this._wasmFunctions.has(calleeName);
3471
+ tasks.push({ callCode, calleeName, spawn, isWasm });
3472
+ assignments.push(stmt.targets[0]); // string variable name
3473
+ } else if (stmt.type === 'ExpressionStatement' && stmt.expression && stmt.expression.type === 'SpawnExpression') {
3474
+ const spawn = stmt.expression;
3475
+ const calleeName = spawn.callee && spawn.callee.type === 'Identifier' ? spawn.callee.name : null;
3476
+ const callCode = `${this.genExpression(spawn.callee)}(${spawn.arguments.map(a => this.genExpression(a)).join(', ')})`;
3477
+ const isWasm = calleeName && this._wasmFunctions.has(calleeName);
3478
+ tasks.push({ callCode, calleeName, spawn, isWasm });
3479
+ assignments.push(null); // fire-and-forget
3480
+ } else {
3481
+ // Non-spawn statement — generate normally
3482
+ lines.push(this.generateStatement(stmt));
3483
+ }
3484
+ }
3485
+
3486
+ this._needsResultOption = true;
3487
+
3488
+ if (tasks.length === 0) {
3489
+ return lines.join('\n');
3490
+ }
3491
+
3492
+ const base = this._concurrentCounter = (this._concurrentCounter || 0) + 1;
3493
+ const tempVars = tasks.map((_, i) => `__c${base}_${i}`);
3494
+
3495
+ // Classify tasks
3496
+ const wasmTasks = tasks.filter(t => t.isWasm);
3497
+ const allWasm = wasmTasks.length === tasks.length;
3498
+ const hasWasm = wasmTasks.length > 0;
3499
+
3500
+ // Check if all WASM tasks share the same function (shared module optimization)
3501
+ const allSameWasm = allWasm && wasmTasks.every(t => t.calleeName === wasmTasks[0].calleeName);
3502
+
3503
+ // --- WASM path: all tasks are @wasm functions ---
3504
+ if (allWasm && (node.mode === 'all' || node.mode === 'first' || node.mode === 'cancel_on_error' || (node.mode === 'timeout' && node.timeout))) {
3505
+ this._needsRuntimeBridge = true;
3506
+ const rtVar = `__tova_rt`;
3507
+ // Build task descriptors
3508
+ const taskDescs = tasks.map(t => {
3509
+ const argsCode = t.spawn.arguments.map(a => this.genExpression(a)).join(', ');
3510
+ return `{ wasm: __wasm_bytes_${t.calleeName}, func: ${JSON.stringify(t.calleeName)}, args: [${argsCode}] }`;
3511
+ });
3512
+ const taskArrayCode = `[${taskDescs.join(', ')}]`;
3513
+
3514
+ // Choose runtime function based on mode
3515
+ let rtFunc;
3516
+ let rtCallSuffix = '';
3517
+ if (node.mode === 'first') {
3518
+ rtFunc = 'concurrentWasmFirst';
3519
+ } else if (node.mode === 'cancel_on_error') {
3520
+ rtFunc = 'concurrentWasmCancelOnError';
3521
+ } else if (node.mode === 'timeout' && node.timeout) {
3522
+ rtFunc = 'concurrentWasmTimeout';
3523
+ rtCallSuffix = `, ${this.genExpression(node.timeout)}`;
3524
+ } else {
3525
+ rtFunc = allSameWasm ? 'concurrentWasmShared' : 'concurrentWasm';
3526
+ }
3527
+
3528
+ if (node.mode === 'first') {
3529
+ // First mode: single result from WASM, fallback to Promise.race
3530
+ const firstVar = `__first${base}`;
3531
+ lines.push(`${this.i()}let ${firstVar};`);
3532
+ lines.push(`${this.i()}if (typeof ${rtVar} !== 'undefined' && ${rtVar} && ${rtVar}.isRuntimeAvailable()) {`);
3533
+ lines.push(`${this.i()} ${firstVar} = { __result: new Ok(await ${rtVar}.${rtFunc}(${taskArrayCode})) };`);
3534
+ lines.push(`${this.i()}} else {`);
3535
+ // Fallback: Promise.race
3536
+ const acVar = `__ac${base}`;
3537
+ lines.push(`${this.i()} const ${acVar} = new AbortController();`);
3538
+ const fallbackFirst = tasks.map((t, i) =>
3539
+ `(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) }; } })()`
3540
+ );
3541
+ lines.push(`${this.i()} ${firstVar} = await Promise.race([`);
3542
+ for (let i = 0; i < fallbackFirst.length; i++) {
3543
+ lines.push(`${this.i()} ${fallbackFirst[i]}${i < fallbackFirst.length - 1 ? ',' : ''}`);
3544
+ }
3545
+ lines.push(`${this.i()} ]);`);
3546
+ lines.push(`${this.i()}}`);
3547
+ // Assign winner to all named variables
3548
+ for (let i = 0; i < assignments.length; i++) {
3549
+ if (assignments[i]) {
3550
+ this.declareVar(assignments[i]);
3551
+ lines.push(`${this.i()}const ${assignments[i]} = ${firstVar}.__result;`);
3552
+ }
3553
+ }
3554
+ return lines.join('\n');
3555
+ }
3556
+
3557
+ // All other WASM modes: array result
3558
+ lines.push(`${this.i()}let ${tempVars.join(', ')};`);
3559
+ lines.push(`${this.i()}if (typeof ${rtVar} !== 'undefined' && ${rtVar} && ${rtVar}.isRuntimeAvailable()) {`);
3560
+ if (node.mode === 'cancel_on_error') {
3561
+ // cancel_on_error: WASM function throws on first task error — wrap with try/catch
3562
+ // to provide per-task Err results consistent with JS fallback path
3563
+ lines.push(`${this.i()} try {`);
3564
+ lines.push(`${this.i()} const __wasm_results = await ${rtVar}.${rtFunc}(${taskArrayCode});`);
3565
+ lines.push(`${this.i()} [${tempVars.join(', ')}] = __wasm_results.map(__v => new Ok(__v));`);
3566
+ lines.push(`${this.i()} } catch (__wasm_err) {`);
3567
+ lines.push(`${this.i()} [${tempVars.join(', ')}] = Array(${tasks.length}).fill(new Err(__wasm_err));`);
3568
+ lines.push(`${this.i()} }`);
3569
+ } else if (node.mode === 'timeout' && node.timeout) {
3570
+ // timeout: WASM function throws on timeout — wrap with try/catch
3571
+ lines.push(`${this.i()} try {`);
3572
+ lines.push(`${this.i()} const __wasm_results = await ${rtVar}.${rtFunc}(${taskArrayCode}${rtCallSuffix});`);
3573
+ lines.push(`${this.i()} [${tempVars.join(', ')}] = __wasm_results.map(__v => new Ok(__v));`);
3574
+ lines.push(`${this.i()} } catch (__wasm_err) {`);
3575
+ lines.push(`${this.i()} [${tempVars.join(', ')}] = Array(${tasks.length}).fill(new Err(__wasm_err));`);
3576
+ lines.push(`${this.i()} }`);
3577
+ } else {
3578
+ lines.push(`${this.i()} const __wasm_results = await ${rtVar}.${rtFunc}(${taskArrayCode}${rtCallSuffix});`);
3579
+ lines.push(`${this.i()} [${tempVars.join(', ')}] = __wasm_results.map(__v => new Ok(__v));`);
3580
+ }
3581
+ lines.push(`${this.i()}} else {`);
3582
+ // Fallback: standard Promise.all path
3583
+ if (node.mode === 'cancel_on_error') {
3584
+ const acVar = `__ac${base}`;
3585
+ const abortedVar = `__aborted${base}`;
3586
+ lines.push(`${this.i()} const ${acVar} = new AbortController();`);
3587
+ lines.push(`${this.i()} const ${abortedVar} = new Promise((_, reject) => { ${acVar}.signal.addEventListener('abort', () => reject(new Error('cancelled')), { once: true }); });`);
3588
+ const fallbackExprs = tasks.map(t =>
3589
+ `(async () => { try { return new Ok(await Promise.race([${t.callCode}, ${abortedVar}])); } catch(__e) { if (!${acVar}.signal.aborted) ${acVar}.abort(); return new Err(__e); } })()`
3590
+ );
3591
+ lines.push(`${this.i()} [${tempVars.join(', ')}] = await Promise.all([`);
3592
+ for (let i = 0; i < fallbackExprs.length; i++) {
3593
+ lines.push(`${this.i()} ${fallbackExprs[i]}${i < fallbackExprs.length - 1 ? ',' : ''}`);
3594
+ }
3595
+ lines.push(`${this.i()} ]);`);
3596
+ } else if (node.mode === 'timeout' && node.timeout) {
3597
+ const timeoutMs = this.genExpression(node.timeout);
3598
+ const acVar = `__ac${base}`;
3599
+ const abortedVar = `__aborted${base}`;
3600
+ const timerVar = `__timer${base}`;
3601
+ lines.push(`${this.i()} const ${acVar} = new AbortController();`);
3602
+ lines.push(`${this.i()} const ${abortedVar} = new Promise((_, reject) => { ${acVar}.signal.addEventListener('abort', () => reject(new Error('concurrent timeout')), { once: true }); });`);
3603
+ lines.push(`${this.i()} const ${timerVar} = setTimeout(() => ${acVar}.abort(), ${timeoutMs});`);
3604
+ const fallbackExprs = tasks.map(t =>
3605
+ `(async () => { try { return new Ok(await Promise.race([${t.callCode}, ${abortedVar}])); } catch(__e) { return new Err(__e); } })()`
3606
+ );
3607
+ lines.push(`${this.i()} [${tempVars.join(', ')}] = await Promise.all([`);
3608
+ for (let i = 0; i < fallbackExprs.length; i++) {
3609
+ lines.push(`${this.i()} ${fallbackExprs[i]}${i < fallbackExprs.length - 1 ? ',' : ''}`);
3610
+ }
3611
+ lines.push(`${this.i()} ]);`);
3612
+ lines.push(`${this.i()} clearTimeout(${timerVar});`);
3613
+ } else {
3614
+ const fallbackExprs = tasks.map(t =>
3615
+ `(async () => { try { return new Ok(await ${t.callCode}); } catch(__e) { return new Err(__e); } })()`
3616
+ );
3617
+ lines.push(`${this.i()} [${tempVars.join(', ')}] = await Promise.all([`);
3618
+ for (let i = 0; i < fallbackExprs.length; i++) {
3619
+ lines.push(`${this.i()} ${fallbackExprs[i]}${i < fallbackExprs.length - 1 ? ',' : ''}`);
3620
+ }
3621
+ lines.push(`${this.i()} ]);`);
3622
+ }
3623
+ lines.push(`${this.i()}}`);
3624
+ } else {
3625
+ // --- JS path (with per-task WASM routing for mixed blocks) ---
3626
+ const taskExprs = tasks.map(t => {
3627
+ if (t.isWasm && hasWasm) {
3628
+ // Route individual WASM task through runtime with fallback
3629
+ this._needsRuntimeBridge = true;
3630
+ const argsCode = t.spawn.arguments.map(a => this.genExpression(a)).join(', ');
3631
+ 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); } })()`;
3632
+ }
3633
+ return `(async () => { try { return new Ok(await ${t.callCode}); } catch(__e) { return new Err(__e); } })()`;
3634
+ });
3635
+
3636
+ if (node.mode === 'timeout' && node.timeout) {
3637
+ const timeoutMs = this.genExpression(node.timeout);
3638
+ const acVar = `__ac${base}`;
3639
+ const abortedVar = `__aborted${base}`;
3640
+ const timerVar = `__timer${base}`;
3641
+ lines.push(`${this.i()}const ${acVar} = new AbortController();`);
3642
+ lines.push(`${this.i()}const ${abortedVar} = new Promise((_, reject) => { ${acVar}.signal.addEventListener('abort', () => reject(new Error('concurrent timeout')), { once: true }); });`);
3643
+ lines.push(`${this.i()}const ${timerVar} = setTimeout(() => ${acVar}.abort(), ${timeoutMs});`);
3644
+ const taskExprsTimeout = tasks.map(t => {
3645
+ if (t.isWasm && hasWasm) {
3646
+ this._needsRuntimeBridge = true;
3647
+ const argsCode = t.spawn.arguments.map(a => this.genExpression(a)).join(', ');
3648
+ 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); } })()`;
3649
+ }
3650
+ return `(async () => { try { return new Ok(await Promise.race([${t.callCode}, ${abortedVar}])); } catch(__e) { return new Err(__e); } })()`;
3651
+ });
3652
+ lines.push(`${this.i()}const [${tempVars.join(', ')}] = await Promise.all([`);
3653
+ for (let i = 0; i < taskExprsTimeout.length; i++) {
3654
+ lines.push(`${this.i()} ${taskExprsTimeout[i]}${i < taskExprsTimeout.length - 1 ? ',' : ''}`);
3655
+ }
3656
+ lines.push(`${this.i()}]);`);
3657
+ lines.push(`${this.i()}clearTimeout(${timerVar});`);
3658
+ } else if (node.mode === 'cancel_on_error') {
3659
+ const acVar = `__ac${base}`;
3660
+ const abortedVar = `__aborted${base}`;
3661
+ lines.push(`${this.i()}const ${acVar} = new AbortController();`);
3662
+ lines.push(`${this.i()}const ${abortedVar} = new Promise((_, reject) => { ${acVar}.signal.addEventListener('abort', () => reject(new Error('cancelled')), { once: true }); });`);
3663
+ const taskExprsAbort = tasks.map(t => {
3664
+ if (t.isWasm && hasWasm) {
3665
+ this._needsRuntimeBridge = true;
3666
+ const argsCode = t.spawn.arguments.map(a => this.genExpression(a)).join(', ');
3667
+ 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); } })()`;
3668
+ }
3669
+ return `(async () => { try { return new Ok(await Promise.race([${t.callCode}, ${abortedVar}])); } catch(__e) { if (!${acVar}.signal.aborted) ${acVar}.abort(); return new Err(__e); } })()`;
3670
+ });
3671
+ lines.push(`${this.i()}const [${tempVars.join(', ')}] = await Promise.all([`);
3672
+ for (let i = 0; i < taskExprsAbort.length; i++) {
3673
+ lines.push(`${this.i()} ${taskExprsAbort[i]}${i < taskExprsAbort.length - 1 ? ',' : ''}`);
3674
+ }
3675
+ lines.push(`${this.i()}]);`);
3676
+ } else if (node.mode === 'first') {
3677
+ const acVar = `__ac${base}`;
3678
+ const firstVar = `__first${base}`;
3679
+ lines.push(`${this.i()}const ${acVar} = new AbortController();`);
3680
+ const taskExprsFirst = tasks.map((t, i) => {
3681
+ if (t.isWasm && hasWasm) {
3682
+ this._needsRuntimeBridge = true;
3683
+ const argsCode = t.spawn.arguments.map(a => this.genExpression(a)).join(', ');
3684
+ 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) }; } })()`;
3685
+ }
3686
+ 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) }; } })()`;
3687
+ });
3688
+ lines.push(`${this.i()}const ${firstVar} = await Promise.race([`);
3689
+ for (let i = 0; i < taskExprsFirst.length; i++) {
3690
+ lines.push(`${this.i()} ${taskExprsFirst[i]}${i < taskExprsFirst.length - 1 ? ',' : ''}`);
3691
+ }
3692
+ lines.push(`${this.i()}]);`);
3693
+ // For first mode, assign the winner's result to all named variables
3694
+ for (let i = 0; i < assignments.length; i++) {
3695
+ if (assignments[i]) {
3696
+ this.declareVar(assignments[i]);
3697
+ lines.push(`${this.i()}const ${assignments[i]} = ${firstVar}.__result;`);
3698
+ }
3699
+ }
3700
+ return lines.join('\n'); // early return — skip the normal assignment loop
3701
+ } else {
3702
+ lines.push(`${this.i()}const [${tempVars.join(', ')}] = await Promise.all([`);
3703
+ for (let i = 0; i < taskExprs.length; i++) {
3704
+ lines.push(`${this.i()} ${taskExprs[i]}${i < taskExprs.length - 1 ? ',' : ''}`);
3705
+ }
3706
+ lines.push(`${this.i()}]);`);
3707
+ }
3708
+ }
3709
+
3710
+ // Assign results to named variables
3711
+ for (let i = 0; i < assignments.length; i++) {
3712
+ if (assignments[i]) {
3713
+ this.declareVar(assignments[i]);
3714
+ lines.push(`${this.i()}const ${assignments[i]} = ${tempVars[i]};`);
3715
+ }
3716
+ }
3717
+
3718
+ return lines.join('\n');
3719
+ }
3720
+
3721
+ genSelectStatement(node) {
3722
+ this._needsResultOption = true; // for Some/None in _tryReceive
3723
+ const base = this._selectCounter = (this._selectCounter || 0) + 1;
3724
+
3725
+ const hasDefault = node.cases.some(c => c.kind === 'default');
3726
+
3727
+ if (hasDefault) {
3728
+ return this._genSelectWithDefault(node, base);
3729
+ }
3730
+ return this._genSelectWithRace(node, base);
3731
+ }
3732
+
3733
+ _genSelectWithRace(node, base) {
3734
+ const lines = [];
3735
+ const selVar = `__sel${base}`;
3736
+ const promises = [];
3737
+
3738
+ for (let i = 0; i < node.cases.length; i++) {
3739
+ const c = node.cases[i];
3740
+ if (c.kind === 'receive') {
3741
+ const ch = this.genExpression(c.channel);
3742
+ promises.push(`${ch}.receive().then(__v => ({ __case: ${i}, __value: __v }))`);
3743
+ } else if (c.kind === 'send') {
3744
+ const ch = this.genExpression(c.channel);
3745
+ const val = this.genExpression(c.value);
3746
+ promises.push(`${ch}.send(${val}).then(() => ({ __case: ${i} }))`);
3747
+ } else if (c.kind === 'timeout') {
3748
+ const ms = this.genExpression(c.value);
3749
+ promises.push(`new Promise(__r => setTimeout(() => __r({ __case: ${i} }), ${ms}))`);
3750
+ }
3751
+ }
3752
+
3753
+ lines.push(`${this.i()}const ${selVar} = await Promise.race([`);
3754
+ for (let i = 0; i < promises.length; i++) {
3755
+ lines.push(`${this.i()} ${promises[i]}${i < promises.length - 1 ? ',' : ''}`);
3756
+ }
3757
+ lines.push(`${this.i()}]);`);
3758
+
3759
+ // Generate if/else chain (NOT switch — avoids break conflicts in loops)
3760
+ for (let i = 0; i < node.cases.length; i++) {
3761
+ const c = node.cases[i];
3762
+ const prefix = i === 0 ? 'if' : ' else if';
3763
+ lines.push(`${this.i()}${prefix} (${selVar}.__case === ${i}) {`);
3764
+ this.indent++;
3765
+ if (c.kind === 'receive' && c.binding) {
3766
+ this.declareVar(c.binding);
3767
+ lines.push(`${this.i()}const ${c.binding} = ${selVar}.__value.value;`);
3768
+ }
3769
+ for (const stmt of c.body) {
3770
+ lines.push(this.generateStatement(stmt));
3771
+ }
3772
+ this.indent--;
3773
+ lines.push(`${this.i()}}`);
3774
+ }
3775
+
3776
+ return lines.join('\n');
3777
+ }
3778
+
3779
+ _genSelectWithDefault(node, base) {
3780
+ const lines = [];
3781
+ const nonDefault = node.cases.filter(c => c.kind !== 'default');
3782
+ const defaultCase = node.cases.find(c => c.kind === 'default');
3783
+
3784
+ lines.push(`${this.i()}{`);
3785
+ this.indent++;
3786
+
3787
+ let depth = 0;
3788
+ for (let i = 0; i < nonDefault.length; i++) {
3789
+ const c = nonDefault[i];
3790
+ const tryVar = `__try${base}_${i}`;
3791
+
3792
+ if (c.kind === 'receive') {
3793
+ const ch = this.genExpression(c.channel);
3794
+ lines.push(`${this.i()}const ${tryVar} = ${ch}._tryReceive();`);
3795
+ lines.push(`${this.i()}if (${tryVar}.__tag === 'Some') {`);
3796
+ this.indent++;
3797
+ if (c.binding) {
3798
+ this.declareVar(c.binding);
3799
+ lines.push(`${this.i()}const ${c.binding} = ${tryVar}.value;`);
3800
+ }
3801
+ for (const stmt of c.body) {
3802
+ lines.push(this.generateStatement(stmt));
3803
+ }
3804
+ this.indent--;
3805
+ lines.push(`${this.i()}} else {`);
3806
+ this.indent++;
3807
+ depth++;
3808
+ } else if (c.kind === 'send') {
3809
+ const ch = this.genExpression(c.channel);
3810
+ const val = this.genExpression(c.value);
3811
+ lines.push(`${this.i()}const ${tryVar} = ${ch}._trySend(${val});`);
3812
+ lines.push(`${this.i()}if (${tryVar}) {`);
3813
+ this.indent++;
3814
+ for (const stmt of c.body) {
3815
+ lines.push(this.generateStatement(stmt));
3816
+ }
3817
+ this.indent--;
3818
+ lines.push(`${this.i()}} else {`);
3819
+ this.indent++;
3820
+ depth++;
3821
+ }
3822
+ }
3823
+
3824
+ // Default case body
3825
+ for (const stmt of defaultCase.body) {
3826
+ lines.push(this.generateStatement(stmt));
3827
+ }
3828
+
3829
+ // Close all else blocks
3830
+ for (let i = 0; i < depth; i++) {
3831
+ this.indent--;
3832
+ lines.push(`${this.i()}}`);
3833
+ }
3834
+
3835
+ this.indent--;
3836
+ lines.push(`${this.i()}}`);
3837
+
3838
+ return lines.join('\n');
3839
+ }
3840
+
3841
+ genSpawnExpression(node) {
3842
+ // Standalone spawn expression (not inside concurrent block codegen path)
3843
+ // This happens when spawn is used directly in an expression context
3844
+ const callCode = `${this.genExpression(node.callee)}(${node.arguments.map(a => this.genExpression(a)).join(', ')})`;
3845
+ return `(async () => { try { return new Ok(await ${callCode}); } catch(__e) { return new Err(__e); } })()`;
3846
+ }
3847
+
3016
3848
  // Check if a function body contains yield expressions (for generator detection)
3017
3849
  _containsYield(node) {
3018
3850
  if (!node) return false;
@@ -3036,4 +3868,445 @@ export class BaseCodegen {
3036
3868
  this._yieldCache.set(node, result);
3037
3869
  return result;
3038
3870
  }
3871
+
3872
+ // ─── Scalar replacement codegen for Result/Option ───────────
3873
+ // Generates boolean+value pairs instead of allocating Result/Option objects.
3874
+
3875
+ /**
3876
+ * Generates scalar-replaced code for an assignment like:
3877
+ * r = if cond { Ok(val) } else { Err(err) }
3878
+ * Output: let r__ok, r__v; if (cond) { r__ok = true; r__v = val; } else { ... }
3879
+ */
3880
+ _genScalarAssignment(node, info) {
3881
+ const varName = node.targets[0];
3882
+ const okVar = `${varName}__ok`;
3883
+ const vVar = `${varName}__v`;
3884
+ const ifExpr = node.values[0];
3885
+
3886
+ // Mark all three names as declared so later code doesn't redeclare
3887
+ this.declareVar(varName);
3888
+ this.declareVar(okVar);
3889
+ this.declareVar(vVar);
3890
+
3891
+ const parts = [];
3892
+ parts.push(`${this.i()}let ${okVar}, ${vVar};`);
3893
+
3894
+ // Build if/else chain
3895
+ let ifCode = `${this.i()}if (${this.genExpression(ifExpr.condition)}) {\n`;
3896
+ this.indent++;
3897
+ ifCode += this._genScalarBranch(ifExpr.consequent, okVar, vVar, info.kind);
3898
+ this.indent--;
3899
+ ifCode += `\n${this.i()}}`;
3900
+
3901
+ if (ifExpr.alternates) {
3902
+ for (const alt of ifExpr.alternates) {
3903
+ ifCode += ` else if (${this.genExpression(alt.condition)}) {\n`;
3904
+ this.indent++;
3905
+ ifCode += this._genScalarBranch(alt.body, okVar, vVar, info.kind);
3906
+ this.indent--;
3907
+ ifCode += `\n${this.i()}}`;
3908
+ }
3909
+ }
3910
+
3911
+ if (ifExpr.elseBody) {
3912
+ ifCode += ` else {\n`;
3913
+ this.indent++;
3914
+ ifCode += this._genScalarBranch(ifExpr.elseBody, okVar, vVar, info.kind);
3915
+ this.indent--;
3916
+ ifCode += `\n${this.i()}}`;
3917
+ }
3918
+
3919
+ parts.push(ifCode);
3920
+ return parts.join('\n');
3921
+ }
3922
+
3923
+ /**
3924
+ * Generates scalar assignments for one branch of an if expression.
3925
+ * Extracts the Ok/Err/Some/None constructor call from the last expression
3926
+ * and emits okVar = true/false; vVar = value;
3927
+ */
3928
+ _genScalarBranch(block, okVar, vVar, kind) {
3929
+ if (!block) return '';
3930
+ const stmts = block.type === 'BlockStatement' ? block.body : [block];
3931
+ const lines = [];
3932
+
3933
+ // Generate any statements before the last one (side effects)
3934
+ for (let i = 0; i < stmts.length - 1; i++) {
3935
+ lines.push(this.generateStatement(stmts[i]));
3936
+ }
3937
+
3938
+ // Last statement has the constructor
3939
+ const last = stmts[stmts.length - 1];
3940
+ let expr = last;
3941
+ if (last.type === 'ExpressionStatement') expr = last.expression;
3942
+ if (last.type === 'ReturnStatement' && last.value) expr = last.value;
3943
+
3944
+ if (expr.type === 'CallExpression' && expr.callee && expr.callee.type === 'Identifier') {
3945
+ const name = expr.callee.name;
3946
+ if (name === 'Ok' || name === 'Some') {
3947
+ lines.push(`${this.i()}${okVar} = true; ${vVar} = ${this.genExpression(expr.arguments[0])};`);
3948
+ } else if (name === 'Err') {
3949
+ lines.push(`${this.i()}${okVar} = false; ${vVar} = ${this.genExpression(expr.arguments[0])};`);
3950
+ }
3951
+ } else if (expr.type === 'Identifier' && expr.name === 'None') {
3952
+ lines.push(`${this.i()}${okVar} = false; ${vVar} = undefined;`);
3953
+ }
3954
+
3955
+ return lines.join('\n');
3956
+ }
3957
+
3958
+ // ─── Scalar replacement analysis for Result/Option ──────────
3959
+ // Detects local variables assigned from if-expressions that produce
3960
+ // Ok/Err or Some/None, where all subsequent usages are safe method
3961
+ // calls (isOk, unwrap, unwrapOr, etc.). Such variables can be
3962
+ // replaced with a boolean + value pair (zero allocation).
3963
+
3964
+ /**
3965
+ * Scans an array of statements for scalar-replaceable Result/Option
3966
+ * assignments. Returns Map<varName, {kind, assignIdx}>.
3967
+ */
3968
+ _preAnalyzeScalarResults(stmts) {
3969
+ const map = new Map();
3970
+ for (let i = 0; i < stmts.length; i++) {
3971
+ const stmt = stmts[i];
3972
+ // Must be a single-target, single-value assignment
3973
+ if (stmt.type !== 'Assignment' || stmt.targets.length !== 1 || stmt.values.length !== 1) continue;
3974
+ const target = stmt.targets[0];
3975
+ if (typeof target !== 'string') continue; // skip MemberExpression targets
3976
+ const value = stmt.values[0];
3977
+ // Value must be an IfExpression (or IfStatement used as expression)
3978
+ if (value.type !== 'IfExpression' && value.type !== 'IfStatement') continue;
3979
+ const kind = this._detectResultOptionIf(value);
3980
+ if (kind === null) continue;
3981
+ // Check all subsequent statements for safe usage
3982
+ let allSafe = true;
3983
+ let anyUsed = false;
3984
+ for (let j = i + 1; j < stmts.length; j++) {
3985
+ const usage = this._checkScalarSafeUsage(stmts[j], target, kind);
3986
+ if (usage === 'unsafe') { allSafe = false; break; }
3987
+ if (usage === 'used') anyUsed = true;
3988
+ }
3989
+ if (allSafe && anyUsed) {
3990
+ map.set(target, { kind, assignIdx: i });
3991
+ }
3992
+ }
3993
+ return map;
3994
+ }
3995
+
3996
+ /**
3997
+ * Checks if ALL branches of an if expression return Ok/Err or Some/None.
3998
+ * Returns 'result' | 'option' | null.
3999
+ */
4000
+ _detectResultOptionIf(ifExpr) {
4001
+ // Must have an else branch (otherwise result could be undefined)
4002
+ if (!ifExpr.elseBody) return null;
4003
+
4004
+ const types = [];
4005
+
4006
+ // Check consequent block
4007
+ types.push(this._getBlockResultType(ifExpr.consequent));
4008
+
4009
+ // Check elif alternates
4010
+ if (ifExpr.alternates) {
4011
+ for (const alt of ifExpr.alternates) {
4012
+ types.push(this._getBlockResultType(alt.body));
4013
+ }
4014
+ }
4015
+
4016
+ // Check else block
4017
+ types.push(this._getBlockResultType(ifExpr.elseBody));
4018
+
4019
+ // All must be non-null
4020
+ if (types.some(t => t === null)) return null;
4021
+
4022
+ // Check if all are Result constructors (Ok/Err)
4023
+ if (types.every(t => t === 'Ok' || t === 'Err')) return 'result';
4024
+
4025
+ // Check if all are Option constructors (Some/None)
4026
+ if (types.every(t => t === 'Some' || t === 'None')) return 'option';
4027
+
4028
+ return null;
4029
+ }
4030
+
4031
+ /**
4032
+ * Gets the constructor name from the last expression in a block.
4033
+ * Returns 'Ok' | 'Err' | 'Some' | 'None' | null.
4034
+ */
4035
+ _getBlockResultType(block) {
4036
+ if (!block) return null;
4037
+ const stmts = block.type === 'BlockStatement' ? block.body : [block];
4038
+ if (stmts.length === 0) return null;
4039
+ const last = stmts[stmts.length - 1];
4040
+ // Could be ExpressionStatement or bare expression (implicit return)
4041
+ let expr = last;
4042
+ if (last.type === 'ExpressionStatement') expr = last.expression;
4043
+ if (last.type === 'ReturnStatement' && last.value) expr = last.value;
4044
+ // Check for Ok(val), Err(val), Some(val) call
4045
+ if (expr.type === 'CallExpression' && expr.callee && expr.callee.type === 'Identifier') {
4046
+ const name = expr.callee.name;
4047
+ if (['Ok', 'Err', 'Some'].includes(name) && expr.arguments.length === 1) return name;
4048
+ }
4049
+ // Check for bare None identifier
4050
+ if (expr.type === 'Identifier' && expr.name === 'None') return 'None';
4051
+ return null;
4052
+ }
4053
+
4054
+ /**
4055
+ * Walks a statement checking all references to varName.
4056
+ * Returns 'used' | 'unsafe' | 'none'.
4057
+ */
4058
+ _checkScalarSafeUsage(stmt, varName, kind) {
4059
+ let found = false;
4060
+ let safe = true;
4061
+
4062
+ const SAFE_RESULT = new Set(['isOk', 'isErr', 'unwrap', 'unwrapOr', 'expect']);
4063
+ const SAFE_OPTION = new Set(['isSome', 'isNone', 'unwrap', 'unwrapOr', 'expect']);
4064
+ const safeSet = kind === 'result' ? SAFE_RESULT : SAFE_OPTION;
4065
+
4066
+ // Custom expression walker that handles method calls specially
4067
+ const walkExpr = (expr) => {
4068
+ if (!expr || !safe) return;
4069
+ // Check for varName.method() pattern FIRST
4070
+ if (expr.type === 'CallExpression' &&
4071
+ expr.callee && expr.callee.type === 'MemberExpression' &&
4072
+ !expr.callee.computed &&
4073
+ expr.callee.object && expr.callee.object.type === 'Identifier' &&
4074
+ expr.callee.object.name === varName) {
4075
+ if (safeSet.has(expr.callee.property)) {
4076
+ found = true;
4077
+ // Walk only the arguments (NOT the callee.object to avoid bare ref detection)
4078
+ for (const arg of expr.arguments) walkExpr(arg);
4079
+ return;
4080
+ }
4081
+ // Unsafe method call on the variable
4082
+ safe = false;
4083
+ return;
4084
+ }
4085
+ // Check for bare varName reference (not part of a safe method call)
4086
+ if (expr.type === 'Identifier' && expr.name === varName) {
4087
+ safe = false;
4088
+ return;
4089
+ }
4090
+ // Walk children
4091
+ switch (expr.type) {
4092
+ case 'BinaryExpression':
4093
+ case 'LogicalExpression':
4094
+ walkExpr(expr.left);
4095
+ walkExpr(expr.right);
4096
+ break;
4097
+ case 'UnaryExpression':
4098
+ walkExpr(expr.operand);
4099
+ break;
4100
+ case 'CallExpression':
4101
+ walkExpr(expr.callee);
4102
+ for (const arg of expr.arguments) walkExpr(arg);
4103
+ break;
4104
+ case 'MemberExpression':
4105
+ walkExpr(expr.object);
4106
+ if (expr.computed) walkExpr(expr.property);
4107
+ break;
4108
+ case 'ConditionalExpression':
4109
+ walkExpr(expr.condition);
4110
+ walkExpr(expr.consequent);
4111
+ walkExpr(expr.alternate);
4112
+ break;
4113
+ case 'ArrayLiteral':
4114
+ if (expr.elements) for (const el of expr.elements) walkExpr(el);
4115
+ break;
4116
+ case 'ObjectLiteral':
4117
+ if (expr.properties) for (const prop of expr.properties) walkExpr(prop.value);
4118
+ break;
4119
+ case 'TemplateLiteral':
4120
+ if (expr.expressions) for (const e of expr.expressions) walkExpr(e);
4121
+ break;
4122
+ // Lambdas/functions — don't descend (separate scope)
4123
+ case 'FunctionExpression':
4124
+ case 'ArrowFunction':
4125
+ case 'LambdaExpression':
4126
+ break;
4127
+ // Assignment expression
4128
+ case 'AssignmentExpression':
4129
+ walkExpr(expr.right);
4130
+ break;
4131
+ }
4132
+ };
4133
+
4134
+ // Walk statement dispatching to expression walker
4135
+ this._walkStmtForScalar(stmt, walkExpr);
4136
+
4137
+ if (!safe) return 'unsafe';
4138
+ return found ? 'used' : 'none';
4139
+ }
4140
+
4141
+ /**
4142
+ * Walks all expressions within a statement, calling walkExpr for each
4143
+ * top-level expression found. Used by _checkScalarSafeUsage.
4144
+ */
4145
+ _walkStmtForScalar(stmt, walkExpr) {
4146
+ if (!stmt) return;
4147
+ switch (stmt.type) {
4148
+ case 'ExpressionStatement':
4149
+ walkExpr(stmt.expression);
4150
+ break;
4151
+ case 'Assignment':
4152
+ case 'VarDeclaration':
4153
+ if (stmt.values) for (const v of stmt.values) walkExpr(v);
4154
+ if (stmt.targets) {
4155
+ for (const t of stmt.targets) {
4156
+ if (typeof t === 'object') walkExpr(t);
4157
+ }
4158
+ }
4159
+ break;
4160
+ case 'ReturnStatement':
4161
+ if (stmt.value) walkExpr(stmt.value);
4162
+ break;
4163
+ case 'IfStatement':
4164
+ case 'IfExpression':
4165
+ walkExpr(stmt.condition);
4166
+ this._walkBlockForScalar(stmt.consequent, walkExpr);
4167
+ if (stmt.alternates) {
4168
+ for (const alt of stmt.alternates) {
4169
+ walkExpr(alt.condition);
4170
+ this._walkBlockForScalar(alt.body, walkExpr);
4171
+ }
4172
+ }
4173
+ if (stmt.elseBody) this._walkBlockForScalar(stmt.elseBody, walkExpr);
4174
+ break;
4175
+ case 'ForStatement':
4176
+ walkExpr(stmt.iterable);
4177
+ this._walkBlockForScalar(stmt.body, walkExpr);
4178
+ break;
4179
+ case 'WhileStatement':
4180
+ walkExpr(stmt.condition);
4181
+ this._walkBlockForScalar(stmt.body, walkExpr);
4182
+ break;
4183
+ case 'CallExpression':
4184
+ walkExpr(stmt);
4185
+ break;
4186
+ default:
4187
+ if (stmt.expression) walkExpr(stmt.expression);
4188
+ break;
4189
+ }
4190
+ }
4191
+
4192
+ /**
4193
+ * Walks all statements in a block, dispatching to _walkStmtForScalar.
4194
+ */
4195
+ _walkBlockForScalar(block, walkExpr) {
4196
+ if (!block) return;
4197
+ const stmts = block.type === 'BlockStatement' ? block.body : [block];
4198
+ for (const s of stmts) this._walkStmtForScalar(s, walkExpr);
4199
+ }
4200
+
4201
+ /**
4202
+ * General-purpose expression walker. Calls callback(node) for every
4203
+ * expression node in the tree.
4204
+ */
4205
+ _walkExpressions(node, callback) {
4206
+ if (!node) return;
4207
+ callback(node);
4208
+ switch (node.type) {
4209
+ case 'BinaryExpression':
4210
+ case 'LogicalExpression':
4211
+ this._walkExpressions(node.left, callback);
4212
+ this._walkExpressions(node.right, callback);
4213
+ break;
4214
+ case 'UnaryExpression':
4215
+ this._walkExpressions(node.operand, callback);
4216
+ break;
4217
+ case 'CallExpression':
4218
+ this._walkExpressions(node.callee, callback);
4219
+ for (const arg of node.arguments) this._walkExpressions(arg, callback);
4220
+ break;
4221
+ case 'MemberExpression':
4222
+ this._walkExpressions(node.object, callback);
4223
+ if (node.computed) this._walkExpressions(node.property, callback);
4224
+ break;
4225
+ case 'ConditionalExpression':
4226
+ this._walkExpressions(node.condition, callback);
4227
+ this._walkExpressions(node.consequent, callback);
4228
+ this._walkExpressions(node.alternate, callback);
4229
+ break;
4230
+ case 'ArrayLiteral':
4231
+ if (node.elements) for (const el of node.elements) this._walkExpressions(el, callback);
4232
+ break;
4233
+ case 'ObjectLiteral':
4234
+ if (node.properties) for (const prop of node.properties) this._walkExpressions(prop.value, callback);
4235
+ break;
4236
+ case 'TemplateLiteral':
4237
+ if (node.expressions) for (const expr of node.expressions) this._walkExpressions(expr, callback);
4238
+ break;
4239
+ // Lambdas/functions — don't descend (separate scope)
4240
+ case 'FunctionExpression':
4241
+ case 'ArrowFunction':
4242
+ case 'LambdaExpression':
4243
+ break;
4244
+ // Leaf nodes — no children
4245
+ case 'Identifier':
4246
+ case 'NumberLiteral':
4247
+ case 'StringLiteral':
4248
+ case 'BooleanLiteral':
4249
+ case 'NilLiteral':
4250
+ break;
4251
+ }
4252
+ }
4253
+
4254
+ /**
4255
+ * Walks all expressions within a statement.
4256
+ */
4257
+ _walkStatementExpressions(stmt, callback) {
4258
+ if (!stmt) return;
4259
+ switch (stmt.type) {
4260
+ case 'ExpressionStatement':
4261
+ this._walkExpressions(stmt.expression, callback);
4262
+ break;
4263
+ case 'Assignment':
4264
+ case 'VarDeclaration':
4265
+ if (stmt.values) for (const v of stmt.values) this._walkExpressions(v, callback);
4266
+ if (stmt.targets) {
4267
+ for (const t of stmt.targets) {
4268
+ if (typeof t === 'object') this._walkExpressions(t, callback);
4269
+ }
4270
+ }
4271
+ break;
4272
+ case 'ReturnStatement':
4273
+ if (stmt.value) this._walkExpressions(stmt.value, callback);
4274
+ break;
4275
+ case 'IfStatement':
4276
+ case 'IfExpression':
4277
+ this._walkExpressions(stmt.condition, callback);
4278
+ this._walkStatementBlock(stmt.consequent, callback);
4279
+ if (stmt.alternates) {
4280
+ for (const alt of stmt.alternates) {
4281
+ this._walkExpressions(alt.condition, callback);
4282
+ this._walkStatementBlock(alt.body, callback);
4283
+ }
4284
+ }
4285
+ if (stmt.elseBody) this._walkStatementBlock(stmt.elseBody, callback);
4286
+ break;
4287
+ case 'ForStatement':
4288
+ this._walkExpressions(stmt.iterable, callback);
4289
+ this._walkStatementBlock(stmt.body, callback);
4290
+ break;
4291
+ case 'WhileStatement':
4292
+ this._walkExpressions(stmt.condition, callback);
4293
+ this._walkStatementBlock(stmt.body, callback);
4294
+ break;
4295
+ case 'CallExpression':
4296
+ this._walkExpressions(stmt, callback);
4297
+ break;
4298
+ default:
4299
+ if (stmt.expression) this._walkExpressions(stmt.expression, callback);
4300
+ break;
4301
+ }
4302
+ }
4303
+
4304
+ /**
4305
+ * Walks all statements in a block for _walkStatementExpressions.
4306
+ */
4307
+ _walkStatementBlock(block, callback) {
4308
+ if (!block) return;
4309
+ const stmts = block.type === 'BlockStatement' ? block.body : [block];
4310
+ for (const s of stmts) this._walkStatementExpressions(s, callback);
4311
+ }
3039
4312
  }