@scriptc/compiler 0.2.2 → 0.2.3

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 (178) hide show
  1. package/ambient/scriptc-node-fallback.d.ts +99 -27
  2. package/dist/backend/cycle-analysis.js +63 -50
  3. package/dist/backend/cycle-analysis.js.map +1 -1
  4. package/dist/backend/cycle-analysis.test.js +51 -0
  5. package/dist/backend/cycle-analysis.test.js.map +1 -1
  6. package/dist/backend/llvm/borrowed-receivers.d.ts +9 -0
  7. package/dist/backend/llvm/borrowed-receivers.js +58 -0
  8. package/dist/backend/llvm/borrowed-receivers.js.map +1 -0
  9. package/dist/backend/llvm/call-lifetimes.d.ts +20 -0
  10. package/dist/backend/llvm/call-lifetimes.js +179 -0
  11. package/dist/backend/llvm/call-lifetimes.js.map +1 -0
  12. package/dist/backend/llvm/call-lifetimes.test.d.ts +1 -0
  13. package/dist/backend/llvm/call-lifetimes.test.js +205 -0
  14. package/dist/backend/llvm/call-lifetimes.test.js.map +1 -0
  15. package/dist/backend/llvm/dyn.d.ts +2 -2
  16. package/dist/backend/llvm/dyn.js +79 -35
  17. package/dist/backend/llvm/dyn.js.map +1 -1
  18. package/dist/backend/llvm/emitter.d.ts +33 -0
  19. package/dist/backend/llvm/emitter.js +189 -16
  20. package/dist/backend/llvm/emitter.js.map +1 -1
  21. package/dist/backend/llvm/emitter.test.js +20 -1
  22. package/dist/backend/llvm/emitter.test.js.map +1 -1
  23. package/dist/backend/llvm/expr-async.js +1 -1
  24. package/dist/backend/llvm/expr-async.js.map +1 -1
  25. package/dist/backend/llvm/expr-calls.js +42 -5
  26. package/dist/backend/llvm/expr-calls.js.map +1 -1
  27. package/dist/backend/llvm/expr-containers.js +70 -13
  28. package/dist/backend/llvm/expr-containers.js.map +1 -1
  29. package/dist/backend/llvm/expr-dynamic.js +1 -1
  30. package/dist/backend/llvm/expr-dynamic.js.map +1 -1
  31. package/dist/backend/llvm/expr-primitives.js +6 -5
  32. package/dist/backend/llvm/expr-primitives.js.map +1 -1
  33. package/dist/backend/llvm/expr-records.js +1 -1
  34. package/dist/backend/llvm/expr-records.js.map +1 -1
  35. package/dist/backend/llvm/expr-stream-bridges.js +5 -10
  36. package/dist/backend/llvm/expr-stream-bridges.js.map +1 -1
  37. package/dist/backend/llvm/lib-shared.js +35 -1
  38. package/dist/backend/llvm/lib-shared.js.map +1 -1
  39. package/dist/backend/llvm/local-array-reads.d.ts +44 -0
  40. package/dist/backend/llvm/local-array-reads.js +313 -0
  41. package/dist/backend/llvm/local-array-reads.js.map +1 -0
  42. package/dist/backend/llvm/local-array-reads.test.d.ts +1 -0
  43. package/dist/backend/llvm/local-array-reads.test.js +446 -0
  44. package/dist/backend/llvm/local-array-reads.test.js.map +1 -0
  45. package/dist/backend/llvm/map-read-lifetimes.d.ts +36 -0
  46. package/dist/backend/llvm/map-read-lifetimes.js +128 -0
  47. package/dist/backend/llvm/map-read-lifetimes.js.map +1 -0
  48. package/dist/backend/llvm/map-read-lifetimes.test.d.ts +1 -0
  49. package/dist/backend/llvm/map-read-lifetimes.test.js +123 -0
  50. package/dist/backend/llvm/map-read-lifetimes.test.js.map +1 -0
  51. package/dist/backend/llvm/shapes.js +1 -0
  52. package/dist/backend/llvm/shapes.js.map +1 -1
  53. package/dist/backend/llvm/split.js +15 -5
  54. package/dist/backend/llvm/split.js.map +1 -1
  55. package/dist/backend/llvm/split.test.js +17 -0
  56. package/dist/backend/llvm/split.test.js.map +1 -1
  57. package/dist/backend/llvm/stack-unions.d.ts +20 -0
  58. package/dist/backend/llvm/stack-unions.js +61 -0
  59. package/dist/backend/llvm/stack-unions.js.map +1 -0
  60. package/dist/backend/llvm/stack-unions.test.d.ts +1 -0
  61. package/dist/backend/llvm/stack-unions.test.js +241 -0
  62. package/dist/backend/llvm/stack-unions.test.js.map +1 -0
  63. package/dist/backend/llvm/string-lifetimes.d.ts +12 -0
  64. package/dist/backend/llvm/string-lifetimes.js +70 -0
  65. package/dist/backend/llvm/string-lifetimes.js.map +1 -0
  66. package/dist/backend/llvm/string-lifetimes.test.d.ts +1 -0
  67. package/dist/backend/llvm/string-lifetimes.test.js +230 -0
  68. package/dist/backend/llvm/string-lifetimes.test.js.map +1 -0
  69. package/dist/backend/mangle.d.ts +2 -0
  70. package/dist/backend/mangle.js +4 -0
  71. package/dist/backend/mangle.js.map +1 -1
  72. package/dist/backend/may-throw.js +67 -29
  73. package/dist/backend/may-throw.js.map +1 -1
  74. package/dist/backend/may-throw.test.js +104 -1
  75. package/dist/backend/may-throw.test.js.map +1 -1
  76. package/dist/backend/native-toolchain.d.ts +2 -2
  77. package/dist/backend/native-toolchain.js +2 -0
  78. package/dist/backend/native-toolchain.js.map +1 -1
  79. package/dist/frontend/input-tracker.js +3 -3
  80. package/dist/frontend/input-tracker.js.map +1 -1
  81. package/dist/frontend/input-tracker.test.js +38 -2
  82. package/dist/frontend/input-tracker.test.js.map +1 -1
  83. package/dist/frontend/lowering/collection-ingestion.d.ts +31 -0
  84. package/dist/frontend/lowering/collection-ingestion.js +215 -0
  85. package/dist/frontend/lowering/collection-ingestion.js.map +1 -0
  86. package/dist/frontend/lowering/containers/bytes.js +2 -0
  87. package/dist/frontend/lowering/containers/bytes.js.map +1 -1
  88. package/dist/frontend/lowering/import-meta.d.ts +10 -0
  89. package/dist/frontend/lowering/import-meta.js +52 -0
  90. package/dist/frontend/lowering/import-meta.js.map +1 -0
  91. package/dist/frontend/lowering/lower-assert.js +105 -41
  92. package/dist/frontend/lowering/lower-assert.js.map +1 -1
  93. package/dist/frontend/lowering/lower-builtin-values.d.ts +2 -0
  94. package/dist/frontend/lowering/lower-builtin-values.js +22 -1
  95. package/dist/frontend/lowering/lower-builtin-values.js.map +1 -1
  96. package/dist/frontend/lowering/lower-builtins.d.ts +3 -13
  97. package/dist/frontend/lowering/lower-builtins.js +150 -46
  98. package/dist/frontend/lowering/lower-builtins.js.map +1 -1
  99. package/dist/frontend/lowering/lower-calls.js +6 -3
  100. package/dist/frontend/lowering/lower-calls.js.map +1 -1
  101. package/dist/frontend/lowering/lower-classes.js +4 -4
  102. package/dist/frontend/lowering/lower-classes.js.map +1 -1
  103. package/dist/frontend/lowering/lower-containers.d.ts +5 -1
  104. package/dist/frontend/lowering/lower-containers.js +40 -3
  105. package/dist/frontend/lowering/lower-containers.js.map +1 -1
  106. package/dist/frontend/lowering/lower-exprs.js +35 -41
  107. package/dist/frontend/lowering/lower-exprs.js.map +1 -1
  108. package/dist/frontend/lowering/lower-inspect.js +43 -0
  109. package/dist/frontend/lowering/lower-inspect.js.map +1 -1
  110. package/dist/frontend/lowering/lower-stmts.js +72 -68
  111. package/dist/frontend/lowering/lower-stmts.js.map +1 -1
  112. package/dist/frontend/lowering/lower-system-errors.d.ts +6 -0
  113. package/dist/frontend/lowering/lower-system-errors.js +29 -0
  114. package/dist/frontend/lowering/lower-system-errors.js.map +1 -0
  115. package/dist/frontend/lowering/lowerer.d.ts +4 -3
  116. package/dist/frontend/lowering/lowerer.js +50 -22
  117. package/dist/frontend/lowering/lowerer.js.map +1 -1
  118. package/dist/frontend/lowering/surfaces.d.ts +2 -2
  119. package/dist/frontend/lowering/surfaces.js +69 -21
  120. package/dist/frontend/lowering/surfaces.js.map +1 -1
  121. package/dist/frontend/module-syntax.d.ts +1 -1
  122. package/dist/frontend/module-syntax.js +8 -1
  123. package/dist/frontend/module-syntax.js.map +1 -1
  124. package/dist/frontend/module-syntax.test.js +10 -0
  125. package/dist/frontend/module-syntax.test.js.map +1 -1
  126. package/dist/frontend/npm.d.ts +4 -0
  127. package/dist/frontend/npm.js +86 -33
  128. package/dist/frontend/npm.js.map +1 -1
  129. package/dist/frontend/npm.test.js +103 -0
  130. package/dist/frontend/npm.test.js.map +1 -1
  131. package/dist/frontend/pipeline-native.js +2 -1
  132. package/dist/frontend/pipeline-native.js.map +1 -1
  133. package/dist/frontend/services-node.js +3 -2
  134. package/dist/frontend/services-node.js.map +1 -1
  135. package/dist/frontend/services.d.ts +5 -1
  136. package/dist/frontend/services.js +9 -1
  137. package/dist/frontend/services.js.map +1 -1
  138. package/dist/frontend/services.test.js +21 -0
  139. package/dist/frontend/services.test.js.map +1 -1
  140. package/dist/frontend/ts7/ast-node.d.ts +2 -0
  141. package/dist/frontend/ts7/ast-node.js +30 -4
  142. package/dist/frontend/ts7/ast-node.js.map +1 -1
  143. package/dist/frontend/ts7/ast-node.test.js +60 -1
  144. package/dist/frontend/ts7/ast-node.test.js.map +1 -1
  145. package/dist/frontend/type-mapper.js +6 -0
  146. package/dist/frontend/type-mapper.js.map +1 -1
  147. package/dist/index.js +3 -2
  148. package/dist/index.js.map +1 -1
  149. package/dist/ir/analysis.js +1 -0
  150. package/dist/ir/analysis.js.map +1 -1
  151. package/dist/ir/integer-loops.d.ts +7 -0
  152. package/dist/ir/integer-loops.js +23 -0
  153. package/dist/ir/integer-loops.js.map +1 -1
  154. package/dist/ir/integer-loops.test.d.ts +1 -0
  155. package/dist/ir/integer-loops.test.js +41 -0
  156. package/dist/ir/integer-loops.test.js.map +1 -0
  157. package/dist/ir/ir.d.ts +21 -17
  158. package/dist/ir/ir.js +43 -9
  159. package/dist/ir/ir.js.map +1 -1
  160. package/dist/ir/ir.test.js +18 -0
  161. package/dist/ir/ir.test.js.map +1 -1
  162. package/dist/ir/validate.js +161 -76
  163. package/dist/ir/validate.js.map +1 -1
  164. package/dist/ir/validate.test.js +227 -0
  165. package/dist/ir/validate.test.js.map +1 -1
  166. package/dist/library/fence-eval.js +9 -2
  167. package/dist/library/fence-eval.js.map +1 -1
  168. package/dist/library/fence-eval.test.d.ts +1 -0
  169. package/dist/library/fence-eval.test.js +21 -0
  170. package/dist/library/fence-eval.test.js.map +1 -0
  171. package/dist/native/runtime-typescript.d.ts +4 -0
  172. package/dist/native/runtime-typescript.js +61 -0
  173. package/dist/native/runtime-typescript.js.map +1 -0
  174. package/dist/native/runtime-typescript.test.d.ts +1 -0
  175. package/dist/native/runtime-typescript.test.js +41 -0
  176. package/dist/native/runtime-typescript.test.js.map +1 -0
  177. package/package.json +20 -20
  178. package/surface-manifest.json +271 -17
@@ -358,8 +358,13 @@ export const LIB_FN_SIGS = {
358
358
  "url.port": { argTypes: [URL_T], result: STRING },
359
359
  "url.pathname": { argTypes: [URL_T], result: STRING },
360
360
  "url.href": { argTypes: [URL_T], result: STRING },
361
+ "url.set": { argTypes: [URL_T, STRING, STRING], result: VOID },
362
+ "url.setChecked": { argTypes: [URL_T, STRING, DYN], result: VOID },
361
363
  "url.fileURLToPathUrl": { argTypes: [URL_T], result: STRING },
362
364
  "url.fileURLToPathChecked": { argTypes: [DYN], result: STRING },
365
+ "url.fileURLToPathOptions": { argTypes: [DYN, DYN], result: STRING },
366
+ "url.fileURLToPathBuffer": { argTypes: [DYN, DYN], result: BYTES_U8 },
367
+ "url.pathToFileURLChecked": { argTypes: [DYN, DYN], result: URL_T },
363
368
  "url.fileURLToPathStr": { argTypes: [STRING], result: STRING },
364
369
  "url.pathToFileURL": { argTypes: [STRING], result: URL_T },
365
370
  "url.pathToFileURLPlatform": { argTypes: [STRING, BOOL], result: URL_T },
@@ -400,6 +405,15 @@ export const LIB_FN_SIGS = {
400
405
  "qs.unescape": { argTypes: [STRING], result: STRING },
401
406
  "util.parseArgs": { argTypes: [DYN], result: DYN },
402
407
  "util.parseEnv": { argTypes: [DYN], result: DYN },
408
+ "util.getSystemErrorName": { argTypes: [DYN], result: STRING },
409
+ "util.getSystemErrorMessage": { argTypes: [DYN], result: STRING },
410
+ "util.systemErrorEntries": { argTypes: [], result: DYN },
411
+ "util.stripVTControlCharacters": { argTypes: [DYN], result: STRING },
412
+ "util.toUSVString": { argTypes: [DYN], result: STRING },
413
+ "util.isDeepStrictEqual": { argTypes: [DYN, DYN, DYN], result: BOOL },
414
+ "util.styleText": { argTypes: [DYN, DYN, DYN], result: STRING },
415
+ "util.typeIs": { argTypes: [DYN, STRING], result: BOOL },
416
+ "util.typeValue": { argTypes: [DYN, STRING], result: DYN },
403
417
  "fs.statSync": { argTypes: [STRING], result: STATS_T },
404
418
  "fs.lstatSync": { argTypes: [STRING], result: STATS_T },
405
419
  "fs.fstatSync": { argTypes: [F64], result: STATS_T },
@@ -407,9 +421,17 @@ export const LIB_FN_SIGS = {
407
421
  "fs.fsyncSync": { argTypes: [F64], result: VOID },
408
422
  "fs.fdatasyncSync": { argTypes: [F64], result: VOID },
409
423
  "fs.ftruncateSync": { argTypes: [F64, F64], result: VOID },
424
+ "fs.utimesSync": { argTypes: [DYN, DYN, DYN], result: VOID },
425
+ "fs.futimesSync": { argTypes: [DYN, DYN, DYN], result: VOID },
426
+ "fs.lutimesSync": { argTypes: [DYN, DYN, DYN], result: VOID },
410
427
  "fs.readvSync": { argTypes: [F64, arrayOf(BYTES_U8), F64], result: F64 },
411
428
  "fs.writevSync": { argTypes: [F64, arrayOf(BYTES_U8), F64], result: F64 },
412
- "fs.linkSync": { argTypes: [STRING, STRING], result: VOID },
429
+ "fs.linkSync": { argTypes: [DYN, DYN], result: VOID },
430
+ "fs.symlinkSync": { argTypes: [DYN, DYN, DYN], result: VOID },
431
+ "fs.readlinkSync": { argTypes: [DYN, DYN], result: DYN },
432
+ "fs.statfsSync": { argTypes: [DYN, DYN], result: DYN },
433
+ "fs.readlinkSyncStr": { argTypes: [DYN, DYN], result: STRING },
434
+ "fs.readlinkSyncBuffer": { argTypes: [DYN, DYN], result: BYTES_U8 },
413
435
  "fs.openSync": { argTypes: [STRING, STRING], result: F64 },
414
436
  "fs.openNumericSync": { argTypes: [STRING, F64, F64], result: F64 },
415
437
  "fs.readSync": { argTypes: [F64, BYTES_U8, F64, F64, F64], result: F64 },
@@ -1051,6 +1073,14 @@ export const LIB_FN_SIGS = {
1051
1073
  "fsp.mkdirRecursiveMode": { argTypes: [STRING, F64], result: { kind: "promise", inner: VOID } },
1052
1074
  "fsp.unlink": { argTypes: [STRING], result: { kind: "promise", inner: VOID } },
1053
1075
  "fsp.chmod": { argTypes: [STRING, F64], result: { kind: "promise", inner: VOID } },
1076
+ "fsp.link": { argTypes: [DYN, DYN], result: { kind: "promise", inner: VOID } },
1077
+ "fsp.symlink": { argTypes: [DYN, DYN, DYN], result: { kind: "promise", inner: VOID } },
1078
+ "fsp.readlink": { argTypes: [DYN, DYN], result: { kind: "promise", inner: DYN } },
1079
+ "fsp.statfs": { argTypes: [DYN, DYN], result: { kind: "promise", inner: DYN } },
1080
+ "fsp.readlinkStr": { argTypes: [DYN, DYN], result: { kind: "promise", inner: STRING } },
1081
+ "fsp.readlinkBuffer": { argTypes: [DYN, DYN], result: { kind: "promise", inner: BYTES_U8 } },
1082
+ "fsp.utimes": { argTypes: [DYN, DYN, DYN], result: { kind: "promise", inner: VOID } },
1083
+ "fsp.lutimes": { argTypes: [DYN, DYN, DYN], result: { kind: "promise", inner: VOID } },
1054
1084
  "fsp.rename": { argTypes: [STRING, STRING], result: { kind: "promise", inner: VOID } },
1055
1085
  "fsp.readdir": { argTypes: [STRING], result: { kind: "promise", inner: arrayOf(STRING) } },
1056
1086
  // Result is promise<call-site Dirent record array>; the libCall case
@@ -1067,6 +1097,7 @@ export const LIB_FN_SIGS = {
1067
1097
  "fileHandle.datasync": { argTypes: [FILEHANDLE_T], result: { kind: "promise", inner: VOID } },
1068
1098
  "fileHandle.truncate": { argTypes: [FILEHANDLE_T, F64], result: { kind: "promise", inner: VOID } },
1069
1099
  "fileHandle.chmod": { argTypes: [FILEHANDLE_T, F64], result: { kind: "promise", inner: VOID } },
1100
+ "fileHandle.utimes": { argTypes: [FILEHANDLE_T, DYN, DYN], result: { kind: "promise", inner: VOID } },
1070
1101
  "fileHandle.readv": { argTypes: [FILEHANDLE_T, arrayOf(BYTES_U8), F64], result: { kind: "promise", inner: VOID } },
1071
1102
  "fileHandle.writev": { argTypes: [FILEHANDLE_T, arrayOf(BYTES_U8), F64], result: { kind: "promise", inner: VOID } },
1072
1103
  // read/write carry call-site result record shapes; the validator checks
@@ -1390,6 +1421,9 @@ export const LIB_FN_SIGS = {
1390
1421
  "assert.shapeEnd": { argTypes: [STRING, BOOL], result: VOID },
1391
1422
  "assert.regexErrTest": { argTypes: [REGEX, { kind: "object", className: "%Error" }], result: BOOL },
1392
1423
  "assert.unwantedRejection": { argTypes: [{ kind: "object", className: "%Error" }, STRING, BOOL], result: VOID },
1424
+ "assert.unwantedError": { argTypes: [STRING, BOOL, STRING, BOOL], result: VOID },
1425
+ "assert.noErrorPredicate": { argTypes: [DYN, DYN], result: BOOL },
1426
+ "assert.regexDynTest": { argTypes: [REGEX, DYN], result: BOOL },
1393
1427
  "assert.expectsErrDyn": { argTypes: [DYN, DYN, STRING, BOOL], result: VOID },
1394
1428
  // assert.ifError's typed entries (always throw; unit args never
1395
1429
  // lower). The error slot is program-dependent — any %Error-hierarchy
@@ -1761,6 +1795,7 @@ export function validateModule(mod) {
1761
1795
  });
1762
1796
  }
1763
1797
  const classesByName = new Map();
1798
+ const classFields = new Map();
1764
1799
  for (const cls of mod.classes ?? []) {
1765
1800
  if (classesByName.has(cls.name)) {
1766
1801
  errors.push({ message: `duplicate class "${cls.name}"`, loc: cls.loc });
@@ -1813,16 +1848,24 @@ export function validateModule(mod) {
1813
1848
  errors.push({ message: `class ${cls.name}: invalid symbol field metadata`, loc: cls.loc });
1814
1849
  }
1815
1850
  }
1816
- const seen = new Set();
1851
+ // Reuse duplicate detection as the field index for function validation.
1852
+ // Array.find selected the first field, even in an invalid duplicate layout.
1853
+ const seen = new Map();
1817
1854
  for (const f of cls.fields) {
1818
1855
  if (seen.has(f.name)) {
1819
1856
  errors.push({ message: `class ${cls.name}: duplicate field "${f.name}"`, loc: cls.loc });
1820
1857
  }
1821
- seen.add(f.name);
1858
+ else
1859
+ seen.set(f.name, f.type);
1822
1860
  if (isUnitType(f.type)) {
1823
1861
  errors.push({ message: `class ${cls.name}: field "${f.name}" is ${f.type.kind}`, loc: cls.loc });
1824
1862
  }
1825
1863
  }
1864
+ // Duplicate class names, like classesByName, select the last declaration.
1865
+ if (seen.size > 0)
1866
+ classFields.set(cls.name, seen);
1867
+ else
1868
+ classFields.delete(cls.name);
1826
1869
  // Constructor presence is checked at `new` sites (below): a class kept
1827
1870
  // only for its layout/type (reachability never constructs it) carries
1828
1871
  // no constructor function, and that is fine — nothing calls it.
@@ -1899,30 +1942,41 @@ export function validateModule(mod) {
1899
1942
  }
1900
1943
  const noLoc = { file: mod.sourceFile, start: 0, end: 0 };
1901
1944
  const recordsById = new Map();
1945
+ const recordIds = new Set((mod.records ?? []).map((record) => record.id));
1946
+ const recordValidation = new Map();
1902
1947
  for (const rec of mod.records ?? []) {
1903
1948
  if (recordsById.has(rec.id)) {
1904
1949
  errors.push({ message: `duplicate record shape "${rec.id}"`, loc: noLoc });
1905
1950
  }
1906
1951
  recordsById.set(rec.id, rec);
1907
- const seen = new Set();
1952
+ const fields = new Map();
1908
1953
  for (const f of rec.fields) {
1909
- if (seen.has(f.name)) {
1954
+ if (fields.has(f.name)) {
1910
1955
  errors.push({ message: `record ${rec.id}: duplicate field "${f.name}"`, loc: noLoc });
1911
1956
  }
1912
- seen.add(f.name);
1957
+ else
1958
+ fields.set(f.name, f.type);
1913
1959
  // Unit kinds (undefinedT/nullT) exist only as union arms — a BARE
1914
1960
  // unit field is as malformed as a void one. dyn and jsval slots
1915
1961
  // are valid; both are refcounted values held by the record shape.
1916
1962
  if (f.type.kind === "void" || isUnitType(f.type)) {
1917
1963
  errors.push({ message: `record ${rec.id}: field "${f.name}" is ${f.type.kind}`, loc: noLoc });
1918
1964
  }
1919
- if (f.type.kind === "record" && !new Set((mod.records ?? []).map((r) => r.id)).has(f.type.shapeId)) {
1965
+ if (f.type.kind === "record" && !recordIds.has(f.type.shapeId)) {
1920
1966
  errors.push({
1921
1967
  message: `record ${rec.id}: field "${f.name}" references undeclared shape "${f.type.shapeId}"`,
1922
1968
  loc: noLoc,
1923
1969
  });
1924
1970
  }
1925
1971
  }
1972
+ // Reads historically use the first duplicate field, while literal and
1973
+ // clone checks use the last. Preserve both on malformed shapes; valid
1974
+ // shapes share one index. All indexes belong to this validation call.
1975
+ recordValidation.set(rec.id, {
1976
+ fields,
1977
+ initializationFields: fields.size === rec.fields.length ? fields : new Map(rec.fields.map((field) => [field.name, field.type])),
1978
+ hasAccessorSlots: shapeHasAccessorSlots(rec),
1979
+ });
1926
1980
  // Canonical order is the shape's identity — enforce it.
1927
1981
  const sorted = [...rec.fields].map((f) => f.name).sort();
1928
1982
  if (rec.fields.some((f, i) => f.name !== sorted[i])) {
@@ -1975,7 +2029,7 @@ export function validateModule(mod) {
1975
2029
  for (const candidate of cases) {
1976
2030
  const arm = u.arms[candidate.tag];
1977
2031
  const record = arm?.kind === "record" ? recordsById.get(arm.shapeId) : undefined;
1978
- const member = record?.fields.find((entry) => entry.name === field);
2032
+ const member = record ? recordValidation.get(record.id)?.fields.get(field) : undefined;
1979
2033
  if (!Number.isInteger(candidate.tag) || candidate.tag < 0 || !member || tags.has(candidate.tag)) {
1980
2034
  errors.push({ message: `union ${u.id}: invalid discriminant tag ${candidate.tag}`, loc: noLoc });
1981
2035
  }
@@ -1988,8 +2042,8 @@ export function validateModule(mod) {
1988
2042
  errors.push({ message: `union ${u.id}: undefined discriminant value`, loc: noLoc });
1989
2043
  continue;
1990
2044
  }
1991
- const fieldTypes = member?.type.kind === "union"
1992
- ? unionsById.get(member.type.unionId)?.arms ?? [] : member ? [member.type] : [];
2045
+ const fieldTypes = member?.kind === "union"
2046
+ ? unionsById.get(member.unionId)?.arms ?? [] : member ? [member] : [];
1993
2047
  const valid = fieldTypes.some((type) => typeof value === "string" ? type.kind === "string"
1994
2048
  : typeof value === "boolean" ? type.kind === "bool"
1995
2049
  : typeof value === "number" && Number.isFinite(value) && type.kind === "f64");
@@ -2152,12 +2206,34 @@ export function validateModule(mod) {
2152
2206
  }
2153
2207
  }
2154
2208
  }
2209
+ // Class metadata is invariant across functions. Keep field lookup, hierarchy
2210
+ // membership, and virtual-call resolution local to this validation invocation.
2211
+ const classValidation = {
2212
+ fields: classFields,
2213
+ hierarchy: new Set(), implementations: new Map(), virtualCalls: new Map(),
2214
+ };
2215
+ for (const cls of classesByName.values()) {
2216
+ if (cls.base !== undefined) {
2217
+ classValidation.hierarchy.add(cls.name);
2218
+ classValidation.hierarchy.add(cls.base);
2219
+ }
2220
+ if (cls.name === RUNTIME_EMITTER_CLASS)
2221
+ classValidation.hierarchy.add(cls.name);
2222
+ for (const method of new Set(cls.methods ?? [])) {
2223
+ if (cls.abstractMethods?.includes(method))
2224
+ continue;
2225
+ let implementations = classValidation.implementations.get(method);
2226
+ if (!implementations)
2227
+ classValidation.implementations.set(method, (implementations = []));
2228
+ implementations.push(cls);
2229
+ }
2230
+ }
2155
2231
  for (const fn of mod.functions) {
2156
- validateFunction(fn, functionsByName, ffiByName, classesByName, recordsById, unionsById, globalsById, errors);
2232
+ validateFunction(fn, functionsByName, ffiByName, classesByName, recordsById, recordValidation, unionsById, globalsById, classValidation, errors);
2157
2233
  }
2158
2234
  return errors;
2159
2235
  }
2160
- function validateFunction(fn, functions, ffiByName, classes, records, unions, globals, errors) {
2236
+ function validateFunction(fn, functions, ffiByName, classes, records, recordValidation, unions, globals, classValidation, errors) {
2161
2237
  const locals = new Map(fn.locals.map((l) => [l.id, l]));
2162
2238
  const err = (message, loc) => errors.push({ message: `in ${fn.name}: ${message}`, loc });
2163
2239
  const asyncCaches = [
@@ -2200,17 +2276,41 @@ function validateFunction(fn, functions, ffiByName, classes, records, unions, gl
2200
2276
  }
2201
2277
  return false;
2202
2278
  };
2203
- const hierarchy = new Set();
2204
- for (const c of classes.values()) {
2205
- if (c.base !== undefined) {
2206
- hierarchy.add(c.name);
2207
- hierarchy.add(c.base);
2208
- }
2209
- // The runtime emitter class is ALWAYS a hierarchy member: ScrEmitter
2210
- // carries its vtable word even with no subclass in the program.
2211
- if (c.name === RUNTIME_EMITTER_CLASS)
2212
- hierarchy.add(c.name);
2213
- }
2279
+ const hierarchy = classValidation.hierarchy;
2280
+ const virtualCallInfo = (className, method) => {
2281
+ let methods = classValidation.virtualCalls.get(className);
2282
+ const cached = methods?.get(method);
2283
+ if (cached)
2284
+ return cached;
2285
+ if (!methods)
2286
+ classValidation.virtualCalls.set(className, (methods = new Map()));
2287
+ let declared = false;
2288
+ let implementation;
2289
+ for (let c = classes.get(className); c; c = c.base !== undefined ? classes.get(c.base) : undefined) {
2290
+ if (c.methods?.includes(method)) {
2291
+ declared = true;
2292
+ if (!c.abstractMethods?.includes(method)) {
2293
+ implementation = functions.get(`%${c.name}.${method}`);
2294
+ break;
2295
+ }
2296
+ }
2297
+ }
2298
+ let hasOverride = false;
2299
+ if (declared) {
2300
+ // Preserve class-table order when choosing an abstract slot's ABI.
2301
+ for (const c of classValidation.implementations.get(method) ?? []) {
2302
+ if (!isStrictSubclass(c.name, className))
2303
+ continue;
2304
+ hasOverride = true;
2305
+ implementation ??= functions.get(`%${c.name}.${method}`);
2306
+ if (implementation)
2307
+ break;
2308
+ }
2309
+ }
2310
+ const result = { declared, hasOverride, implementation };
2311
+ methods.set(method, result);
2312
+ return result;
2313
+ };
2214
2314
  // Unit kinds live only inside unions: a bare-unit local, param, or
2215
2315
  // return type is frontend breakage (mapType never produces them).
2216
2316
  for (const l of fn.locals) {
@@ -2636,15 +2736,15 @@ function validateFunction(fn, functions, ffiByName, classes, records, unions, gl
2636
2736
  // dyn field with fieldDyn set (validated numeric read-modify-write).
2637
2737
  checkExpr(e.obj);
2638
2738
  const cls = classes.get(e.className);
2639
- const field = cls?.fields.find((f) => f.name === e.field);
2739
+ const fieldType = classValidation.fields.get(e.className)?.get(e.field);
2640
2740
  if (!cls)
2641
2741
  err(`fieldIncDec on undeclared class "${e.className}"`, e.loc);
2642
- else if (!field)
2742
+ else if (!fieldType)
2643
2743
  err(`class ${e.className} has no field "${e.field}"`, e.loc);
2644
2744
  else {
2645
2745
  expectType(e.obj, { kind: "object", className: e.className }, "fieldIncDec receiver");
2646
- if (e.fieldDyn ? field.type.kind !== "dyn" : field.type.kind !== "f64") {
2647
- err(`fieldIncDec ${e.className}.${e.field} field/flag mismatch (${field.type.kind})`, e.loc);
2746
+ if (e.fieldDyn ? fieldType.kind !== "dyn" : fieldType.kind !== "f64") {
2747
+ err(`fieldIncDec ${e.className}.${e.field} field/flag mismatch (${fieldType.kind})`, e.loc);
2648
2748
  }
2649
2749
  }
2650
2750
  if (e.type.kind !== "f64")
@@ -3668,14 +3768,14 @@ function validateFunction(fn, functions, ffiByName, classes, records, unions, gl
3668
3768
  case "fieldGet": {
3669
3769
  checkExpr(e.obj);
3670
3770
  const cls = classes.get(e.className);
3671
- const field = cls?.fields.find((f) => f.name === e.field);
3771
+ const fieldType = classValidation.fields.get(e.className)?.get(e.field);
3672
3772
  if (!cls)
3673
3773
  err(`fieldGet on undeclared class "${e.className}"`, e.loc);
3674
- else if (!field)
3774
+ else if (!fieldType)
3675
3775
  err(`class ${e.className} has no field "${e.field}"`, e.loc);
3676
3776
  else {
3677
3777
  expectType(e.obj, { kind: "object", className: e.className }, "fieldGet receiver");
3678
- if (!typeEquals(e.type, field.type)) {
3778
+ if (!typeEquals(e.type, fieldType)) {
3679
3779
  err(`fieldGet ${e.className}.${e.field} type mismatch`, e.loc);
3680
3780
  }
3681
3781
  }
@@ -3849,30 +3949,15 @@ function validateFunction(fn, functions, ffiByName, classes, records, unions, gl
3849
3949
  // is then well-formed against any concrete override below — the
3850
3950
  // frontend's override-exactness rule makes every implementation
3851
3951
  // ABI-identical, so any one of them carries the slot's signature.
3852
- let declared = false;
3853
- let impl;
3854
- for (let c = classes.get(e.className); c; c = c.base !== undefined ? classes.get(c.base) : undefined) {
3855
- if (c.methods?.includes(e.method)) {
3856
- declared = true;
3857
- if (!c.abstractMethods?.includes(e.method)) {
3858
- impl = functions.get(`%${c.name}.${e.method}`);
3859
- break;
3860
- }
3861
- }
3862
- }
3863
- if (!declared) {
3952
+ const info = virtualCallInfo(e.className, e.method);
3953
+ if (!info.declared) {
3864
3954
  err(`virtualCall ${e.className}.${e.method}: no declaration on the base chain`, e.loc);
3865
3955
  break;
3866
3956
  }
3867
- const concreteBelow = [...classes.values()].filter((c) => c.methods?.includes(e.method) &&
3868
- !c.abstractMethods?.includes(e.method) &&
3869
- isStrictSubclass(c.name, e.className));
3870
- if (concreteBelow.length === 0) {
3957
+ if (!info.hasOverride) {
3871
3958
  err(`virtualCall ${e.className}.${e.method}: no concrete override below the static class`, e.loc);
3872
3959
  }
3873
- impl ??= concreteBelow
3874
- .map((c) => functions.get(`%${c.name}.${e.method}`))
3875
- .find((f) => f !== undefined);
3960
+ const impl = info.implementation;
3876
3961
  if (!impl) {
3877
3962
  err(`virtualCall ${e.className}.${e.method}: no implementation function exists`, e.loc);
3878
3963
  break;
@@ -3908,7 +3993,7 @@ function validateFunction(fn, functions, ffiByName, classes, records, unions, gl
3908
3993
  err(`recordLit of undeclared shape "${e.type.shapeId}"`, e.loc);
3909
3994
  break;
3910
3995
  }
3911
- const want = new Map(shape.fields.map((f) => [f.name, f.type]));
3996
+ const want = recordValidation.get(shape.id).initializationFields;
3912
3997
  const seen = new Set();
3913
3998
  for (const f of e.fields) {
3914
3999
  checkExpr(f.value);
@@ -3964,11 +4049,11 @@ function validateFunction(fn, functions, ffiByName, classes, records, unions, gl
3964
4049
  err(`recordClone of undeclared shape "${e.type.shapeId}"`, e.loc);
3965
4050
  break;
3966
4051
  }
3967
- if (shape.tuple || shape.indexValue || shapeHasAccessorSlots(shape)) {
4052
+ if (shape.tuple || shape.indexValue || recordValidation.get(shape.id).hasAccessorSlots) {
3968
4053
  err(`recordClone requires a plain declared-field shape, got ${shape.id}`, e.loc);
3969
4054
  }
3970
4055
  expectType(e.source, e.type, "recordClone source");
3971
- const want = new Map(shape.fields.map((f) => [f.name, f.type]));
4056
+ const want = recordValidation.get(shape.id).initializationFields;
3972
4057
  const seen = new Set();
3973
4058
  for (const f of e.overrides) {
3974
4059
  checkExpr(f.value);
@@ -3986,14 +4071,14 @@ function validateFunction(fn, functions, ffiByName, classes, records, unions, gl
3986
4071
  case "recordGet": {
3987
4072
  checkExpr(e.obj);
3988
4073
  const shape = records.get(e.shapeId);
3989
- const field = shape?.fields.find((f) => f.name === e.field);
4074
+ const field = recordValidation.get(e.shapeId)?.fields.get(e.field);
3990
4075
  if (!shape)
3991
4076
  err(`recordGet on undeclared shape "${e.shapeId}"`, e.loc);
3992
4077
  else if (!field)
3993
4078
  err(`shape ${e.shapeId} has no field "${e.field}"`, e.loc);
3994
4079
  else {
3995
4080
  expectType(e.obj, { kind: "record", shapeId: e.shapeId }, "recordGet receiver");
3996
- if (!typeEquals(e.type, field.type)) {
4081
+ if (!typeEquals(e.type, field)) {
3997
4082
  err(`recordGet ${e.shapeId}.${e.field} type mismatch`, e.loc);
3998
4083
  }
3999
4084
  }
@@ -4365,9 +4450,9 @@ function validateFunction(fn, functions, ffiByName, classes, records, unions, gl
4365
4450
  }
4366
4451
  def.arms.forEach((arm, i) => {
4367
4452
  const fieldType = arm.kind === "record"
4368
- ? records.get(arm.shapeId)?.fields.find((f) => f.name === e.field)?.type
4453
+ ? recordValidation.get(arm.shapeId)?.fields.get(e.field)
4369
4454
  : arm.kind === "object"
4370
- ? classes.get(arm.className)?.fields.find((f) => f.name === e.field)?.type
4455
+ ? classValidation.fields.get(arm.className)?.get(e.field)
4371
4456
  : undefined;
4372
4457
  if (!fieldType) {
4373
4458
  err(`unionDisc: arm ${i} of ${e.unionId} has no field "${e.field}"`, e.loc);
@@ -4430,7 +4515,7 @@ function validateFunction(fn, functions, ffiByName, classes, records, unions, gl
4430
4515
  return;
4431
4516
  }
4432
4517
  const literal = e.key.kind === "strLit" ? e.key.value : null;
4433
- const declared = literal !== null ? shape.fields.find((f) => f.name === literal)?.type : undefined;
4518
+ const declared = literal !== null ? recordValidation.get(shape.id)?.fields.get(literal) : undefined;
4434
4519
  if (declared) {
4435
4520
  if (!surfaces(declared)) {
4436
4521
  err(`unionKeyGet: arm ${i} field "${literal}" of type ${declared.kind} cannot surface as the result`, e.loc);
@@ -4651,11 +4736,11 @@ function validateFunction(fn, functions, ffiByName, classes, records, unions, gl
4651
4736
  const countName = e.fn === "fileHandle.read" || e.fn === "fileHandle.readv" ? "bytesRead" : "bytesWritten";
4652
4737
  const payloadName = e.fn === "fileHandle.readv" || e.fn === "fileHandle.writev" ? "buffers" : "buffer";
4653
4738
  const payload = e.args[1]?.type;
4654
- const count = shape?.fields.find((f) => f.name === countName);
4655
- const buffer = shape?.fields.find((f) => f.name === payloadName);
4739
+ const count = shape ? recordValidation.get(shape.id)?.fields.get(countName) : undefined;
4740
+ const buffer = shape ? recordValidation.get(shape.id)?.fields.get(payloadName) : undefined;
4656
4741
  const ok = shape !== undefined && !shape.tuple && shape.indexValue === undefined && shape.fields.length === 2 &&
4657
- count?.type.kind === "f64" && payload !== undefined && buffer !== undefined &&
4658
- typeEquals(buffer.type, payload);
4742
+ count?.kind === "f64" && payload !== undefined && buffer !== undefined &&
4743
+ typeEquals(buffer, payload);
4659
4744
  if (!ok) {
4660
4745
  err(`libCall ${e.fn} must return a promise of { ${countName}: number, ${payloadName} }`, e.loc);
4661
4746
  }
@@ -5948,17 +6033,17 @@ function validateFunction(fn, functions, ffiByName, classes, records, unions, gl
5948
6033
  break;
5949
6034
  }
5950
6035
  const rec = records.get(resultT.shapeId);
5951
- const doneF = rec?.fields.find((f) => f.name === "done");
5952
- const valueF = rec?.fields.find((f) => f.name === "value");
5953
- if (!rec || rec.fields.length !== 2 || doneF?.type.kind !== "bool" || valueF === undefined) {
6036
+ const doneF = rec ? recordValidation.get(rec.id)?.fields.get("done") : undefined;
6037
+ const valueF = rec ? recordValidation.get(rec.id)?.fields.get("value") : undefined;
6038
+ if (!rec || rec.fields.length !== 2 || doneF?.kind !== "bool" || valueF === undefined) {
5954
6039
  err(`genResume result record ${resultT.shapeId} is not { done: bool, value: V }`, e.loc);
5955
6040
  break;
5956
6041
  }
5957
- if (valueF.type.kind === "dyn")
6042
+ if (valueF.kind === "dyn")
5958
6043
  break;
5959
- const vdef = valueF.type.kind === "union" ? unions.get(valueF.type.unionId) : undefined;
6044
+ const vdef = valueF.kind === "union" ? unions.get(valueF.unionId) : undefined;
5960
6045
  if (!vdef || !vdef.arms.some((a) => a.kind === "undefinedT")) {
5961
- err(`genResume value slot ${typeKey(valueF.type)} is neither dyn nor an undefined-armed union`, e.loc);
6046
+ err(`genResume value slot ${typeKey(valueF)} is neither dyn nor an undefined-armed union`, e.loc);
5962
6047
  }
5963
6048
  break;
5964
6049
  }
@@ -6042,10 +6127,10 @@ function validateFunction(fn, functions, ffiByName, classes, records, unions, gl
6042
6127
  break;
6043
6128
  }
6044
6129
  const shape = records.get(e.type.shapeId);
6045
- const fields = new Map(shape?.fields.map((f) => [f.name, f.type]) ?? []);
6046
- const prom = fields.get("promise");
6047
- const resolve = fields.get("resolve");
6048
- const reject = fields.get("reject");
6130
+ const fields = recordValidation.get(e.type.shapeId)?.initializationFields;
6131
+ const prom = fields?.get("promise");
6132
+ const resolve = fields?.get("resolve");
6133
+ const reject = fields?.get("reject");
6049
6134
  if (!shape || shape.fields.length !== 3 || !prom || !resolve || !reject) {
6050
6135
  err("promiseWithResolvers record must be { promise, resolve, reject }", e.loc);
6051
6136
  break;
@@ -6357,14 +6442,14 @@ function validateFunction(fn, functions, ffiByName, classes, records, unions, gl
6357
6442
  checkExpr(s.obj);
6358
6443
  checkExpr(s.value);
6359
6444
  const cls = classes.get(s.className);
6360
- const field = cls?.fields.find((f) => f.name === s.field);
6445
+ const fieldType = classValidation.fields.get(s.className)?.get(s.field);
6361
6446
  if (!cls)
6362
6447
  err(`fieldSet on undeclared class "${s.className}"`, s.loc);
6363
- else if (!field)
6448
+ else if (!fieldType)
6364
6449
  err(`class ${s.className} has no field "${s.field}"`, s.loc);
6365
6450
  else {
6366
6451
  expectType(s.obj, { kind: "object", className: s.className }, "fieldSet receiver");
6367
- expectType(s.value, field.type, `fieldSet ${s.className}.${s.field}`);
6452
+ expectType(s.value, fieldType, `fieldSet ${s.className}.${s.field}`);
6368
6453
  }
6369
6454
  break;
6370
6455
  }
@@ -6372,14 +6457,14 @@ function validateFunction(fn, functions, ffiByName, classes, records, unions, gl
6372
6457
  checkExpr(s.obj);
6373
6458
  checkExpr(s.value);
6374
6459
  const shape = records.get(s.shapeId);
6375
- const field = shape?.fields.find((f) => f.name === s.field);
6460
+ const field = recordValidation.get(s.shapeId)?.fields.get(s.field);
6376
6461
  if (!shape)
6377
6462
  err(`recordSet on undeclared shape "${s.shapeId}"`, s.loc);
6378
6463
  else if (!field)
6379
6464
  err(`shape ${s.shapeId} has no field "${s.field}"`, s.loc);
6380
6465
  else {
6381
6466
  expectType(s.obj, { kind: "record", shapeId: s.shapeId }, "recordSet receiver");
6382
- expectType(s.value, field.type, `recordSet ${s.shapeId}.${s.field}`);
6467
+ expectType(s.value, field, `recordSet ${s.shapeId}.${s.field}`);
6383
6468
  }
6384
6469
  break;
6385
6470
  }