@scriptc/compiler 0.2.0 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (284) hide show
  1. package/ambient/scriptc-node-fallback.d.ts +84 -19
  2. package/dist/backend/cycle-analysis.js +1 -0
  3. package/dist/backend/cycle-analysis.js.map +1 -1
  4. package/dist/backend/llvm/classes.js +21 -5
  5. package/dist/backend/llvm/classes.js.map +1 -1
  6. package/dist/backend/llvm/dyn.d.ts +8 -0
  7. package/dist/backend/llvm/dyn.js +247 -108
  8. package/dist/backend/llvm/dyn.js.map +1 -1
  9. package/dist/backend/llvm/emitter.d.ts +6 -3
  10. package/dist/backend/llvm/emitter.js +99 -25
  11. package/dist/backend/llvm/emitter.js.map +1 -1
  12. package/dist/backend/llvm/emitter.test.js +9 -1
  13. package/dist/backend/llvm/emitter.test.js.map +1 -1
  14. package/dist/backend/llvm/expr-bytes.d.ts +1 -1
  15. package/dist/backend/llvm/expr-bytes.js +28 -17
  16. package/dist/backend/llvm/expr-bytes.js.map +1 -1
  17. package/dist/backend/llvm/expr-containers.js +5 -0
  18. package/dist/backend/llvm/expr-containers.js.map +1 -1
  19. package/dist/backend/llvm/expr-control.js +1 -1
  20. package/dist/backend/llvm/expr-control.js.map +1 -1
  21. package/dist/backend/llvm/expr-dynamic.js +47 -13
  22. package/dist/backend/llvm/expr-dynamic.js.map +1 -1
  23. package/dist/backend/llvm/expr-primitives.js +11 -1
  24. package/dist/backend/llvm/expr-primitives.js.map +1 -1
  25. package/dist/backend/llvm/expr-records.js +11 -3
  26. package/dist/backend/llvm/expr-records.js.map +1 -1
  27. package/dist/backend/llvm/expr-stream-bridges.js +187 -18
  28. package/dist/backend/llvm/expr-stream-bridges.js.map +1 -1
  29. package/dist/backend/llvm/expr-stream-callbacks.d.ts +2 -2
  30. package/dist/backend/llvm/expr-stream-callbacks.js +7 -57
  31. package/dist/backend/llvm/expr-stream-callbacks.js.map +1 -1
  32. package/dist/backend/llvm/lib-dispatch.js +41 -3
  33. package/dist/backend/llvm/lib-dispatch.js.map +1 -1
  34. package/dist/backend/llvm/lib-filesystem.js +12 -9
  35. package/dist/backend/llvm/lib-filesystem.js.map +1 -1
  36. package/dist/backend/llvm/lib-process.js +23 -3
  37. package/dist/backend/llvm/lib-process.js.map +1 -1
  38. package/dist/backend/llvm/lib-shared.js +80 -3
  39. package/dist/backend/llvm/lib-shared.js.map +1 -1
  40. package/dist/backend/llvm/shapes.js +8 -2
  41. package/dist/backend/llvm/shapes.js.map +1 -1
  42. package/dist/backend/may-throw.js +4 -0
  43. package/dist/backend/may-throw.js.map +1 -1
  44. package/dist/backend/native-codegen.d.ts +1 -1
  45. package/dist/backend/native-codegen.js +4 -3
  46. package/dist/backend/native-codegen.js.map +1 -1
  47. package/dist/backend/native-codegen.test.js +11 -0
  48. package/dist/backend/native-codegen.test.js.map +1 -1
  49. package/dist/backend/native-toolchain.d.ts +3 -6
  50. package/dist/backend/native-toolchain.js +53 -23
  51. package/dist/backend/native-toolchain.js.map +1 -1
  52. package/dist/backend/native-toolchain.test.js +72 -0
  53. package/dist/backend/native-toolchain.test.js.map +1 -1
  54. package/dist/backend/runtime-abi.d.ts +2 -2
  55. package/dist/backend/runtime-abi.js +2 -2
  56. package/dist/backend/runtime-pack-native.js +4 -1
  57. package/dist/backend/runtime-pack-native.js.map +1 -1
  58. package/dist/backend/runtime-pack-targets.test.js +1 -1
  59. package/dist/backend/runtime-pack.test.js +1 -1
  60. package/dist/backend/target-diagnostics.d.ts +5 -0
  61. package/dist/backend/target-diagnostics.js +72 -1
  62. package/dist/backend/target-diagnostics.js.map +1 -1
  63. package/dist/backend/target-diagnostics.test.d.ts +1 -0
  64. package/dist/backend/target-diagnostics.test.js +68 -0
  65. package/dist/backend/target-diagnostics.test.js.map +1 -0
  66. package/dist/backend/targets.js +10 -6
  67. package/dist/backend/targets.js.map +1 -1
  68. package/dist/backend/targets.test.js +7 -0
  69. package/dist/backend/targets.test.js.map +1 -1
  70. package/dist/compat/fetch-profile.js +34 -4
  71. package/dist/compat/fetch-profile.js.map +1 -1
  72. package/dist/coverage/surface-manifest.js +5 -3
  73. package/dist/coverage/surface-manifest.js.map +1 -1
  74. package/dist/diagnostics/diagnostic.js +2 -7
  75. package/dist/diagnostics/diagnostic.js.map +1 -1
  76. package/dist/executable/executable-cache.d.ts +7 -3
  77. package/dist/executable/executable-cache.js +47 -11
  78. package/dist/executable/executable-cache.js.map +1 -1
  79. package/dist/executable/executable-cache.test.js +109 -2
  80. package/dist/executable/executable-cache.test.js.map +1 -1
  81. package/dist/executable/prepare.js +2 -1
  82. package/dist/executable/prepare.js.map +1 -1
  83. package/dist/frontend/builtin-modules.d.ts +1 -1
  84. package/dist/frontend/builtin-modules.js +1 -1
  85. package/dist/frontend/builtin-modules.js.map +1 -1
  86. package/dist/frontend/function-completion.d.ts +6 -0
  87. package/dist/frontend/function-completion.js +50 -0
  88. package/dist/frontend/function-completion.js.map +1 -0
  89. package/dist/frontend/lowering/abstract-equality.js +5 -1
  90. package/dist/frontend/lowering/abstract-equality.js.map +1 -1
  91. package/dist/frontend/lowering/checked-iterable-spread.d.ts +4 -0
  92. package/dist/frontend/lowering/checked-iterable-spread.js +30 -0
  93. package/dist/frontend/lowering/checked-iterable-spread.js.map +1 -0
  94. package/dist/frontend/lowering/checked-promise-all.d.ts +5 -0
  95. package/dist/frontend/lowering/checked-promise-all.js +31 -0
  96. package/dist/frontend/lowering/checked-promise-all.js.map +1 -0
  97. package/dist/frontend/lowering/class-callbacks.d.ts +4 -4
  98. package/dist/frontend/lowering/class-callbacks.js +29 -17
  99. package/dist/frontend/lowering/class-callbacks.js.map +1 -1
  100. package/dist/frontend/lowering/class-construction.d.ts +14 -0
  101. package/dist/frontend/lowering/class-construction.js +185 -0
  102. package/dist/frontend/lowering/class-construction.js.map +1 -0
  103. package/dist/frontend/lowering/class-descriptors.d.ts +2 -1
  104. package/dist/frontend/lowering/class-descriptors.js +58 -10
  105. package/dist/frontend/lowering/class-descriptors.js.map +1 -1
  106. package/dist/frontend/lowering/class-dynamic-dispatch.d.ts +36 -1
  107. package/dist/frontend/lowering/class-dynamic-dispatch.js +965 -91
  108. package/dist/frontend/lowering/class-dynamic-dispatch.js.map +1 -1
  109. package/dist/frontend/lowering/class-dynamic-dispatch.test.js +66 -3
  110. package/dist/frontend/lowering/class-dynamic-dispatch.test.js.map +1 -1
  111. package/dist/frontend/lowering/class-instance-constructor.d.ts +1 -1
  112. package/dist/frontend/lowering/class-instance-constructor.js +15 -42
  113. package/dist/frontend/lowering/class-instance-constructor.js.map +1 -1
  114. package/dist/frontend/lowering/class-method-values.d.ts +20 -1
  115. package/dist/frontend/lowering/class-method-values.js +120 -24
  116. package/dist/frontend/lowering/class-method-values.js.map +1 -1
  117. package/dist/frontend/lowering/class-prototypes.d.ts +10 -3
  118. package/dist/frontend/lowering/class-prototypes.js +188 -21
  119. package/dist/frontend/lowering/class-prototypes.js.map +1 -1
  120. package/dist/frontend/lowering/class-runtime-statics.d.ts +8 -0
  121. package/dist/frontend/lowering/class-runtime-statics.js +84 -0
  122. package/dist/frontend/lowering/class-runtime-statics.js.map +1 -0
  123. package/dist/frontend/lowering/class-static-data.d.ts +1 -0
  124. package/dist/frontend/lowering/class-static-data.js +6 -4
  125. package/dist/frontend/lowering/class-static-data.js.map +1 -1
  126. package/dist/frontend/lowering/containers/bytes.js +66 -17
  127. package/dist/frontend/lowering/containers/bytes.js.map +1 -1
  128. package/dist/frontend/lowering/containers/string-and-regexp.js +24 -47
  129. package/dist/frontend/lowering/containers/string-and-regexp.js.map +1 -1
  130. package/dist/frontend/lowering/deferred-module-initializers.d.ts +13 -0
  131. package/dist/frontend/lowering/deferred-module-initializers.js +410 -0
  132. package/dist/frontend/lowering/deferred-module-initializers.js.map +1 -0
  133. package/dist/frontend/lowering/deferred-module-initializers.test.d.ts +1 -0
  134. package/dist/frontend/lowering/deferred-module-initializers.test.js +92 -0
  135. package/dist/frontend/lowering/deferred-module-initializers.test.js.map +1 -0
  136. package/dist/frontend/lowering/error-methods.d.ts +4 -1
  137. package/dist/frontend/lowering/error-methods.js +100 -1
  138. package/dist/frontend/lowering/error-methods.js.map +1 -1
  139. package/dist/frontend/lowering/expressions/native-proxy.js +3 -1
  140. package/dist/frontend/lowering/expressions/native-proxy.js.map +1 -1
  141. package/dist/frontend/lowering/expressions/nullish-assignment.js +19 -11
  142. package/dist/frontend/lowering/expressions/nullish-assignment.js.map +1 -1
  143. package/dist/frontend/lowering/expressions/object-literals.js +68 -7
  144. package/dist/frontend/lowering/expressions/object-literals.js.map +1 -1
  145. package/dist/frontend/lowering/expressions/optional-chains.d.ts +3 -4
  146. package/dist/frontend/lowering/expressions/optional-chains.js +72 -20
  147. package/dist/frontend/lowering/expressions/optional-chains.js.map +1 -1
  148. package/dist/frontend/lowering/function-context.d.ts +11 -3
  149. package/dist/frontend/lowering/function-context.js +11 -5
  150. package/dist/frontend/lowering/function-context.js.map +1 -1
  151. package/dist/frontend/lowering/generator-dynamic-dispatch.d.ts +1 -0
  152. package/dist/frontend/lowering/generator-dynamic-dispatch.js +39 -1
  153. package/dist/frontend/lowering/generator-dynamic-dispatch.js.map +1 -1
  154. package/dist/frontend/lowering/lib-boundary.d.ts +2 -0
  155. package/dist/frontend/lowering/lib-boundary.js +54 -1
  156. package/dist/frontend/lowering/lib-boundary.js.map +1 -1
  157. package/dist/frontend/lowering/lower-builtin-values.d.ts +7 -1
  158. package/dist/frontend/lowering/lower-builtin-values.js +113 -3
  159. package/dist/frontend/lowering/lower-builtin-values.js.map +1 -1
  160. package/dist/frontend/lowering/lower-builtins.d.ts +10 -14
  161. package/dist/frontend/lowering/lower-builtins.js +443 -88
  162. package/dist/frontend/lowering/lower-builtins.js.map +1 -1
  163. package/dist/frontend/lowering/lower-calls.d.ts +8 -5
  164. package/dist/frontend/lowering/lower-calls.js +717 -142
  165. package/dist/frontend/lowering/lower-calls.js.map +1 -1
  166. package/dist/frontend/lowering/lower-classes.d.ts +18 -13
  167. package/dist/frontend/lowering/lower-classes.js +1076 -188
  168. package/dist/frontend/lowering/lower-classes.js.map +1 -1
  169. package/dist/frontend/lowering/lower-containers.d.ts +9 -0
  170. package/dist/frontend/lowering/lower-containers.js +172 -51
  171. package/dist/frontend/lowering/lower-containers.js.map +1 -1
  172. package/dist/frontend/lowering/lower-dgram.js +3 -2
  173. package/dist/frontend/lowering/lower-dgram.js.map +1 -1
  174. package/dist/frontend/lowering/lower-exprs.d.ts +12 -8
  175. package/dist/frontend/lowering/lower-exprs.js +802 -232
  176. package/dist/frontend/lowering/lower-exprs.js.map +1 -1
  177. package/dist/frontend/lowering/lower-generators.js +17 -2
  178. package/dist/frontend/lowering/lower-generators.js.map +1 -1
  179. package/dist/frontend/lowering/lower-inspect.js +15 -1
  180. package/dist/frontend/lowering/lower-inspect.js.map +1 -1
  181. package/dist/frontend/lowering/lower-island.d.ts +2 -0
  182. package/dist/frontend/lowering/lower-island.js +36 -10
  183. package/dist/frontend/lowering/lower-island.js.map +1 -1
  184. package/dist/frontend/lowering/lower-island.test.d.ts +1 -0
  185. package/dist/frontend/lowering/lower-island.test.js +51 -0
  186. package/dist/frontend/lowering/lower-island.test.js.map +1 -0
  187. package/dist/frontend/lowering/lower-modules.d.ts +1 -3
  188. package/dist/frontend/lowering/lower-modules.js +380 -73
  189. package/dist/frontend/lowering/lower-modules.js.map +1 -1
  190. package/dist/frontend/lowering/lower-modules.test.d.ts +1 -0
  191. package/dist/frontend/lowering/lower-modules.test.js +107 -0
  192. package/dist/frontend/lowering/lower-modules.test.js.map +1 -0
  193. package/dist/frontend/lowering/lower-stmts.d.ts +7 -3
  194. package/dist/frontend/lowering/lower-stmts.js +221 -47
  195. package/dist/frontend/lowering/lower-stmts.js.map +1 -1
  196. package/dist/frontend/lowering/lower-stmts.test.d.ts +1 -0
  197. package/dist/frontend/lowering/lower-stmts.test.js +77 -0
  198. package/dist/frontend/lowering/lower-stmts.test.js.map +1 -0
  199. package/dist/frontend/lowering/lower-stream.js +3 -1
  200. package/dist/frontend/lowering/lower-stream.js.map +1 -1
  201. package/dist/frontend/lowering/lower-url.d.ts +2 -1
  202. package/dist/frontend/lowering/lower-url.js +32 -9
  203. package/dist/frontend/lowering/lower-url.js.map +1 -1
  204. package/dist/frontend/lowering/lowerer.d.ts +53 -10
  205. package/dist/frontend/lowering/lowerer.js +390 -77
  206. package/dist/frontend/lowering/lowerer.js.map +1 -1
  207. package/dist/frontend/lowering/lowerer.test.js +187 -3
  208. package/dist/frontend/lowering/lowerer.test.js.map +1 -1
  209. package/dist/frontend/lowering/module-namespace-elements.d.ts +7 -0
  210. package/dist/frontend/lowering/module-namespace-elements.js +54 -0
  211. package/dist/frontend/lowering/module-namespace-elements.js.map +1 -0
  212. package/dist/frontend/lowering/object-factory-new.d.ts +4 -3
  213. package/dist/frontend/lowering/object-factory-new.js +27 -14
  214. package/dist/frontend/lowering/object-factory-new.js.map +1 -1
  215. package/dist/frontend/lowering/surfaces.d.ts +3 -0
  216. package/dist/frontend/lowering/surfaces.js +48 -19
  217. package/dist/frontend/lowering/surfaces.js.map +1 -1
  218. package/dist/frontend/lowering/text-decoder-encoding.d.ts +2 -0
  219. package/dist/frontend/lowering/text-decoder-encoding.js +8 -0
  220. package/dist/frontend/lowering/text-decoder-encoding.js.map +1 -1
  221. package/dist/frontend/lowering/text-decoder-encoding.test.js +2 -1
  222. package/dist/frontend/lowering/text-decoder-encoding.test.js.map +1 -1
  223. package/dist/frontend/lowering/unregistered-class-types.test.js +3 -3
  224. package/dist/frontend/lowering/unregistered-class-types.test.js.map +1 -1
  225. package/dist/frontend/npm-static-prune.js +116 -34
  226. package/dist/frontend/npm-static-prune.js.map +1 -1
  227. package/dist/frontend/npm-static-prune.test.js +8 -0
  228. package/dist/frontend/npm-static-prune.test.js.map +1 -1
  229. package/dist/frontend/program.d.ts +1 -1
  230. package/dist/frontend/program.js +57 -26
  231. package/dist/frontend/program.js.map +1 -1
  232. package/dist/frontend/program.test.js +18 -0
  233. package/dist/frontend/program.test.js.map +1 -1
  234. package/dist/frontend/ts7/ast.js +4 -3
  235. package/dist/frontend/ts7/ast.js.map +1 -1
  236. package/dist/frontend/ts7/semantic-json.js +5 -10
  237. package/dist/frontend/ts7/semantic-json.js.map +1 -1
  238. package/dist/frontend/type-mapper.d.ts +9 -1
  239. package/dist/frontend/type-mapper.js +169 -23
  240. package/dist/frontend/type-mapper.js.map +1 -1
  241. package/dist/frontend/type-mapper.test.js +23 -0
  242. package/dist/frontend/type-mapper.test.js.map +1 -1
  243. package/dist/frontend/union-discriminants.d.ts +1 -1
  244. package/dist/frontend/union-discriminants.js +24 -9
  245. package/dist/frontend/union-discriminants.js.map +1 -1
  246. package/dist/index.js +16 -10
  247. package/dist/index.js.map +1 -1
  248. package/dist/ir/analysis.js +2 -0
  249. package/dist/ir/analysis.js.map +1 -1
  250. package/dist/ir/ir.d.ts +74 -41
  251. package/dist/ir/ir.js +167 -43
  252. package/dist/ir/ir.js.map +1 -1
  253. package/dist/ir/ir.test.js +35 -0
  254. package/dist/ir/ir.test.js.map +1 -1
  255. package/dist/ir/serialize.js +35 -3
  256. package/dist/ir/serialize.js.map +1 -1
  257. package/dist/ir/serialize.test.js +26 -1
  258. package/dist/ir/serialize.test.js.map +1 -1
  259. package/dist/ir/traverse.d.ts +0 -3
  260. package/dist/ir/traverse.js +33 -20
  261. package/dist/ir/traverse.js.map +1 -1
  262. package/dist/ir/validate.js +136 -16
  263. package/dist/ir/validate.js.map +1 -1
  264. package/dist/ir/validate.test.js +77 -1
  265. package/dist/ir/validate.test.js.map +1 -1
  266. package/dist/library/compiler-self-identity.js +1 -1
  267. package/dist/library/compiler-self-identity.test.js +2 -2
  268. package/dist/library/compiler-self-identity.test.js.map +1 -1
  269. package/dist/library/int-infer.d.ts +2 -2
  270. package/dist/library/int-infer.js +10 -7
  271. package/dist/library/int-infer.js.map +1 -1
  272. package/dist/library/int-infer.test.js +44 -0
  273. package/dist/library/int-infer.test.js.map +1 -1
  274. package/dist/library/prepare.js +2 -1
  275. package/dist/library/prepare.js.map +1 -1
  276. package/dist/native/sanitizer.js +3 -1
  277. package/dist/native/sanitizer.js.map +1 -1
  278. package/dist/native/toolchain.d.ts +1 -1
  279. package/dist/native/toolchain.js +26 -6
  280. package/dist/native/toolchain.js.map +1 -1
  281. package/dist/native/toolchain.test.js +53 -1
  282. package/dist/native/toolchain.test.js.map +1 -1
  283. package/package.json +20 -20
  284. package/surface-manifest.json +170 -93
@@ -1,26 +1,76 @@
1
- import { BOOL, DYN, DYN_CLASS_PROPERTIES as PROPERTY_BAG, STRING, VOID, canConvertToDyn, canDynCheckTo, isClassOwnEnumerableFieldName, isDynTypedRefType, isUnitType, typeEquals, typeKey } from "../../ir/ir.js";
1
+ import { BOOL, DYN, F64, DYN_CLASS_PROPERTIES as PROPERTY_BAG, STRING, VOID, canConvertToDyn, canDynCheckTo, isClassOwnEnumerableFieldName, isDynTypedRefType, isUnitType, typeEquals, typeKey } from "../../ir/ir.js";
2
2
  import { streamTypedRefEligible } from "../../ir/analysis.js";
3
3
  import { varRef } from "../../ir/build.js";
4
- import { everyStmtList, transformStmtList } from "../../ir/traverse.js";
4
+ import { everyStmtList, transformExpr, transformStmtList } from "../../ir/traverse.js";
5
5
  import { dynUndefinedExpr, PoisonError } from "./lowerer.js";
6
6
  import { implicitDefaultInstance } from "./lower-calls.js";
7
- import { accessorCall, findGenericMethodOn, findMethodOn, genericOverrideBelow, upcastTo } from "./lower-classes.js";
8
- import { classPrototypeData, hasClassPrototypeData } from "./class-prototypes.js";
9
- import { errorToStringCall, errorToStringMethod, refreshErrorMethodDispatch } from "./error-methods.js";
7
+ import { accessorCall, classValueRef, findGenericMethodOn, findGenericStaticOn, findMethodOn, findStaticOn, genericOverrideBelow, upcastTo } from "./lower-classes.js";
8
+ import { classPrototypeData, hasClassPrototypeData, reflectClassPrototype } from "./class-prototypes.js";
9
+ import { errorPropertyRead, errorPropertyWrite, errorToStringCall, errorToStringMethod, refreshErrorPropertyDispatch, refreshErrorMethodDispatch } from "./error-methods.js";
10
10
  import { GeneratorDynamicDispatch } from "./generator-dynamic-dispatch.js";
11
- import { classMethodValue } from "./class-method-values.js";
11
+ import { classMethodValue, reflectedClassStaticMethodValue, refreshClassMethodValueSelections } from "./class-method-values.js";
12
+ import { classStaticData } from "./class-static-data.js";
13
+ import { isJsSourceFile } from "../program.js";
12
14
  import * as ts from "../ts7/adapter.js";
13
15
  import { SYMBOL_T } from "../../ir/ir.js";
14
16
  import { isClassCallback } from "./class-callbacks.js";
15
- export function classInstanceOf(lowerer, value, info, loc) {
16
- const name = `%dyn.class.instanceof:${info.def.name}`;
17
- if (!lowerer.liftedFns.some((fn) => fn.name === name))
17
+ import { refreshDescriptorGuards } from "./class-descriptors.js";
18
+ import { ClassConstructionDispatch } from "./class-construction.js";
19
+ import { emitterRooted } from "./lower-event-emitter.js";
20
+ const STREAM_METHODS = new Set(["on", "addListener", "once", "prependListener", "prependOnceListener", "off", "removeListener", "read", "pause", "resume", "isPaused", "destroy"]);
21
+ const EMITTER_METHODS = new Set(["emit", "on", "addListener", "once", "prependListener", "prependOnceListener", "off", "removeListener"]);
22
+ const STREAM_BOOL_PROPERTIES = new Set(["readable", "readableEnded", "writable", "writableEnded", "writableFinished", "writableNeedDrain", "destroyed", "closed", "readableObjectMode", "writableObjectMode", "allowHalfOpen"]);
23
+ const STREAM_NUM_PROPERTIES = new Set(["readableLength", "readableHighWaterMark", "writableLength", "writableHighWaterMark", "writableCorked"]);
24
+ const streamProperty = (name) => STREAM_BOOL_PROPERTIES.has(name) || STREAM_NUM_PROPERTIES.has(name);
25
+ function symbolMemberKey(lowerer, info, name, loc) {
26
+ if (name.startsWith("sym:"))
27
+ return lowerer.coerceToExpected({ kind: "libCall", fn: "sym.wellKnown",
28
+ args: [{ kind: "strLit", value: name.slice(4), type: STRING, loc }], type: SYMBOL_T, loc }, DYN);
29
+ if (info.symbolFields !== undefined) {
30
+ for (const [symbol, member] of info.symbolFields) {
31
+ if (member !== name)
32
+ continue;
33
+ const global = lowerer.globalsBySymbol.get(symbol);
34
+ if (global)
35
+ return lowerer.coerceToExpected(varRef(global.id, global.type, loc), DYN);
36
+ }
37
+ }
38
+ if (info.symbolMethods !== undefined) {
39
+ for (const [symbol, member] of info.symbolMethods) {
40
+ if (member !== name)
41
+ continue;
42
+ const global = lowerer.globalsBySymbol.get(symbol);
43
+ if (global)
44
+ return lowerer.coerceToExpected(varRef(global.id, global.type, loc), DYN);
45
+ }
46
+ }
47
+ return null;
48
+ }
49
+ export function classInstanceOf(lowerer, value, info, loc, classValue) {
50
+ const name = `%dyn.class.${classValue ? "valueInstanceof" : "instanceof"}:${info.def.name}`;
51
+ const params = [{ localId: "value", name: "value", type: DYN },
52
+ ...(classValue ? [{ localId: "class", name: "class", type: classValue.type }] : [])];
53
+ if (!lowerer.liftedFns.some((fn) => fn.name === name)) {
54
+ const candidate = varRef("candidate", DYN, loc);
55
+ const prototype = classPrototypeData(lowerer, info, loc, classValue ? varRef("class", classValue.type, loc) : undefined);
56
+ const body = prototype ? [
57
+ { kind: "varDecl", localId: "candidate", init: varRef("value", DYN, loc), loc },
58
+ { kind: "while", cond: { kind: "ternary", cond: { kind: "dynTest", test: "nullish", value: candidate, type: BOOL, loc },
59
+ then: { kind: "boolLit", value: false, type: BOOL, loc }, else_: { kind: "dynTest", test: "object", value: candidate, type: BOOL, loc }, type: BOOL, loc }, body: [
60
+ { kind: "assign", localId: "candidate", value: { kind: "libCall", fn: "dyn.getPrototype", args: [candidate], type: DYN, loc, prototypeIdentityOnly: true }, loc },
61
+ { kind: "if", cond: { kind: "dynScalarEq", left: candidate, right: prototype, type: BOOL, loc }, then: [
62
+ { kind: "return", value: { kind: "boolLit", value: true, type: BOOL, loc }, loc },
63
+ ], else_: null, loc },
64
+ ], loc },
65
+ ] : [];
66
+ body.push({ kind: "return", value: { kind: "boolLit", value: false, type: BOOL, loc }, loc });
18
67
  lowerer.liftedFns.push({
19
- name, params: [{ localId: "value", name: "value", type: DYN }], returnType: BOOL,
20
- locals: [{ id: "value", name: "value", type: DYN, mutable: false }],
21
- body: [{ kind: "return", value: { kind: "boolLit", value: false, type: BOOL, loc }, loc }], loc,
68
+ name, params, returnType: BOOL,
69
+ locals: [...params.map((p) => ({ id: p.localId, name: p.name, type: p.type, mutable: false })),
70
+ ...(prototype ? [{ id: "candidate", name: "candidate", type: DYN, mutable: true }] : [])], body, loc,
22
71
  });
23
- return { kind: "call", callee: name, args: [value], type: BOOL, loc };
72
+ }
73
+ return { kind: "call", callee: name, args: [value, ...(classValue ? [classValue] : [])], type: BOOL, loc };
24
74
  }
25
75
  export function classPropertiesHelper(lowerer, loc) {
26
76
  const name = "%dyn.class.properties";
@@ -35,6 +85,82 @@ export function classPropertiesHelper(lowerer, loc) {
35
85
  lowerer.liftedFns.push(helper);
36
86
  return helper;
37
87
  }
88
+ /** A for-in presence recheck needs only the live property table, avoiding
89
+ * value conversion or a dispatch branch for every possible member name. */
90
+ export function classForInHasKey(lowerer, value, key, loc) {
91
+ const name = "%dyn.class.forInHasKey";
92
+ if (!lowerer.liftedFns.some((fn) => fn.name === name)) {
93
+ lowerer.liftedFns.push({ name, params: [{ localId: "value", name: "value", type: DYN }, { localId: "key", name: "key", type: STRING }], returnType: BOOL,
94
+ locals: [{ id: "value", name: "value", type: DYN, mutable: false }, { id: "key", name: "key", type: STRING, mutable: false }],
95
+ body: [{ kind: "return", value: { kind: "libCall", fn: "dyn.hasKey", args: [
96
+ { kind: "call", callee: classPropertiesHelper(lowerer, loc).name, args: [varRef("value", DYN, loc)], type: DYN, loc },
97
+ varRef("key", STRING, loc),
98
+ ], type: BOOL, loc }, loc }], loc,
99
+ });
100
+ }
101
+ return { kind: "call", callee: name, args: [value, key], type: BOOL, loc };
102
+ }
103
+ /** Record field creation after the dispatch worklist closes, including writes
104
+ * in generated member tables. Native values stay in their slots; the bag
105
+ * owns only their property presence, attributes, and insertion position. */
106
+ export function trackClassFieldCreation(lowerer, functions) {
107
+ if (![...lowerer.classes.values()].some((info) => info.def.tracksOwnFields))
108
+ return;
109
+ const loc = functions[0].loc;
110
+ const name = "%dyn.class.fieldCreated";
111
+ const value = varRef("value", DYN, loc), key = varRef("key", DYN, loc);
112
+ const bag = varRef("bag", DYN, loc);
113
+ const created = (receiver, className, field, loc) => ({ kind: "exprStmt", expr: {
114
+ kind: "call", callee: name, args: [lowerer.coerceToExpected(receiver, DYN), symbolMemberKey(lowerer, lowerer.classes.get(className), field, loc) ??
115
+ lowerer.coerceToExpected({ kind: "strLit", value: field, type: STRING, loc }, DYN)], type: VOID, loc,
116
+ }, loc });
117
+ const tracked = (className, field) => !!lowerer.classes.get(className)?.def.tracksOwnFields && (isClassOwnEnumerableFieldName(field) ||
118
+ symbolMemberKey(lowerer, lowerer.classes.get(className), field, loc) !== null);
119
+ for (const fn of functions) {
120
+ const instrumented = new WeakSet();
121
+ const local = (type, loc) => {
122
+ const id = `%fieldCreated.${fn.locals.length}`;
123
+ fn.locals.push({ id, name: id, type, mutable: false });
124
+ return { kind: "varRef", localId: id, type, loc };
125
+ };
126
+ fn.body = transformStmtList(fn.body, {
127
+ stmt: (statement) => {
128
+ if (statement.kind !== "fieldSet" || instrumented.has(statement) || !tracked(statement.className, statement.field))
129
+ return statement;
130
+ const receiver = local(statement.obj.type, statement.loc);
131
+ const store = { ...statement, obj: receiver };
132
+ instrumented.add(store);
133
+ return { kind: "block", body: [
134
+ { kind: "varDecl", localId: receiver.localId, init: statement.obj, loc: statement.loc },
135
+ store, created(receiver, statement.className, statement.field, statement.loc),
136
+ ], loc: statement.loc };
137
+ },
138
+ expr: (expression) => {
139
+ if (expression.kind !== "fieldIncDec" || instrumented.has(expression) || !tracked(expression.className, expression.field))
140
+ return expression;
141
+ const receiver = local(expression.obj.type, expression.loc), result = local(expression.type, expression.loc);
142
+ const update = { ...expression, obj: receiver };
143
+ instrumented.add(update);
144
+ return { kind: "seqExpr", stmts: [
145
+ { kind: "varDecl", localId: receiver.localId, init: expression.obj, loc: expression.loc },
146
+ { kind: "varDecl", localId: result.localId, init: update, loc: expression.loc },
147
+ created(receiver, expression.className, expression.field, expression.loc),
148
+ ], result, type: expression.type, loc: expression.loc };
149
+ },
150
+ });
151
+ }
152
+ functions.push({ name, params: [{ localId: "value", name: "value", type: DYN }, { localId: "key", name: "key", type: DYN }], returnType: VOID,
153
+ locals: [{ id: "value", name: "value", type: DYN, mutable: false }, { id: "key", name: "key", type: DYN, mutable: false }, { id: "bag", name: "bag", type: DYN, mutable: false }],
154
+ body: [
155
+ { kind: "varDecl", localId: "bag", init: { kind: "call", callee: classPropertiesHelper(lowerer, loc).name, args: [value], type: DYN, loc }, loc },
156
+ { kind: "if", cond: { kind: "unary", op: "!", operand: { kind: "libCall", fn: "dyn.hasOwnComputed", args: [bag, key], type: BOOL, loc }, type: BOOL, loc }, then: [
157
+ { kind: "exprStmt", expr: { kind: "libCall", fn: "dyn.defineProperty", args: [bag, lowerer.coerceToExpected(key, DYN), { kind: "dynObjLit", fields: [
158
+ { key: { kind: "strLit", value: "value", type: STRING, loc }, value: dynUndefinedExpr(loc) },
159
+ ...["writable", "enumerable", "configurable"].map((key) => ({ key: { kind: "strLit", value: key, type: STRING, loc }, value: lowerer.coerceToExpected({ kind: "boolLit", value: true, type: BOOL, loc }, DYN) })),
160
+ ], type: DYN, loc }], type: DYN, loc }, loc },
161
+ ], else_: null, loc },
162
+ ], loc, });
163
+ }
38
164
  /** Calls on native class capsules keep the instance's compiled methods. The
39
165
  * reachable-body fixed point discovers both the boxed classes and method
40
166
  * names before generating checked native dispatch; ordinary dyn receivers
@@ -45,20 +171,51 @@ export class ClassDynamicDispatch {
45
171
  this.asyncFree = asyncFree;
46
172
  }
47
173
  generatorDispatch = new GeneratorDynamicDispatch();
174
+ constructionDispatch = new ClassConstructionDispatch();
48
175
  boxed = new Set();
176
+ boxedConstructors = new Set();
49
177
  dispatches = new Map();
50
178
  properties = new Map();
51
179
  computed = new Map();
180
+ computedClassHelpers = new Map();
181
+ normalization = null;
182
+ normalizationKey = "";
183
+ staticPropertyBag = null;
184
+ staticBagClasses = new Set();
52
185
  propertyBag = null;
53
186
  errorStrings = new Map();
54
187
  bagClasses = new Set();
55
188
  bagInitializers = new Map();
56
189
  typedPropertyBags = new Map();
57
190
  generated = new Set();
191
+ constructDispatch = null;
192
+ constructors = new Map();
193
+ constructed = new Set();
58
194
  instanceTests = new Map();
195
+ jsonPrototypes = new Set();
196
+ serializesClasses = false;
197
+ descriptorHelper = null;
198
+ prototypeHelper = null;
199
+ prototypeClasses = new Set();
200
+ prototypeIdentityHelper = null;
201
+ prototypeIdentityClasses = new Set();
202
+ instancePrototypes = new Map();
203
+ checkedCasts = new Map();
204
+ enumeration = null;
205
+ enumerationClasses = new Set();
59
206
  process(lowerer, functions) {
207
+ // Dispatch tables grow between worklist waves. Keep their normalization
208
+ // entry ahead of every newly inserted branch after rebuilding the tables.
209
+ for (const fn of this.normalizedDispatches()) {
210
+ const first = fn.body[0];
211
+ if (first?.kind === "assign" && first.value.kind === "call" && first.value.callee === this.normalization?.name)
212
+ fn.body.shift();
213
+ }
60
214
  let changed = refreshErrorMethodDispatch(lowerer, functions);
215
+ changed = refreshClassMethodValueSelections(lowerer) || changed;
216
+ changed = refreshErrorPropertyDispatch(lowerer, functions) || changed;
61
217
  changed = this.generatorDispatch.process(lowerer, functions, this.generated) || changed;
218
+ changed = this.constructionDispatch.process(lowerer, functions) || changed;
62
219
  const seenTypes = new Set();
63
220
  const rewrite = new Set();
64
221
  const discover = (type) => {
@@ -66,8 +223,12 @@ export class ClassDynamicDispatch {
66
223
  if (seenTypes.has(key))
67
224
  return;
68
225
  seenTypes.add(key);
69
- if (isDynTypedRefType(type))
70
- this.boxed.add(type.className);
226
+ if (type.kind === "object") {
227
+ if (isDynTypedRefType(type))
228
+ this.boxed.add(type.className);
229
+ }
230
+ else if (type.kind === "classval")
231
+ this.boxedConstructors.add(type.className);
71
232
  else if (type.kind === "array")
72
233
  discover(type.elem);
73
234
  else if (type.kind === "record") {
@@ -85,12 +246,27 @@ export class ClassDynamicDispatch {
85
246
  };
86
247
  for (const fn of functions)
87
248
  everyStmtList(fn.body, {
88
- stmt: () => true,
249
+ stmt: (statement) => {
250
+ // Field-creation instrumentation can box a base-constructor receiver
251
+ // even when only its derived instances otherwise cross the boundary.
252
+ if (statement.kind === "fieldSet" && lowerer.classes.get(statement.className)?.def.tracksOwnFields &&
253
+ isClassOwnEnumerableFieldName(statement.field))
254
+ discover(statement.obj.type);
255
+ return true;
256
+ },
89
257
  expr: (expr) => {
258
+ if (expr.kind === "jsonStringify" || expr.kind === "libCall" &&
259
+ (expr.fn === "json.stringifyValue" || expr.fn === "json.stringifyReplacer")) {
260
+ this.serializesClasses = true;
261
+ }
90
262
  switch (expr.kind) {
91
263
  case "dynFrom":
92
264
  discover(expr.value.type);
93
265
  break;
266
+ case "fieldIncDec":
267
+ if (lowerer.classes.get(expr.className)?.def.tracksOwnFields && isClassOwnEnumerableFieldName(expr.field))
268
+ discover(expr.obj.type);
269
+ break;
94
270
  case "call":
95
271
  if (expr.callee === "%dyn.class.properties" && expr.args[0]?.kind === "dynFrom" && isDynTypedRefType(expr.args[0].value.type))
96
272
  rewrite.add(fn);
@@ -99,9 +275,13 @@ export class ClassDynamicDispatch {
99
275
  case "dynInvoke":
100
276
  rewrite.add(fn);
101
277
  break;
278
+ case "dynCheck":
279
+ if (isDynTypedRefType(expr.type))
280
+ rewrite.add(fn);
281
+ break;
102
282
  case "libCall":
103
- if (expr.fn === "dyn.keySet" || expr.fn === "dyn.keySetComputed" || (expr.fn === "dyn.iterator" || expr.fn === "dyn.arrayFromIterator") || expr.fn === "dyn.toString" || expr.fn === "dyn.stringConstructor" ||
104
- expr.fn === "dyn.hasKeyComputed" || expr.fn === "dyn.hasOwnComputed" || expr.fn === "dyn.propertyIsEnumerableComputed")
283
+ if (expr.fn === "bytes.construct" || expr.fn === "dyn.keySet" || expr.fn === "dyn.keySetComputed" || (expr.fn === "dyn.iterator" || expr.fn === "dyn.arrayFromIterator") || expr.fn === "dyn.toString" || expr.fn === "dyn.stringConstructor" ||
284
+ expr.fn === "dyn.forInKeys" || expr.fn === "dyn.hasKeyComputed" || expr.fn === "dyn.hasOwnComputed" || expr.fn === "dyn.propertyIsEnumerableComputed" || expr.fn === "dyn.defineProperty" || expr.fn === "dyn.getPrototype" || expr.fn === "dyn.reflectGet" || expr.fn === "dyn.reflectSet")
105
285
  rewrite.add(fn);
106
286
  break;
107
287
  }
@@ -111,9 +291,9 @@ export class ClassDynamicDispatch {
111
291
  if (this.boxed.size === 0 && rewrite.size === 0)
112
292
  return changed;
113
293
  for (const fn of functions) {
114
- if (!fn.name.startsWith("%dyn.class.instanceof:"))
294
+ if (!fn.name.startsWith("%dyn.class.instanceof:") && !fn.name.startsWith("%dyn.class.valueInstanceof:"))
115
295
  continue;
116
- const target = fn.name.slice("%dyn.class.instanceof:".length);
296
+ const target = fn.name.slice(fn.name.indexOf(":") + 1);
117
297
  let checked = this.instanceTests.get(fn.name);
118
298
  if (!checked) {
119
299
  checked = new Set();
@@ -129,8 +309,10 @@ export class ClassDynamicDispatch {
129
309
  const loc = fn.loc;
130
310
  const value = varRef("value", DYN, loc);
131
311
  const type = { kind: "object", className };
132
- const result = subtype ? { kind: "boolLit", value: true, type: BOOL, loc }
133
- : { kind: "instanceOf", value: { kind: "dynCheck", value, type, loc }, className: target, type: BOOL, loc };
312
+ const result = subtype && fn.params[1] && lowerer.classes.get(target)?.localClass
313
+ ? { kind: "instanceOfValue", value: { kind: "dynCheck", value, type, loc }, classValue: varRef("class", fn.params[1].type, loc), type: BOOL, loc }
314
+ : subtype ? { kind: "boolLit", value: true, type: BOOL, loc }
315
+ : { kind: "instanceOf", value: { kind: "dynCheck", value, type, loc }, className: target, type: BOOL, loc };
134
316
  fn.body.unshift({ kind: "if", cond: { kind: "libCall", fn: "dyn.typedRefIs", args: [value,
135
317
  { kind: "strLit", value: typeKey(type), type: STRING, loc }], type: BOOL, loc }, then: [{ kind: "return", value: result, loc }], else_: null, loc });
136
318
  changed = true;
@@ -145,33 +327,146 @@ export class ClassDynamicDispatch {
145
327
  const info = lowerer.classes.get(className);
146
328
  if (!info || info.builtinEmitter || info.builtinStream || info.builtinError)
147
329
  continue;
330
+ if (info.symbolFields && info.def.symbolFields === undefined) {
331
+ info.def.symbolFields = [...info.symbolFields].flatMap(([symbol, field]) => {
332
+ const global = lowerer.globalsBySymbol.get(symbol);
333
+ return global?.type.kind === "symbol" ? [{ field, globalId: global.id }] : [];
334
+ });
335
+ }
336
+ if (this.prototypeHelper && !this.prototypeClasses.has(className)) {
337
+ this.prototypeClasses.add(className);
338
+ const loc = this.prototypeHelper.loc;
339
+ const type = { kind: "object", className };
340
+ const value = varRef("value", DYN, loc);
341
+ const prototype = reflectClassPrototype(lowerer, info, loc, { kind: "dynCheck", value, type, loc });
342
+ if (prototype)
343
+ this.prototypeHelper.body.unshift({ kind: "if", cond: { kind: "libCall", fn: "dyn.typedRefIs", args: [value,
344
+ { kind: "strLit", value: typeKey(type), type: STRING, loc }], type: BOOL, loc },
345
+ then: [{ kind: "return", value: prototype, loc }], else_: null, loc });
346
+ changed = true;
347
+ }
348
+ if (this.prototypeIdentityHelper && !this.prototypeIdentityClasses.has(className)) {
349
+ this.prototypeIdentityClasses.add(className);
350
+ const loc = this.prototypeIdentityHelper.loc;
351
+ const type = { kind: "object", className };
352
+ const value = varRef("value", DYN, loc);
353
+ const prototype = classPrototypeData(lowerer, info, loc, { kind: "dynCheck", value, type, loc });
354
+ if (prototype)
355
+ this.prototypeIdentityHelper.body.unshift({ kind: "if", cond: { kind: "libCall", fn: "dyn.typedRefIs", args: [value,
356
+ { kind: "strLit", value: typeKey(type), type: STRING, loc }], type: BOOL, loc },
357
+ then: [{ kind: "return", value: prototype, loc }], else_: null, loc });
358
+ changed = true;
359
+ }
360
+ if (this.serializesClasses && !this.jsonPrototypes.has(className) && info.decl && findMethodOn(lowerer, info, "toJSON")) {
361
+ this.jsonPrototypes.add(className);
362
+ const loc = this.propertyBag.loc;
363
+ const method = classMethodValue(lowerer, info.decl, info, "toJSON", loc);
364
+ const prototype = method ? classPrototypeData(lowerer, info, loc) : null;
365
+ const helper = lowerer.liftedFns.find((fn) => fn.name === info.def.prototypeDataHelper);
366
+ const init = helper?.body[0];
367
+ if (!info.prototypeReflectionReady && method && prototype && init?.kind === "if" && canConvertToDyn(method.type, (id) => lowerer.shapes.get(id), (id) => lowerer.unions.get(id))) {
368
+ const descriptor = { kind: "exprStmt", expr: {
369
+ kind: "libCall", fn: "dyn.defineProperty", args: [prototype,
370
+ { kind: "dynFrom", value: { kind: "strLit", value: "toJSON", type: STRING, loc }, type: DYN, loc },
371
+ { kind: "dynObjLit", fields: [
372
+ { key: { kind: "strLit", value: "value", type: STRING, loc }, value: { kind: "dynFrom", value: method, type: DYN, loc } },
373
+ ...["writable", "configurable"].map((key) => ({ key: { kind: "strLit", value: key, type: STRING, loc }, value: { kind: "dynFrom", value: { kind: "boolLit", value: true, type: BOOL, loc }, type: DYN, loc } })),
374
+ ], type: DYN, loc },
375
+ ], type: DYN, loc
376
+ }, loc };
377
+ init.then.push(descriptor);
378
+ changed = true;
379
+ }
380
+ }
381
+ }
382
+ for (const plan of this.propertyReceivers(lowerer, false, false)) {
383
+ const info = plan.info;
384
+ const className = info.def.name;
385
+ let instancePrototype = this.instancePrototypes.get(plan.capsule.def.name);
386
+ if (!instancePrototype && hasClassPrototypeData(info)) {
387
+ const name = `%class.instancePrototype:${plan.capsule.def.name}`;
388
+ const type = { kind: "object", className: plan.capsule.def.name };
389
+ const loc = this.propertyBag.loc;
390
+ const fn = { name, params: [{ localId: "this", name: "this", type }],
391
+ locals: [{ id: "this", name: "this", type, mutable: false }], returnType: DYN,
392
+ body: [{ kind: "return", value: { kind: "libCall", fn: "dyn.getPrototype",
393
+ args: [{ kind: "dynObjLit", fields: [], type: DYN, loc }], type: DYN, loc }, loc }], loc };
394
+ plan.capsule.def.instancePrototypeHelper = name;
395
+ instancePrototype = { fn, classes: new Set() };
396
+ this.instancePrototypes.set(plan.capsule.def.name, instancePrototype);
397
+ this.generated.add(fn);
398
+ lowerer.liftedFns.push(fn);
399
+ }
400
+ if (instancePrototype && hasClassPrototypeData(info) && !instancePrototype.classes.has(className)) {
401
+ instancePrototype.classes.add(className);
402
+ const loc = instancePrototype.fn.loc;
403
+ const receiver = varRef("this", instancePrototype.fn.params[0].type, loc);
404
+ const prototype = classPrototypeData(lowerer, info, loc, info === plan.capsule ? receiver
405
+ : { kind: "downcast", value: receiver, type: { kind: "object", className }, loc });
406
+ const returned = { kind: "return", value: prototype ?? { kind: "libCall", fn: "dyn.getPrototype",
407
+ args: [{ kind: "dynObjLit", fields: [], type: DYN, loc }], type: DYN, loc }, loc };
408
+ if (info === plan.capsule)
409
+ instancePrototype.fn.body[instancePrototype.fn.body.length - 1] = returned;
410
+ else
411
+ instancePrototype.fn.body.unshift({ kind: "if",
412
+ cond: { kind: "instanceOf", value: receiver, className, type: BOOL, loc }, then: [returned], else_: null, loc });
413
+ changed = true;
414
+ }
415
+ const needsPrototype = (current) => hasClassPrototypeData(current) || current.subclasses.some((child) => lowerer.classCanBeConstructed(child) && needsPrototype(child));
416
+ const prototypeFor = (current, receiver, loc) => {
417
+ const currentReceiver = receiver.type.kind === "object" && receiver.type.className !== current.def.name
418
+ ? { kind: "downcast", value: receiver, type: { kind: "object", className: current.def.name }, loc }
419
+ : receiver;
420
+ let result = classPrototypeData(lowerer, current, loc, currentReceiver);
421
+ for (const child of current.subclasses) {
422
+ if (!lowerer.classCanBeConstructed(child))
423
+ continue;
424
+ const selected = prototypeFor(child, receiver, loc);
425
+ if (selected && result)
426
+ result = { kind: "ternary", cond: { kind: "instanceOf", value: receiver, className: child.def.name, type: BOOL, loc },
427
+ then: selected, else_: result, type: DYN, loc };
428
+ }
429
+ return result;
430
+ };
148
431
  for (const existingInit of this.bagInitializers.get(className) ?? []) {
149
- if (existingInit.value.kind === "dynObjLit" && hasClassPrototypeData(info)) {
150
- const prototype = classPrototypeData(lowerer, info, existingInit.loc, existingInit.obj);
432
+ if (existingInit.value.kind === "dynObjLit" && needsPrototype(info)) {
433
+ const prototype = prototypeFor(info, existingInit.obj, existingInit.loc);
151
434
  if (prototype) {
152
435
  existingInit.value = { kind: "libCall", fn: "dyn.objCreate", args: [prototype], type: DYN, loc: existingInit.loc };
153
436
  changed = true;
154
437
  }
155
438
  }
156
439
  }
157
- if (this.bagClasses.has(className))
440
+ if (this.bagClasses.has(plan.key))
158
441
  continue;
159
- this.bagClasses.add(className);
442
+ this.bagClasses.add(plan.key);
160
443
  this.ensurePropertyBag(info);
444
+ if (info.decl && isJsSourceFile(info.decl.getSourceFile())) {
445
+ let root = info;
446
+ while (root.base && !root.base.def.runtime)
447
+ root = root.base;
448
+ const track = (current) => {
449
+ current.def.tracksOwnFields = true;
450
+ current.subclasses.forEach(track);
451
+ };
452
+ // Runtime-owned roots keep their dedicated property contracts.
453
+ if (!root.base?.def.runtime)
454
+ track(root);
455
+ }
161
456
  const loc = this.propertyBag.loc;
162
- const type = { kind: "object", className };
163
- const value = varRef("p.0", DYN, loc);
164
- const receiver = { kind: "dynCheck", value, type, loc };
457
+ const { receiver, condition } = this.propertyReceiver(plan, loc);
165
458
  const bag = { kind: "fieldGet", obj: receiver, className, field: PROPERTY_BAG, type: DYN, loc };
166
- const prototype = hasClassPrototypeData(info) ? classPrototypeData(lowerer, info, loc, receiver) : null;
459
+ const prototype = needsPrototype(info) ? prototypeFor(info, receiver, loc) : null;
167
460
  const initialize = {
168
461
  kind: "fieldSet", obj: receiver, className, field: PROPERTY_BAG, value: prototype
169
462
  ? { kind: "libCall", fn: "dyn.objCreate", args: [prototype], type: DYN, loc }
170
463
  : { kind: "dynObjLit", fields: [], type: DYN, loc }, loc,
171
464
  };
172
- this.bagInitializers.set(className, [initialize]);
465
+ const initializers = this.bagInitializers.get(className) ?? [];
466
+ initializers.push(initialize);
467
+ this.bagInitializers.set(className, initializers);
173
468
  this.propertyBag.body.unshift({
174
- kind: "if", cond: { kind: "libCall", fn: "dyn.typedRefIs", args: [value, { kind: "strLit", value: typeKey(type), type: STRING, loc }], type: BOOL, loc },
469
+ kind: "if", cond: condition,
175
470
  then: [
176
471
  { kind: "if", cond: { kind: "dynTest", test: "undefined", value: bag, type: BOOL, loc }, then: [
177
472
  initialize,
@@ -181,15 +476,19 @@ export class ClassDynamicDispatch {
181
476
  });
182
477
  changed = true;
183
478
  }
479
+ refreshDescriptorGuards(lowerer);
184
480
  const byMethod = new Map();
185
481
  const candidates = (method) => {
186
482
  const found = byMethod.get(method);
187
483
  if (found)
188
484
  return found;
189
- const matching = [...this.boxed].flatMap((name) => {
190
- const info = lowerer.classes.get(name);
191
- if (!info || info.fields.has(method) || info.builtinEmitter || info.builtinStream || info.builtinError)
485
+ const matching = this.propertyReceivers(lowerer, true).flatMap(({ info }) => {
486
+ if (!info || info.fields.has(method) || info.builtinError)
192
487
  return [];
488
+ if (emitterRooted(lowerer, info) && EMITTER_METHODS.has(method))
489
+ return [info];
490
+ if (info.builtinStream)
491
+ return STREAM_METHODS.has(method) ? [info] : [];
193
492
  return findMethodOn(lowerer, info, method) || findGenericMethodOn(lowerer, info, method) ? [info] : [];
194
493
  });
195
494
  byMethod.set(method, matching);
@@ -201,9 +500,82 @@ export class ClassDynamicDispatch {
201
500
  // every reachability pass. Recompute this set as new bodies appear.
202
501
  if (this.generated.has(fn) || !rewrite.has(fn))
203
502
  continue;
503
+ const keyInitializers = new Map();
504
+ const written = new Set();
505
+ everyStmtList(fn.body, { expr: (expression) => {
506
+ if (expression.kind === "incDec" || expression.kind === "assignExpr")
507
+ written.add(expression.localId);
508
+ return true;
509
+ }, stmt: (statement) => {
510
+ if (statement.kind === "varDecl" && statement.init)
511
+ keyInitializers.set(statement.localId, statement.init);
512
+ if (statement.kind === "assign")
513
+ written.add(statement.localId);
514
+ return true;
515
+ } });
516
+ const keyValue = (value) => {
517
+ const visited = new Set();
518
+ while (true) {
519
+ if (value.kind === "dynFrom") {
520
+ value = value.value;
521
+ continue;
522
+ }
523
+ if (value.kind === "libCall" && value.fn === "dyn.propertyKey") {
524
+ value = value.args[0];
525
+ continue;
526
+ }
527
+ if (value.kind === "varRef" && !written.has(value.localId) && !visited.has(value.localId) && keyInitializers.has(value.localId)) {
528
+ visited.add(value.localId);
529
+ value = keyInitializers.get(value.localId);
530
+ continue;
531
+ }
532
+ return value;
533
+ }
534
+ };
204
535
  fn.body = transformStmtList(fn.body, {
205
536
  stmt: (stmt) => stmt,
206
537
  expr: (expr) => {
538
+ if (expr.kind === "dynCheck" && isDynTypedRefType(expr.type)) {
539
+ const type = expr.type;
540
+ let cast = this.checkedCasts.get(type.className);
541
+ if (!cast) {
542
+ const loc = expr.loc;
543
+ const fn = { name: `%dyn.class.cast:${type.className}`,
544
+ params: [{ localId: "value", name: "value", type: DYN }],
545
+ locals: [{ id: "value", name: "value", type: DYN, mutable: false }], returnType: type,
546
+ body: [{ kind: "return", value: { ...expr, value: varRef("value", DYN, loc) }, loc }], loc };
547
+ cast = { fn, classes: new Set() };
548
+ this.checkedCasts.set(type.className, cast);
549
+ this.generated.add(fn);
550
+ lowerer.liftedFns.push(fn);
551
+ changed = true;
552
+ }
553
+ return { kind: "call", callee: cast.fn.name, args: [expr.value], type, loc: expr.loc };
554
+ }
555
+ if (expr.kind === "libCall" && expr.fn === "dyn.getPrototype" && expr.args[0]?.kind !== "dynObjLit") {
556
+ if (expr.prototypeIdentityOnly) {
557
+ if (!this.prototypeIdentityHelper) {
558
+ const loc = expr.loc;
559
+ this.prototypeIdentityHelper = { name: "%dyn.class.prototypeIdentity", params: [{ localId: "value", name: "value", type: DYN }],
560
+ locals: [{ id: "value", name: "value", type: DYN, mutable: false }], returnType: DYN, loc,
561
+ body: [{ kind: "return", value: { ...expr, args: [varRef("value", DYN, loc)] }, loc }] };
562
+ lowerer.liftedFns.push(this.prototypeIdentityHelper);
563
+ this.generated.add(this.prototypeIdentityHelper);
564
+ changed = true;
565
+ }
566
+ return { kind: "call", callee: this.prototypeIdentityHelper.name, args: expr.args, type: DYN, loc: expr.loc };
567
+ }
568
+ if (!this.prototypeHelper) {
569
+ const loc = expr.loc;
570
+ this.prototypeHelper = { name: "%dyn.class.getPrototype", params: [{ localId: "value", name: "value", type: DYN }],
571
+ locals: [{ id: "value", name: "value", type: DYN, mutable: false }], returnType: DYN, loc,
572
+ body: [{ kind: "return", value: { ...expr, args: [varRef("value", DYN, loc)] }, loc }] };
573
+ lowerer.liftedFns.push(this.prototypeHelper);
574
+ this.generated.add(this.prototypeHelper);
575
+ changed = true;
576
+ }
577
+ return { kind: "call", callee: this.prototypeHelper.name, args: expr.args, type: DYN, loc: expr.loc };
578
+ }
207
579
  if (expr.kind === "libCall" && (expr.fn === "dyn.toString" || expr.fn === "dyn.stringConstructor") && lowerer.classes.has("%Error")) {
208
580
  const key = JSON.stringify([expr.fn, ...expr.args.map((arg) => typeKey(arg.type))]);
209
581
  let errorString = this.errorStrings.get(key);
@@ -239,13 +611,74 @@ export class ClassDynamicDispatch {
239
611
  }
240
612
  }
241
613
  }
242
- const computedRead = expr.kind === "dynKeyGet" && expr.key.kind !== "strLit" ? expr : null;
243
- const computedWrite = expr.kind === "libCall" && (expr.fn === "dyn.keySetComputed" || expr.fn === "dyn.keySet" && expr.args[1]?.kind !== "strLit") ? expr : null;
614
+ if (expr.kind === "libCall" && expr.fn === "bytes.construct") {
615
+ if (!this.constructDispatch) {
616
+ const loc = expr.loc;
617
+ const params = [DYN, DYN, STRING].map((type, i) => ({ localId: `p.${i}`, name: `p${i}`, type }));
618
+ this.constructDispatch = { name: "%dyn.class.construct", params, returnType: DYN,
619
+ locals: params.map((p) => ({ id: p.localId, name: p.name, type: p.type, mutable: false })),
620
+ body: [{ kind: "return", value: { ...expr, args: params.map((p) => varRef(p.localId, p.type, loc)) }, loc }], loc };
621
+ this.generated.add(this.constructDispatch);
622
+ lowerer.liftedFns.push(this.constructDispatch);
623
+ changed = true;
624
+ }
625
+ return { kind: "call", callee: this.constructDispatch.name, args: expr.args, type: DYN, loc: expr.loc };
626
+ }
627
+ if (expr.kind === "libCall" && expr.fn === "dyn.forInKeys") {
628
+ if (!this.enumeration) {
629
+ const loc = expr.loc;
630
+ const value = varRef("p.0", DYN, loc);
631
+ this.enumeration = { name: "%dyn.class.forInKeys", params: [{ localId: "p.0", name: "value", type: DYN }], returnType: DYN,
632
+ locals: [{ id: "p.0", name: "value", type: DYN, mutable: false }],
633
+ body: [{ kind: "return", value: { ...expr, args: [value] }, loc }], loc };
634
+ this.generated.add(this.enumeration);
635
+ lowerer.liftedFns.push(this.enumeration);
636
+ changed = true;
637
+ }
638
+ return { kind: "call", callee: this.enumeration.name, args: expr.args, type: DYN, loc: expr.loc };
639
+ }
640
+ if (expr.kind === "libCall" && expr.fn === "dyn.defineProperty") {
641
+ const loc = expr.loc;
642
+ if (!this.descriptorHelper) {
643
+ const params = ["target", "key", "descriptor"].map((name) => ({ localId: name, name, type: DYN }));
644
+ const target = varRef("target", DYN, loc);
645
+ const key = varRef("key", DYN, loc);
646
+ const descriptor = varRef("descriptor", DYN, loc);
647
+ this.descriptorHelper = {
648
+ name: "%dyn.class.defineProperty", params, returnType: DYN,
649
+ locals: params.map((param) => ({ id: param.localId, name: param.name, type: DYN, mutable: false })),
650
+ body: [
651
+ { kind: "if", cond: { kind: "dynTest", test: "symbol", value: key, type: BOOL, loc }, then: [
652
+ { kind: "exprStmt", expr: { ...expr, args: [{ kind: "call", callee: this.propertyBag.name, args: [target], type: DYN, loc }, key, descriptor] }, loc },
653
+ { kind: "return", value: target, loc },
654
+ ], else_: null, loc },
655
+ { kind: "return", value: { ...expr, args: [target, key, descriptor] }, loc },
656
+ ], loc,
657
+ };
658
+ this.generated.add(this.descriptorHelper);
659
+ lowerer.liftedFns.push(this.descriptorHelper);
660
+ changed = true;
661
+ }
662
+ return { kind: "call", callee: this.descriptorHelper.name, args: expr.args, type: DYN, loc };
663
+ }
664
+ if (expr.kind === "dynKeyGet" && expr.value.kind === "dynObjLit")
665
+ return expr;
666
+ const reflectRead = expr.kind === "libCall" && expr.fn === "dyn.reflectGet" ? expr : null;
667
+ const reflectWrite = expr.kind === "libCall" && expr.fn === "dyn.reflectSet" ? expr : null;
668
+ const reflect = !!reflectRead || !!reflectWrite;
669
+ const computedRead = reflectRead ? { kind: "dynKeyGet", value: reflectRead.args[0], key: reflectRead.args[1], type: DYN, loc: expr.loc }
670
+ : expr.kind === "dynKeyGet" && expr.key.kind !== "strLit" ? expr : null;
671
+ const computedWrite = reflectWrite ? { ...reflectWrite, fn: "dyn.keySetComputed", args: reflectWrite.args.slice(0, 3), type: VOID }
672
+ : expr.kind === "libCall" && (expr.fn === "dyn.keySetComputed" || expr.fn === "dyn.keySet" && expr.args[1]?.kind !== "strLit") ? expr : null;
244
673
  const computedProbe = expr.kind === "libCall" && ["dyn.hasKeyComputed", "dyn.hasOwnComputed", "dyn.propertyIsEnumerableComputed"].includes(expr.fn) ? expr : null;
245
674
  if (computedRead || computedWrite || computedProbe) {
246
675
  const probe = computedProbe ? computedProbe.fn === "dyn.hasKeyComputed" ? "in" : computedProbe.fn === "dyn.hasOwnComputed" ? "own" : "enumerable" : undefined;
247
- const dynamicKey = !!computedProbe || computedWrite?.fn === "dyn.keySetComputed" || computedRead?.key.type.kind === "dyn";
248
- const key = JSON.stringify([!!computedWrite, computedRead?.optional ?? false, dynamicKey, probe]);
676
+ const dynamicKey = reflect || !!computedProbe || computedWrite?.fn === "dyn.keySetComputed" || computedRead?.key.type.kind === "dyn";
677
+ const sourceKey = keyValue(computedRead?.key ?? (computedWrite ?? computedProbe).args[1]);
678
+ const numericKey = sourceKey.type.kind === "f64" ||
679
+ sourceKey.kind === "dynFrom" && sourceKey.value.type.kind === "f64" ||
680
+ sourceKey.kind === "toString" && sourceKey.operand.type.kind === "f64";
681
+ const key = JSON.stringify([!!computedWrite, computedRead?.optional ?? false, dynamicKey, probe, numericKey, reflect]);
249
682
  let dispatch = this.computed.get(key);
250
683
  if (!dispatch) {
251
684
  const loc = expr.loc;
@@ -253,23 +686,28 @@ export class ClassDynamicDispatch {
253
686
  { localId: "p.0", name: "value", type: DYN },
254
687
  { localId: "p.key", name: "key", type: dynamicKey ? DYN : STRING },
255
688
  ...(computedWrite ? [{ localId: "p.1", name: "stored", type: DYN }] : []),
689
+ ...(reflect ? [{ localId: "p.reflectReceiver", name: "receiver", type: DYN }] : []),
256
690
  ];
257
691
  const keyLocal = dynamicKey ? "key.string" : "p.key";
258
692
  const bag = { kind: "call", callee: this.propertyBag.name, args: [varRef("p.0", DYN, loc)], type: DYN, loc };
259
- const fallback = computedProbe
693
+ const reflectionFallback = (target, property) => ({ kind: "libCall", fn: computedWrite ? "dyn.reflectSet" : "dyn.reflectGet",
694
+ args: [target, lowerer.coerceToExpected(property, DYN), ...(computedWrite ? [varRef("p.1", DYN, loc)] : []), varRef("p.reflectReceiver", DYN, loc)], type: computedWrite ? BOOL : DYN, loc });
695
+ const fallback = reflect ? reflectionFallback(bag, varRef(keyLocal, STRING, loc)) : computedProbe
260
696
  ? { ...computedProbe, fn: probe === "in" ? "dyn.hasKey" : probe === "own" ? "dyn.hasOwn" : "dyn.propertyIsEnumerable", args: [bag, varRef(keyLocal, STRING, loc)] }
261
697
  : computedRead
262
698
  ? { ...computedRead, value: bag, key: varRef(keyLocal, STRING, loc) }
263
699
  : { ...computedWrite, fn: "dyn.keySet", args: [bag, varRef(keyLocal, STRING, loc), varRef("p.1", DYN, loc)] };
264
700
  const helper = {
265
- name: `%dyn.class.computed.${this.computed.size}`, params, returnType: computedProbe ? BOOL : computedWrite ? VOID : DYN,
701
+ name: `%dyn.class.computed.${this.computed.size}`, params, returnType: reflect && computedWrite || computedProbe ? BOOL : computedWrite ? VOID : DYN,
266
702
  locals: [...params.map((p) => ({ id: p.localId, name: p.name, type: p.type, mutable: false })), ...(dynamicKey ? [{ id: keyLocal, name: "key", type: STRING, mutable: false }] : [])],
267
- body: computedWrite
703
+ body: reflect ? [{ kind: "return", value: fallback, loc }] : computedWrite
268
704
  ? [{ kind: "exprStmt", expr: fallback, loc }, { kind: "return", value: null, loc }]
269
705
  : [{ kind: "return", value: fallback, loc }], loc,
270
706
  };
271
707
  if (dynamicKey)
272
- helper.body.unshift({ kind: "if", cond: { kind: "dynTest", test: "symbol", value: varRef("p.key", DYN, loc), type: BOOL, loc }, then: computedProbe ? [
708
+ helper.body.unshift({ kind: "if", cond: { kind: "dynTest", test: "symbol", value: varRef("p.key", DYN, loc), type: BOOL, loc }, then: reflect ? [
709
+ { kind: "return", value: reflectionFallback(bag, varRef("p.key", DYN, loc)), loc },
710
+ ] : computedProbe ? [
273
711
  { kind: "return", value: { ...computedProbe, args: [bag, varRef("p.key", DYN, loc)] }, loc },
274
712
  ] : computedRead ? [
275
713
  { kind: "return", value: { ...computedRead, value: bag, key: varRef("p.key", DYN, loc) }, loc },
@@ -277,18 +715,18 @@ export class ClassDynamicDispatch {
277
715
  { kind: "exprStmt", expr: { ...computedWrite, args: [bag, varRef("p.key", DYN, loc), varRef("p.1", DYN, loc)] }, loc },
278
716
  { kind: "return", value: null, loc },
279
717
  ], else_: null, loc }, { kind: "if", cond: { kind: "dynTest", test: "nullish", value: varRef("p.0", DYN, loc), type: BOOL, loc }, then: [
280
- ...(computedProbe ? [{ kind: "return", value: { ...computedProbe, args: [varRef("p.0", DYN, loc), varRef("p.key", DYN, loc)] }, loc }] : computedRead ? [{ kind: "return", value: { ...computedRead, value: varRef("p.0", DYN, loc), key: varRef("p.key", DYN, loc) }, loc }] : [
718
+ ...(reflect ? [{ kind: "return", value: reflectionFallback(varRef("p.0", DYN, loc), varRef("p.key", DYN, loc)), loc }] : computedProbe ? [{ kind: "return", value: { ...computedProbe, args: [varRef("p.0", DYN, loc), varRef("p.key", DYN, loc)] }, loc }] : computedRead ? [{ kind: "return", value: { ...computedRead, value: varRef("p.0", DYN, loc), key: varRef("p.key", DYN, loc) }, loc }] : [
281
719
  { kind: "exprStmt", expr: { ...computedWrite, args: [varRef("p.0", DYN, loc), varRef("p.key", DYN, loc), varRef("p.1", DYN, loc)] }, loc },
282
720
  { kind: "return", value: null, loc },
283
721
  ]),
284
722
  ], else_: null, loc }, { kind: "varDecl", localId: keyLocal, init: { kind: "libCall", fn: "dyn.toStringCoerce", args: [varRef("p.key", DYN, loc)], type: STRING, loc }, loc });
285
- dispatch = { write: !!computedWrite, fn: helper, classes: new Set(), keyLocal, branchIndex: dynamicKey ? 3 : 0, dynamicKey, probe };
723
+ dispatch = { write: !!computedWrite, reflect, fn: helper, classes: new Set(), keyLocal, branchIndex: dynamicKey ? 3 : 0, dynamicKey, numericKey, probe };
286
724
  this.computed.set(key, dispatch);
287
725
  this.generated.add(helper);
288
726
  lowerer.liftedFns.push(helper);
289
727
  changed = true;
290
728
  }
291
- return { kind: "call", callee: dispatch.fn.name, args: computedRead ? [computedRead.value, computedRead.key] : (computedWrite ?? computedProbe).args, type: dispatch.fn.returnType, loc: expr.loc };
729
+ return { kind: "call", callee: dispatch.fn.name, args: reflect ? (reflectRead ?? reflectWrite).args : computedRead ? [computedRead.value, computedRead.key] : (computedWrite ?? computedProbe).args, type: dispatch.fn.returnType, loc: expr.loc };
292
730
  }
293
731
  const read = expr.kind === "dynKeyGet" && expr.key.kind === "strLit" ? expr : null;
294
732
  const write = expr.kind === "libCall" && expr.fn === "dyn.keySet" && expr.args[1]?.kind === "strLit" ? expr : null;
@@ -299,9 +737,14 @@ export class ClassDynamicDispatch {
299
737
  if (!dispatch) {
300
738
  const loc = expr.loc;
301
739
  const params = (write ? [0, 1] : [0]).map((i) => ({ localId: `p.${i}`, name: `p${i}`, type: DYN }));
302
- const bag = { kind: "call", callee: this.propertyBag.name, args: [varRef("p.0", DYN, loc)], type: DYN, loc };
740
+ const instanceBag = { kind: "call", callee: this.propertyBag.name, args: [varRef("p.0", DYN, loc)], type: DYN, loc };
741
+ const bag = read && !["name", "length", "apply"].includes(name)
742
+ ? { kind: "call", callee: this.constructorPropertyBag(lowerer, loc).name, args: [instanceBag], type: DYN, loc }
743
+ : instanceBag;
303
744
  const fallback = read
304
- ? { ...read, value: bag }
745
+ ? { kind: "ternary", cond: { kind: "dynScalarEq", left: bag, right: varRef("p.0", DYN, loc), type: BOOL, loc },
746
+ then: { ...read, value: bag }, else_: { kind: "libCall", fn: "dyn.bagGet",
747
+ args: [bag, read.key, varRef("p.0", DYN, loc)], type: DYN, loc }, type: DYN, loc }
305
748
  : { ...write, args: [bag, write.args[1], varRef("p.1", DYN, loc)] };
306
749
  const helper = {
307
750
  name: `%dyn.class.property.${this.properties.size}`, params, returnType: write ? VOID : DYN,
@@ -328,13 +771,19 @@ export class ClassDynamicDispatch {
328
771
  const invoke = iterator
329
772
  ? { kind: "dynInvoke", recv: iterator.args[0], method: "sym:iterator", calleeName: "value[Symbol.iterator]", args: [], type: DYN, loc: iterator.loc }
330
773
  : expr.kind === "dynInvoke" ? expr : null;
331
- if (!invoke || candidates(invoke.method).length === 0 && !(invoke.method === "toString" && lowerer.classes.has("%Error")))
774
+ const staticCandidates = invoke && [...this.boxedConstructors].some((name) => {
775
+ const info = lowerer.classes.get(name);
776
+ return info && findStaticOn(lowerer, info, invoke.method)?.method;
777
+ });
778
+ if (!invoke || candidates(invoke.method).length === 0 && !staticCandidates && !(invoke.method === "toString" && lowerer.classes.has("%Error")))
332
779
  return expr;
333
- const key = JSON.stringify([invoke.method, invoke.calleeName, invoke.args.length, iterator?.fn, iterator?.args[1]]);
780
+ const key = JSON.stringify([invoke.method, invoke.args.length, iterator?.fn, iterator?.args[1]]);
334
781
  let dispatch = this.dispatches.get(key);
335
782
  if (!dispatch) {
336
783
  const params = [invoke.recv, ...invoke.args].map((_, i) => ({ localId: `p.${i}`, name: `p${i}`, type: DYN }));
337
784
  params.splice(1, 0, { localId: "p.callback", name: "callback", type: DYN });
785
+ iterator?.args.slice(1).forEach((argument, i) => params.push({ localId: `p.iterator.${i}`, name: `iterator${i}`, type: argument.type }));
786
+ params.push({ localId: "p.calleeName", name: "calleeName", type: STRING });
338
787
  const callback = {
339
788
  name: `%dyn.class.callback.${this.dispatches.size}`, params: [params[0]], returnType: DYN,
340
789
  locals: [{ id: "p.0", name: "receiver", type: DYN, mutable: false }],
@@ -342,10 +791,10 @@ export class ClassDynamicDispatch {
342
791
  };
343
792
  const helper = {
344
793
  name: `%dyn.class.call.${this.dispatches.size}`, params, returnType: DYN,
345
- locals: params.map((p) => ({ id: p.localId, name: p.name, type: DYN, mutable: false })),
794
+ locals: params.map((p) => ({ id: p.localId, name: p.name, type: p.type, mutable: false })),
346
795
  body: [{ kind: "return", value: iterator
347
- ? { ...iterator, args: [varRef("p.0", DYN, expr.loc), ...iterator.args.slice(1)] }
348
- : { ...invoke, recv: varRef("p.0", DYN, expr.loc), args: invoke.args.map((_, i) => varRef(`p.${i + 1}`, DYN, expr.loc)) }, loc: expr.loc }],
796
+ ? { ...iterator, args: [varRef("p.0", DYN, expr.loc), ...iterator.args.slice(1).map((argument, i) => varRef(`p.iterator.${i}`, argument.type, expr.loc))] }
797
+ : { ...invoke, calleeNameValue: varRef("p.calleeName", STRING, expr.loc), recv: varRef("p.0", DYN, expr.loc), args: invoke.args.map((_, i) => varRef(`p.${i + 1}`, DYN, expr.loc)) }, loc: expr.loc }],
349
798
  loc: expr.loc,
350
799
  };
351
800
  if (invoke.method === "toString" && lowerer.classes.has("%Error")) {
@@ -354,7 +803,7 @@ export class ClassDynamicDispatch {
354
803
  helper.body.unshift({ kind: "if", cond: { kind: "dynTest", test: "error", value, type: BOOL, loc }, then: [
355
804
  { kind: "return", value: {
356
805
  kind: "dynCall", callee: errorToStringMethod(lowerer, { kind: "dynCheck", value, type: { kind: "object", className: "%Error" }, loc }),
357
- receiver: value, calleeName: invoke.calleeName, args: invoke.args.map((_, i) => varRef(`p.${i + 1}`, DYN, loc)), type: DYN, loc,
806
+ receiver: value, calleeName: invoke.calleeName, calleeNameValue: varRef("p.calleeName", STRING, loc), args: invoke.args.map((_, i) => varRef(`p.${i + 1}`, DYN, loc)), type: DYN, loc,
358
807
  }, loc },
359
808
  ], else_: null, loc });
360
809
  }
@@ -371,30 +820,75 @@ export class ClassDynamicDispatch {
371
820
  const receiver = varRef(local.id, DYN, expr.loc);
372
821
  return { kind: "seqExpr", stmts: [{ kind: "varDecl", localId: local.id, init: invoke.recv, loc: expr.loc }], result: {
373
822
  kind: "call", callee: dispatch.fn.name, args: [receiver,
374
- { kind: "call", callee: dispatch.callback.name, args: [receiver], type: DYN, loc: expr.loc }, ...invoke.args], type: DYN, loc: expr.loc,
823
+ { kind: "call", callee: dispatch.callback.name, args: [receiver], type: DYN, loc: expr.loc }, ...invoke.args, ...(iterator?.args.slice(1) ?? []), invoke.calleeNameValue ?? { kind: "strLit", value: invoke.calleeName, type: STRING, loc: expr.loc }], type: DYN, loc: expr.loc,
375
824
  }, type: DYN, loc: expr.loc };
376
825
  },
377
826
  });
378
827
  }
828
+ for (const [target, cast] of this.checkedCasts) {
829
+ for (const source of this.boxed) {
830
+ if (source === target || !lowerer.isSubclassOf(target, source) || cast.classes.has(source))
831
+ continue;
832
+ cast.classes.add(source);
833
+ const loc = cast.fn.loc;
834
+ const value = varRef("value", DYN, loc);
835
+ const sourceType = { kind: "object", className: source };
836
+ const object = { kind: "dynCheck", value, type: sourceType, loc };
837
+ cast.fn.body.unshift({ kind: "if", cond: { kind: "libCall", fn: "dyn.typedRefIs", args: [value,
838
+ { kind: "strLit", value: typeKey(sourceType), type: STRING, loc }], type: BOOL, loc }, then: [
839
+ { kind: "if", cond: { kind: "instanceOf", value: object, className: target, type: BOOL, loc }, then: [
840
+ { kind: "return", value: { kind: "downcast", value: object, type: cast.fn.returnType, loc }, loc },
841
+ ], else_: null, loc },
842
+ ], else_: null, loc });
843
+ changed = true;
844
+ }
845
+ }
379
846
  for (const dispatch of this.dispatches.values()) {
380
- for (const info of candidates(dispatch.source.method)) {
381
- if (dispatch.classes.has(info.def.name))
847
+ for (const name of this.boxedConstructors) {
848
+ const info = lowerer.classes.get(name);
849
+ const found = info && findStaticOn(lowerer, info, dispatch.source.method);
850
+ const key = `constructor:${name}`;
851
+ if (!found?.method || dispatch.classes.has(key))
852
+ continue;
853
+ const method = reflectedClassStaticMethodValue(lowerer, found.declarer, dispatch.source.method);
854
+ if (!method)
382
855
  continue;
383
- dispatch.classes.add(info.def.name);
856
+ dispatch.classes.add(key);
384
857
  const loc = dispatch.source.loc;
385
- const type = { kind: "object", className: info.def.name };
386
858
  const receiver = varRef("p.0", DYN, loc);
859
+ const condition = { kind: "libCall", fn: "dyn.classIs", args: [receiver,
860
+ { kind: "strLit", value: typeKey({ kind: "classval", className: name }), type: STRING, loc }], type: BOOL, loc };
861
+ dispatch.callback.body.unshift({ kind: "if", cond: condition, then: [{ kind: "return", value: {
862
+ kind: "dynObjLit", fields: [{ key: { kind: "strLit", value: "value", type: STRING, loc }, value: lowerer.coerceToExpected(method, DYN) }], type: DYN, loc,
863
+ }, loc }], else_: null, loc });
864
+ dispatch.fn.body.unshift({ kind: "if", cond: condition, then: [{ kind: "return", value: {
865
+ kind: "dynCall", callee: { kind: "dynKeyGet", value: varRef("p.callback", DYN, loc), key: { kind: "strLit", value: "value", type: STRING, loc }, type: DYN, loc },
866
+ receiver, calleeName: dispatch.source.calleeName, calleeNameValue: varRef("p.calleeName", STRING, loc), args: dispatch.source.args.map((_, i) => varRef(`p.${i + 1}`, DYN, loc)), type: DYN, loc,
867
+ }, loc }], else_: null, loc });
868
+ changed = true;
869
+ }
870
+ for (const plan of this.propertyReceivers(lowerer, true)) {
871
+ const { info } = plan;
872
+ if (!candidates(dispatch.source.method).includes(info) || dispatch.classes.has(plan.key))
873
+ continue;
874
+ dispatch.classes.add(plan.key);
875
+ const loc = dispatch.source.loc;
876
+ const value = varRef("p.0", DYN, loc);
877
+ const { receiver, condition } = this.propertyReceiver(plan, loc);
387
878
  if (isClassCallback(lowerer, info, dispatch.source.method)) {
388
- const bag = { kind: "call", callee: this.propertyBag.name, args: [receiver], type: DYN, loc };
389
- dispatch.callback.body.unshift({ kind: "if", cond: {
390
- kind: "libCall", fn: "dyn.typedRefIs", args: [receiver, { kind: "strLit", value: typeKey(type), type: STRING, loc }], type: BOOL, loc,
391
- }, then: [{ kind: "return", value: { kind: "libCall", fn: "dyn.getOwnPropertyDescriptor", args: [bag,
392
- lowerer.coerceToExpected({ kind: "strLit", value: dispatch.source.method, type: STRING, loc }, DYN)], type: DYN, loc }, loc }], else_: null, loc });
879
+ const prototypeMethod = lowerer.prototypeMethodAccesses.has(dispatch.source.method);
880
+ if (prototypeMethod)
881
+ classPrototypeData(lowerer, info, loc);
882
+ const bag = { kind: "call", callee: this.propertyBag.name, args: [value], type: DYN, loc };
883
+ dispatch.callback.body.unshift({ kind: "if", cond: condition, then: [{ kind: "return", value: { kind: "ternary", cond: prototypeMethod ? { kind: "boolLit", value: true, type: BOOL, loc } : { kind: "libCall", fn: "dyn.hasKey", args: [bag,
884
+ { kind: "strLit", value: dispatch.source.method, type: STRING, loc }], type: BOOL, loc }, then: { kind: "dynObjLit", fields: [
885
+ { key: { kind: "strLit", value: "value", type: STRING, loc }, value: { kind: "dynKeyGet", value: bag, key: { kind: "strLit", value: dispatch.source.method, type: STRING, loc }, type: DYN, loc } },
886
+ ], type: DYN, loc }, else_: dynUndefinedExpr(loc), type: DYN, loc }, loc }], else_: null, loc });
393
887
  }
394
888
  const before = lowerer.diags.length;
395
889
  let branch;
396
890
  try {
397
- branch = this.methodBody(lowerer, dispatch, info, { kind: "dynCheck", value: receiver, type, loc });
891
+ branch = this.methodBody(lowerer, dispatch, info, receiver);
398
892
  }
399
893
  catch (error) {
400
894
  if (!(error instanceof PoisonError) || !info.decl)
@@ -405,19 +899,42 @@ export class ClassDynamicDispatch {
405
899
  branch = [fence];
406
900
  }
407
901
  dispatch.fn.body.unshift({
408
- kind: "if", cond: {
409
- kind: "libCall", fn: "dyn.typedRefIs", args: [receiver, { kind: "strLit", value: typeKey(type), type: STRING, loc }], type: BOOL, loc,
410
- }, then: branch, else_: null, loc,
902
+ kind: "if", cond: condition, then: branch, else_: null, loc,
411
903
  });
412
904
  changed = true;
413
905
  }
414
906
  }
415
907
  for (const dispatch of this.properties.values()) {
416
- for (const plan of this.propertyReceivers(lowerer)) {
908
+ if (!dispatch.write)
909
+ for (const name of this.boxedConstructors) {
910
+ const info = lowerer.classes.get(name);
911
+ const found = info && findStaticOn(lowerer, info, dispatch.name);
912
+ const field = found?.field;
913
+ const key = `constructor:${name}`;
914
+ if (!info || dispatch.name.startsWith("#") || dispatch.classes.has(key) || !found && dispatch.name !== "prototype")
915
+ continue;
916
+ const method = found?.method && reflectedClassStaticMethodValue(lowerer, found.declarer, dispatch.name);
917
+ if (found && !field && !method || findGenericStaticOn(lowerer, info, dispatch.name) && !method)
918
+ continue;
919
+ const loc = dispatch.fn.loc;
920
+ const result = field ? varRef(field.globalId, field.type, loc) : method ??
921
+ (dispatch.name === "prototype" ? classPrototypeData(lowerer, info, loc) : null) ??
922
+ dynUndefinedExpr(loc);
923
+ if (dispatch.name === "name" || dispatch.name === "length" || dispatch.name === "apply")
924
+ continue;
925
+ dispatch.classes.add(key);
926
+ const value = varRef("p.0", DYN, loc);
927
+ const type = { kind: "classval", className: name };
928
+ dispatch.fn.body.unshift({ kind: "if", cond: { kind: "libCall", fn: "dyn.classIs", args: [value,
929
+ { kind: "strLit", value: typeKey(type), type: STRING, loc }], type: BOOL, loc }, then: [
930
+ { kind: "return", value: lowerer.coerceToExpected(result, DYN), loc },
931
+ ], else_: null, loc });
932
+ changed = true;
933
+ }
934
+ for (const plan of this.propertyReceivers(lowerer, true)) {
417
935
  const { info } = plan;
418
936
  if (!isClassOwnEnumerableFieldName(dispatch.name) ||
419
- !info.fields.has(dispatch.name) && !findMethodOn(lowerer, info, `get:${dispatch.name}`) && !findMethodOn(lowerer, info, `set:${dispatch.name}`) &&
420
- !findMethodOn(lowerer, info, dispatch.name) && !findGenericMethodOn(lowerer, info, dispatch.name))
937
+ !(info.builtinStream !== undefined && streamProperty(dispatch.name)) && !findMethodOn(lowerer, info, dispatch.name) && !findGenericMethodOn(lowerer, info, dispatch.name) && dispatch.name !== "constructor" && !info.fields.has(dispatch.name) && !findMethodOn(lowerer, info, `get:${dispatch.name}`) && !findMethodOn(lowerer, info, `set:${dispatch.name}`))
421
938
  continue;
422
939
  if (dispatch.classes.has(plan.key))
423
940
  continue;
@@ -444,16 +961,60 @@ export class ClassDynamicDispatch {
444
961
  }
445
962
  }
446
963
  for (const dispatch of this.computed.values()) {
447
- for (const plan of this.propertyReceivers(lowerer)) {
964
+ for (const plan of this.propertyReceivers(lowerer, true)) {
448
965
  if (dispatch.classes.has(plan.key))
449
966
  continue;
450
967
  const { info } = plan;
451
968
  dispatch.classes.add(plan.key);
452
969
  const loc = dispatch.fn.loc;
453
- const { receiver, condition } = this.propertyReceiver(plan, loc);
454
- const memberBody = (name) => dispatch.probe
455
- ? [{ kind: "return", value: { kind: "boolLit", value: info.fields.has(name) || dispatch.probe === "in", type: BOOL, loc }, loc }]
456
- : this.propertyBody(lowerer, { ...dispatch, name }, info, receiver);
970
+ const selected = this.propertyReceiver(plan, loc);
971
+ const receiverType = { kind: "object", className: info.def.name };
972
+ const receiver = varRef("p.receiver", receiverType, loc);
973
+ const helperKey = JSON.stringify([dispatch.fn.name, info.def.name]);
974
+ const existingHelper = this.computedClassHelpers.get(helperKey);
975
+ const callHelper = (helper) => {
976
+ const call = { kind: "call", callee: helper.name,
977
+ args: helper.params.map((param) => param.localId === "p.receiver" ? selected.receiver : varRef(param.localId, param.type, loc)), type: helper.returnType, loc };
978
+ return { kind: "if", cond: selected.condition, then: dispatch.write && !dispatch.reflect
979
+ ? [{ kind: "exprStmt", expr: call, loc }, { kind: "return", value: null, loc }]
980
+ : [{ kind: "return", value: call, loc }], else_: null, loc };
981
+ };
982
+ if (existingHelper && !dispatch.dynamicKey) {
983
+ dispatch.fn.body.splice(dispatch.branchIndex, 0, callHelper(existingHelper));
984
+ changed = true;
985
+ continue;
986
+ }
987
+ const memberBody = (name) => {
988
+ if (dispatch.probe && (name === "message" || name === "name") && lowerer.isSubclassOf(info.def.name, "%Error") && dispatch.probe !== "in")
989
+ return [{
990
+ kind: "return", value: dispatch.probe === "own"
991
+ ? { kind: "fieldGet", obj: lowerer.upcastTo(receiver, "%Error"), className: "%Error", field: `%${name}Present`, type: BOOL, loc }
992
+ : { kind: "fieldGet", obj: lowerer.upcastTo(receiver, "%Error"), className: "%Error", field: `%${name}Enumerable`, type: BOOL, loc }, loc,
993
+ }];
994
+ if (info.builtinStream !== undefined && streamProperty(name) && dispatch.probe)
995
+ return [{ kind: "return", value: { kind: "boolLit", value: dispatch.probe === "in", type: BOOL, loc }, loc }];
996
+ if (!dispatch.probe)
997
+ return dispatch.reflect ? this.reflectPropertyBody(lowerer, { ...dispatch, name }, info, receiver) : this.propertyBody(lowerer, { ...dispatch, name }, info, receiver);
998
+ const prototypeMethod = lowerer.prototypeMethodAccesses.has(name);
999
+ if (info.fields.has(name) || dispatch.probe === "in" && !prototypeMethod) {
1000
+ if (info.fields.has(name) && info.def.tracksOwnFields) {
1001
+ const bag = { kind: "call", callee: this.propertyBag.name, args: [lowerer.coerceToExpected(receiver, DYN)], type: DYN, loc };
1002
+ const symbol = symbolMemberKey(lowerer, info, name, loc);
1003
+ const fn = dispatch.probe === "in" ? "dyn.hasKey" : dispatch.probe === "own" ? "dyn.hasOwn" : "dyn.propertyIsEnumerable";
1004
+ return [{ kind: "return", value: { kind: "libCall", fn: symbol ? `${fn}Computed` : fn,
1005
+ args: [bag, symbol ?? { kind: "strLit", value: name, type: STRING, loc }], type: BOOL, loc }, loc }];
1006
+ }
1007
+ return [{ kind: "return", value: { kind: "boolLit", value: true, type: BOOL, loc }, loc }];
1008
+ }
1009
+ if (prototypeMethod)
1010
+ classPrototypeData(lowerer, info, loc, receiver);
1011
+ const bag = { kind: "call", callee: this.propertyBag.name,
1012
+ args: [lowerer.coerceToExpected(receiver, DYN)], type: DYN, loc };
1013
+ const symbol = name.startsWith("sym:") || name.startsWith("%symbol:");
1014
+ const fn = dispatch.probe === "in" ? "dyn.hasKey" : dispatch.probe === "own" ? "dyn.hasOwn" : "dyn.propertyIsEnumerable";
1015
+ return [{ kind: "return", value: { kind: "libCall", fn: symbol ? `${fn}Computed` : fn,
1016
+ args: [bag, symbol ? varRef("p.key", DYN, loc) : { kind: "strLit", value: name, type: STRING, loc }], type: BOOL, loc }, loc }];
1017
+ };
457
1018
  if (dispatch.dynamicKey) {
458
1019
  const symbols = new Map();
459
1020
  const symbolEntries = [];
@@ -480,6 +1041,16 @@ export class ClassDynamicDispatch {
480
1041
  }
481
1042
  }
482
1043
  const branches = [];
1044
+ if (!dispatch.write && !dispatch.reflect && !info.def.tracksOwnFields) {
1045
+ const value = varRef("p.0", DYN, loc);
1046
+ const key = varRef("p.key", DYN, loc);
1047
+ const bag = { kind: "call", callee: this.propertyBag.name, args: [value], type: DYN, loc };
1048
+ branches.push({ kind: "if", cond: { kind: "libCall", fn: "dyn.hasOwnComputed", args: [bag, key], type: BOOL, loc }, then: [
1049
+ { kind: "return", value: dispatch.probe ? {
1050
+ kind: "libCall", fn: dispatch.probe === "enumerable" ? "dyn.propertyIsEnumerableComputed" : "dyn.hasOwnComputed", args: [bag, key], type: BOOL, loc,
1051
+ } : { kind: "dynKeyGet", value: bag, key, type: DYN, loc }, loc },
1052
+ ], else_: null, loc });
1053
+ }
483
1054
  for (const [name, symbol] of symbols) {
484
1055
  const before = lowerer.diags.length;
485
1056
  let then;
@@ -497,13 +1068,21 @@ export class ClassDynamicDispatch {
497
1068
  branches.push({ kind: "if", cond: { kind: "dynScalarEq", left: varRef("p.key", DYN, loc), right: symbol, type: BOOL, loc }, then, else_: null, loc });
498
1069
  }
499
1070
  if (branches.length) {
500
- dispatch.fn.body.unshift({ kind: "if", cond: condition, then: [
501
- { kind: "if", cond: { kind: "dynTest", test: "symbol", value: varRef("p.key", DYN, loc), type: BOOL, loc }, then: branches, else_: null, loc },
1071
+ dispatch.fn.body.unshift({ kind: "if", cond: selected.condition, then: [
1072
+ { kind: "if", cond: { kind: "dynTest", test: "symbol", value: varRef("p.key", DYN, loc), type: BOOL, loc }, then: transformStmtList(branches, { stmt: (statement) => statement, expr: (expression) => expression.kind === "varRef" && expression.localId === "p.receiver" ? selected.receiver : expression }), else_: null, loc },
502
1073
  ], else_: null, loc });
503
1074
  dispatch.branchIndex++;
504
1075
  }
505
1076
  }
1077
+ if (existingHelper) {
1078
+ dispatch.fn.body.splice(dispatch.branchIndex, 0, callHelper(existingHelper));
1079
+ changed = true;
1080
+ continue;
1081
+ }
506
1082
  const names = new Set([...info.fields.keys()].filter(isClassOwnEnumerableFieldName));
1083
+ if (info.builtinStream)
1084
+ for (const name of [...STREAM_BOOL_PROPERTIES, ...STREAM_NUM_PROPERTIES])
1085
+ names.add(name);
507
1086
  for (let owner = info; owner; owner = owner.base) {
508
1087
  const methodNames = [...owner.methods.keys()];
509
1088
  if (owner.genericMethods)
@@ -516,6 +1095,8 @@ export class ClassDynamicDispatch {
516
1095
  }
517
1096
  const branch = [];
518
1097
  for (const name of names) {
1098
+ if (dispatch.numericKey && String(Number(name)) !== name)
1099
+ continue;
519
1100
  const before = lowerer.diags.length;
520
1101
  let body;
521
1102
  try {
@@ -533,16 +1114,106 @@ export class ClassDynamicDispatch {
533
1114
  kind: "strEq", left: varRef(dispatch.keyLocal, STRING, loc), right: { kind: "strLit", value: name, type: STRING, loc }, negated: false, type: BOOL, loc,
534
1115
  }, then: body, else_: null, loc });
535
1116
  }
536
- dispatch.fn.body.splice(dispatch.branchIndex, 0, { kind: "if", cond: condition, then: branch, else_: null, loc });
1117
+ // Keep each class's key table in a separate function. A single
1118
+ // function containing every boxed class grows quadratically during
1119
+ // LLVM's control-flow optimization for large library workloads.
1120
+ const params = [...dispatch.fn.params, { localId: "p.receiver", name: "receiver", type: receiverType }];
1121
+ if (dispatch.keyLocal !== "p.key")
1122
+ params.push({ localId: dispatch.keyLocal, name: "key", type: STRING });
1123
+ const helper = {
1124
+ name: `%dyn.class.keys.${lowerer.liftedFns.length}`, params, returnType: dispatch.fn.returnType,
1125
+ locals: params.map((p) => ({ id: p.localId, name: p.name, type: p.type, mutable: false })),
1126
+ body: [...branch, ...transformStmtList(dispatch.fn.body.slice(dispatch.write ? -2 : -1), { stmt: (statement) => statement,
1127
+ expr: (expression) => expression.kind === "dynKeyGet" && expression.key.type.kind === "string"
1128
+ ? { kind: "libCall", fn: "dyn.bagGet", args: [expression.value, expression.key, lowerer.coerceToExpected(receiver, DYN)], type: DYN, loc }
1129
+ : expression.kind === "libCall" && expression.fn === "dyn.keySet"
1130
+ ? { ...expression, fn: "dyn.bagSet", args: [...expression.args, lowerer.coerceToExpected(receiver, DYN)] }
1131
+ : expression,
1132
+ })], loc,
1133
+ };
1134
+ this.generated.add(helper);
1135
+ lowerer.liftedFns.push(helper);
1136
+ this.computedClassHelpers.set(helperKey, helper);
1137
+ dispatch.fn.body.splice(dispatch.branchIndex, 0, callHelper(helper));
537
1138
  changed = true;
538
1139
  }
539
1140
  }
1141
+ if (this.enumeration)
1142
+ for (const plan of this.propertyReceivers(lowerer, false, false)) {
1143
+ if (!plan.info.def.tracksOwnFields || this.enumerationClasses.has(plan.key))
1144
+ continue;
1145
+ this.enumerationClasses.add(plan.key);
1146
+ const loc = this.enumeration.loc;
1147
+ const { condition } = this.propertyReceiver(plan, loc);
1148
+ const bag = { kind: "call", callee: this.propertyBag.name, args: [varRef("p.0", DYN, loc)], type: DYN, loc };
1149
+ this.enumeration.body.unshift({ kind: "if", cond: condition, then: [
1150
+ { kind: "return", value: { kind: "libCall", fn: "dyn.forInKeys", args: [bag], type: DYN, loc }, loc },
1151
+ ], else_: null, loc });
1152
+ changed = true;
1153
+ }
1154
+ if (this.constructDispatch)
1155
+ for (const [name, info] of this.constructors) {
1156
+ if (this.constructed.has(name))
1157
+ continue;
1158
+ this.constructed.add(name);
1159
+ const loc = this.constructDispatch.loc;
1160
+ const callee = varRef("p.0", DYN, loc), input = varRef("p.1", DYN, loc);
1161
+ const args = [];
1162
+ let supported = true;
1163
+ for (let i = 0; i < info.ctorParams.length; i++) {
1164
+ const param = info.ctorParams[i];
1165
+ if (param.mode !== "required" && param.mode !== "omittable") {
1166
+ supported = false;
1167
+ break;
1168
+ }
1169
+ const value = { kind: "dynKeyGet", value: input, key: { kind: "strLit", value: String(i), type: STRING, loc }, type: DYN, loc };
1170
+ const arg = lowerer.coerceToExpected(value, param.type);
1171
+ if (!typeEquals(arg.type, param.type)) {
1172
+ supported = false;
1173
+ break;
1174
+ }
1175
+ args.push(arg);
1176
+ }
1177
+ if (!supported || !info.decl)
1178
+ continue;
1179
+ const cls = classValueRef(lowerer, info, info.decl);
1180
+ const result = { kind: "new", className: name, args, type: { kind: "object", className: name }, loc };
1181
+ this.constructDispatch.body.unshift({ kind: "if", cond: { kind: "dynScalarEq", left: callee,
1182
+ right: lowerer.coerceToExpected(cls, DYN), type: BOOL, loc }, then: [
1183
+ { kind: "return", value: lowerer.coerceToExpected(result, DYN), loc },
1184
+ ], else_: null, loc });
1185
+ changed = true;
1186
+ }
1187
+ if (this.staticPropertyBag)
1188
+ for (const name of this.boxedConstructors) {
1189
+ if (this.staticBagClasses.has(name))
1190
+ continue;
1191
+ const info = lowerer.classes.get(name);
1192
+ if (!info?.decl || !isJsSourceFile(info.decl.getSourceFile()))
1193
+ continue;
1194
+ const loc = this.staticPropertyBag.loc;
1195
+ const bag = classStaticData(lowerer, info, loc);
1196
+ if (!bag)
1197
+ continue;
1198
+ this.staticBagClasses.add(name);
1199
+ const value = varRef("value", DYN, loc);
1200
+ this.staticPropertyBag.body.unshift({ kind: "if", cond: { kind: "libCall", fn: "dyn.classIs", args: [value,
1201
+ { kind: "strLit", value: typeKey({ kind: "classval", className: name }), type: STRING, loc }], type: BOOL, loc }, then: [{ kind: "return", value: bag, loc }], else_: null, loc });
1202
+ changed = true;
1203
+ }
1204
+ changed = this.normalizeDispatches(lowerer) || changed;
1205
+ for (const fn of this.generated)
1206
+ fn.speculativeDispatch = true;
540
1207
  return changed;
541
1208
  }
542
1209
  /** A known native class can access its shared bag directly. Keep the
543
1210
  * receiver as an owned parameter so calls and temporary instances retain
544
1211
  * their ordinary evaluation and lifetime rules without a boxed capsule. */
545
1212
  typedPropertyBag(lowerer, type, loc) {
1213
+ // A superclass constructor can be the first code to access this bag.
1214
+ // Its receiver still needs the actual subclass's prototype.
1215
+ if (lowerer.classes.get(type.className)?.subclasses.length)
1216
+ return null;
546
1217
  const existing = this.typedPropertyBags.get(type.className);
547
1218
  if (existing)
548
1219
  return existing;
@@ -551,11 +1222,8 @@ export class ClassDynamicDispatch {
551
1222
  return null;
552
1223
  const receiver = varRef("p.0", type, loc);
553
1224
  const bag = { kind: "fieldGet", obj: receiver, className: type.className, field: PROPERTY_BAG, type: DYN, loc };
554
- const info = lowerer.classes.get(type.className);
555
- const prototype = hasClassPrototypeData(info) ? classPrototypeData(lowerer, info, loc, receiver) : null;
556
- const initialize = { ...initializers[0], obj: receiver, value: prototype
557
- ? { kind: "libCall", fn: "dyn.objCreate", args: [prototype], type: DYN, loc }
558
- : { kind: "dynObjLit", fields: [], type: DYN, loc }, loc };
1225
+ const initialize = { ...initializers[0], obj: receiver,
1226
+ value: transformExpr(initializers[0].value, { stmt: (stmt) => stmt, expr: (expr) => expr.kind === "dynCheck" && expr.value.kind === "varRef" && expr.value.localId === "p.0" ? receiver : expr }), loc };
559
1227
  const helper = {
560
1228
  name: `%class.properties:${type.className}`, params: [{ localId: "p.0", name: "value", type }], returnType: DYN,
561
1229
  locals: [{ id: "p.0", name: "value", type, mutable: false }], loc,
@@ -570,6 +1238,79 @@ export class ClassDynamicDispatch {
570
1238
  lowerer.liftedFns.push(helper);
571
1239
  return helper;
572
1240
  }
1241
+ normalizedDispatches() {
1242
+ return [...(this.propertyBag ? [this.propertyBag] : []),
1243
+ ...[...this.properties.values()].map((dispatch) => dispatch.fn),
1244
+ ...[...this.computed.values()].map((dispatch) => dispatch.fn),
1245
+ ...[...this.dispatches.values()].flatMap((dispatch) => [dispatch.fn, dispatch.callback])];
1246
+ }
1247
+ constructorPropertyBag(lowerer, loc) {
1248
+ if (!this.staticPropertyBag) {
1249
+ this.staticPropertyBag = { name: "%dyn.class.staticProperties", params: [{ localId: "value", name: "value", type: DYN }], returnType: DYN,
1250
+ locals: [{ id: "value", name: "value", type: DYN, mutable: false }],
1251
+ body: [{ kind: "return", value: varRef("value", DYN, loc), loc }], loc };
1252
+ lowerer.liftedFns.push(this.staticPropertyBag);
1253
+ this.generated.add(this.staticPropertyBag);
1254
+ }
1255
+ return this.staticPropertyBag;
1256
+ }
1257
+ /** A base capsule and a derived capsule refer to the same native object.
1258
+ * Select its actual layout once, so each member table needs one branch
1259
+ * per class rather than one per possible base/derived capsule pair. */
1260
+ normalizeDispatches(lowerer) {
1261
+ const entries = [];
1262
+ for (const name of this.boxed) {
1263
+ const capsule = lowerer.classes.get(name);
1264
+ if (!capsule || capsule.builtinEmitter || capsule.builtinStream || capsule.builtinError)
1265
+ continue;
1266
+ const children = [];
1267
+ const visit = (info) => {
1268
+ for (const child of info.subclasses) {
1269
+ if (lowerer.classCanBeConstructed(child))
1270
+ children.push(child);
1271
+ visit(child);
1272
+ }
1273
+ };
1274
+ visit(capsule);
1275
+ if (children.length)
1276
+ entries.push({ capsule, children: children.reverse() });
1277
+ }
1278
+ if (!entries.length && !this.normalization)
1279
+ return false;
1280
+ const key = JSON.stringify(entries.map(({ capsule, children }) => [capsule.def.name, children.map((child) => child.def.name)]));
1281
+ let changed = key !== this.normalizationKey;
1282
+ if (!this.normalization) {
1283
+ const loc = this.propertyBag.loc;
1284
+ this.normalization = { name: "%dyn.class.normalize", params: [{ localId: "value", name: "value", type: DYN }], returnType: DYN,
1285
+ locals: [{ id: "value", name: "value", type: DYN, mutable: false }], body: [], loc };
1286
+ lowerer.liftedFns.push(this.normalization);
1287
+ this.generated.add(this.normalization);
1288
+ changed = true;
1289
+ }
1290
+ if (changed) {
1291
+ this.normalizationKey = key;
1292
+ const loc = this.normalization.loc;
1293
+ const value = varRef("value", DYN, loc);
1294
+ this.normalization.body = entries.map(({ capsule, children }) => {
1295
+ const type = { kind: "object", className: capsule.def.name };
1296
+ const receiver = { kind: "dynCheck", value, type, loc };
1297
+ return { kind: "if", cond: { kind: "libCall", fn: "dyn.typedRefIs", args: [value,
1298
+ { kind: "strLit", value: typeKey(type), type: STRING, loc }], type: BOOL, loc }, then: children.map((child) => ({
1299
+ kind: "if", cond: { kind: "instanceOf", value: receiver, className: child.def.name, type: BOOL, loc }, then: [{
1300
+ kind: "return", value: { kind: "dynFrom", value: { kind: "downcast", value: receiver,
1301
+ type: { kind: "object", className: child.def.name }, loc }, type: DYN, loc }, loc,
1302
+ }], else_: null, loc,
1303
+ })), else_: null, loc };
1304
+ });
1305
+ this.normalization.body.push({ kind: "return", value, loc });
1306
+ }
1307
+ for (const fn of this.normalizedDispatches()) {
1308
+ fn.locals.find((local) => local.id === "p.0").mutable = true;
1309
+ fn.body.unshift({ kind: "assign", localId: "p.0", value: { kind: "call", callee: this.normalization.name,
1310
+ args: [varRef("p.0", DYN, fn.loc)], type: DYN, loc: fn.loc }, loc: fn.loc });
1311
+ }
1312
+ return changed;
1313
+ }
573
1314
  /** The hidden bag is part of the native object layout, so every capsule
574
1315
  * shares it and normal class tracing/disposal owns its values. Insert it
575
1316
  * at the same prefix offset throughout a hierarchy, including classes
@@ -592,14 +1333,19 @@ export class ClassDynamicDispatch {
592
1333
  }
593
1334
  /** A capsule retains its static type, while its object may be a subclass.
594
1335
  * Preorder insertion lets later branches test the most derived layout first. */
595
- propertyReceivers(lowerer) {
1336
+ propertyReceivers(lowerer, streams = false, normalize = true) {
596
1337
  const plans = [];
1338
+ const seen = new Set();
597
1339
  for (const name of this.boxed) {
598
1340
  const capsule = lowerer.classes.get(name);
599
- if (!capsule || capsule.builtinEmitter || capsule.builtinStream || capsule.builtinError)
1341
+ if (!capsule || (capsule.builtinEmitter || capsule.builtinStream) && !streams || capsule.builtinError)
600
1342
  continue;
1343
+ const canonical = normalize && !capsule.builtinStream && !capsule.builtinEmitter;
601
1344
  const visit = (info) => {
602
- plans.push({ info, capsule, key: JSON.stringify([name, info.def.name]) });
1345
+ if ((!canonical || !seen.has(info.def.name)) && (info === capsule || lowerer.classCanBeConstructed(info))) {
1346
+ seen.add(info.def.name);
1347
+ plans.push({ info, capsule: canonical ? info : capsule, key: canonical ? info.def.name : JSON.stringify([name, info.def.name]) });
1348
+ }
603
1349
  for (const child of info.subclasses)
604
1350
  visit(child);
605
1351
  };
@@ -621,9 +1367,58 @@ export class ClassDynamicDispatch {
621
1367
  className: plan.info.def.name, type: BOOL, loc }, else_: { kind: "boolLit", value: false, type: BOOL, loc }, type: BOOL, loc },
622
1368
  };
623
1369
  }
1370
+ reflectPropertyBody(lowerer, dispatch, info, receiver) {
1371
+ const { name, write } = dispatch;
1372
+ const loc = dispatch.fn.loc;
1373
+ const actualReceiver = varRef("p.reflectReceiver", DYN, loc);
1374
+ const instance = lowerer.coerceToExpected(receiver, DYN);
1375
+ const key = { kind: "strLit", value: name, type: STRING, loc };
1376
+ const field = info.fields.get(name);
1377
+ const member = `${write ? "set" : "get"}:${name}`;
1378
+ const accessor = field ? null : findMethodOn(lowerer, info, member);
1379
+ const bag = { kind: "call", callee: this.propertyBag.name, args: [instance], type: DYN, loc };
1380
+ const fallback = { kind: "libCall", fn: write ? "dyn.reflectSet" : "dyn.reflectGet",
1381
+ args: [bag, lowerer.coerceToExpected(key, DYN), ...(write ? [varRef("p.1", DYN, loc)] : []), actualReceiver], type: write ? BOOL : DYN, loc };
1382
+ if (accessor && info.decl) {
1383
+ const method = classMethodValue(lowerer, info.decl, info, member, loc);
1384
+ if (method) {
1385
+ const call = { kind: "libCall", fn: "dyn.reflectApply", args: [lowerer.coerceToExpected(method, DYN), actualReceiver,
1386
+ { kind: "dynArrLit", elems: write ? [varRef("p.1", DYN, loc)] : [], type: DYN, loc }], type: DYN, loc };
1387
+ return [
1388
+ { kind: "if", cond: { kind: "libCall", fn: "dyn.hasOwn", args: [bag, key], type: BOOL, loc }, then: [{ kind: "return", value: fallback, loc }], else_: null, loc },
1389
+ ...(write ? [{ kind: "exprStmt", expr: call, loc }, { kind: "return", value: { kind: "boolLit", value: true, type: BOOL, loc }, loc }] : [{ kind: "return", value: call, loc }]),
1390
+ ];
1391
+ }
1392
+ }
1393
+ if (write && !field) {
1394
+ if (findMethodOn(lowerer, info, `get:${name}`) && !accessor)
1395
+ return [{ kind: "return", value: { kind: "boolLit", value: false, type: BOOL, loc }, loc }];
1396
+ return [{ kind: "return", value: fallback, loc }];
1397
+ }
1398
+ const body = this.propertyBody(lowerer, dispatch, info, receiver);
1399
+ if (!write)
1400
+ return transformStmtList(body, { stmt: (statement) => statement, expr: (expression) => expression.kind === "libCall" && expression.fn === "dyn.bagGet"
1401
+ ? { ...expression, fn: "dyn.reflectGet", args: [expression.args[0], lowerer.coerceToExpected(expression.args[1], DYN), actualReceiver] } : expression });
1402
+ return [
1403
+ { kind: "if", cond: { kind: "dynScalarEq", left: instance, right: actualReceiver, negated: true, type: BOOL, loc }, then: [{ kind: "return", value: {
1404
+ kind: "libCall", fn: "dyn.reflectDefine", args: [actualReceiver, lowerer.coerceToExpected(key, DYN), varRef("p.1", DYN, loc)], type: BOOL, loc,
1405
+ }, loc }], else_: null, loc },
1406
+ ...transformStmtList(body, { stmt: (statement) => statement.kind === "return" && !statement.value
1407
+ ? { ...statement, value: { kind: "boolLit", value: true, type: BOOL, loc } } : statement, expr: (expression) => expression }),
1408
+ ];
1409
+ }
624
1410
  propertyBody(lowerer, dispatch, info, receiver) {
625
1411
  const { name, write } = dispatch;
626
1412
  const loc = dispatch.fn.loc;
1413
+ if (info.builtinStream !== undefined && streamProperty(name)) {
1414
+ if (write)
1415
+ return [{ kind: "runtimeFence", code: "SC2020", message: `writing stream property '${name}' through an untyped value is not supported`, loc }];
1416
+ return [{ kind: "return", value: lowerer.coerceToExpected({ kind: "libCall", fn: "stream.prop", args: [receiver, { kind: "strLit", value: name, type: STRING, loc }], type: STREAM_BOOL_PROPERTIES.has(name) ? BOOL : F64, loc }, DYN), loc }];
1417
+ }
1418
+ if ((name === "message" || name === "name") && lowerer.isSubclassOf(info.def.name, "%Error"))
1419
+ return write
1420
+ ? [errorPropertyWrite(lowerer, receiver, lowerer.coerceToExpected(varRef("p.1", DYN, loc), STRING), name), { kind: "return", value: null, loc }]
1421
+ : [{ kind: "return", value: lowerer.coerceToExpected(errorPropertyRead(lowerer, receiver, name), DYN), loc }];
627
1422
  const field = info.fields.get(name);
628
1423
  const member = `${write ? "set" : "get"}:${name}`;
629
1424
  const accessor = field ? null : findMethodOn(lowerer, info, member);
@@ -631,6 +1426,14 @@ export class ClassDynamicDispatch {
631
1426
  const getRecord = (id) => lowerer.shapes.get(id);
632
1427
  const getUnion = (id) => lowerer.unions.get(id);
633
1428
  if (!write) {
1429
+ if (name === "constructor" && !field && !accessor && info.decl && !info.generic && !info.localClass && !info.classDecorators) {
1430
+ this.constructors.set(info.def.name, info);
1431
+ const cls = lowerer.coerceToExpected(classValueRef(lowerer, info, info.decl), DYN);
1432
+ const bag = { kind: "call", callee: this.propertyBag.name, args: [lowerer.coerceToExpected(receiver, DYN)], type: DYN, loc };
1433
+ const key = { kind: "strLit", value: name, type: STRING, loc };
1434
+ return [{ kind: "return", value: { kind: "ternary", cond: { kind: "libCall", fn: "dyn.hasKey", args: [bag, key], type: BOOL, loc },
1435
+ then: { kind: "libCall", fn: "dyn.bagGet", args: [bag, key, lowerer.coerceToExpected(receiver, DYN)], type: DYN, loc }, else_: cls, type: DYN, loc }, loc }];
1436
+ }
634
1437
  if (name === "toString" && findMethodOn(lowerer, info, name)?.declarer.builtinError) {
635
1438
  return [{ kind: "return", value: errorToStringMethod(lowerer, receiver), loc }];
636
1439
  }
@@ -655,10 +1458,25 @@ export class ClassDynamicDispatch {
655
1458
  // A capsule may have the base ABI even when its native object is
656
1459
  // a subclass (for example an inherited iterator returning this).
657
1460
  // Select the method declaration at extraction, before detaching it.
658
- const overrides = [...lowerer.classes.values()].filter((candidate) => candidate !== info && lowerer.isSubclassOf(candidate.def.name, info.def.name) &&
1461
+ const overrides = [...lowerer.classes.values()].filter((candidate) => candidate !== info && lowerer.classCanBeConstructed(candidate) && lowerer.isSubclassOf(candidate.def.name, info.def.name) &&
659
1462
  (candidate.methods.has(name) || candidate.genericMethods?.has(name)));
660
1463
  overrides.sort((a, b) => lowerer.isSubclassOf(a.def.name, b.def.name) ? -1 : lowerer.isSubclassOf(b.def.name, a.def.name) ? 1 : 0);
661
1464
  const body = [];
1465
+ const symbol = symbolMemberKey(lowerer, info, name, loc);
1466
+ if (symbol || isClassOwnEnumerableFieldName(name) && !name.startsWith("sym:")) {
1467
+ const prototypeMethod = lowerer.prototypeMethodAccesses.has(name);
1468
+ if (prototypeMethod)
1469
+ classPrototypeData(lowerer, info, loc, receiver);
1470
+ const bag = { kind: "call", callee: this.propertyBag.name,
1471
+ args: [lowerer.coerceToExpected(receiver, DYN)], type: DYN, loc };
1472
+ const key = symbol ?? { kind: "strLit", value: name, type: STRING, loc };
1473
+ body.push({ kind: "if", cond: { kind: "libCall", fn: symbol ? "dyn.hasOwnComputed" : "dyn.hasKey", args: [bag, key], type: BOOL, loc },
1474
+ then: [{ kind: "return", value: { kind: "libCall", fn: symbol ? "dyn.reflectGet" : "dyn.bagGet", args: [bag, key, lowerer.coerceToExpected(receiver, DYN)], type: DYN, loc }, loc }], else_: null, loc });
1475
+ if (prototypeMethod) {
1476
+ body.push({ kind: "return", value: dynUndefinedExpr(loc), loc });
1477
+ return body;
1478
+ }
1479
+ }
662
1480
  for (const candidate of overrides) {
663
1481
  const selected = classMethodValue(lowerer, info.decl, candidate, name, loc);
664
1482
  const branch = selected && canConvertToDyn(selected.type, getRecord, getUnion)
@@ -670,8 +1488,12 @@ export class ClassDynamicDispatch {
670
1488
  return body;
671
1489
  }
672
1490
  }
673
- if (!field && !accessor)
674
- return [{ kind: "return", value: dynUndefinedExpr(loc), loc }];
1491
+ if (!field && !accessor) {
1492
+ const instance = lowerer.coerceToExpected(receiver, DYN);
1493
+ const bag = { kind: "call", callee: this.propertyBag.name, args: [instance], type: DYN, loc };
1494
+ return [{ kind: "return", value: { kind: "libCall", fn: "dyn.bagGet", args: [bag,
1495
+ { kind: "strLit", value: name, type: STRING, loc }, instance], type: DYN, loc }, loc }];
1496
+ }
675
1497
  const type = field ?? accessor.sig.ret;
676
1498
  if (!canConvertToDyn(type, getRecord, getUnion))
677
1499
  return fence();
@@ -683,8 +1505,31 @@ export class ClassDynamicDispatch {
683
1505
  (t.kind === "union" && (getUnion(t.unionId)?.arms.some(mutable) ?? false));
684
1506
  if (boxed.kind === "dynFrom" && mutable(boxed.value.type))
685
1507
  boxed.liveRef = true;
1508
+ if (accessor) {
1509
+ const bag = { kind: "call", callee: this.propertyBag.name,
1510
+ args: [lowerer.coerceToExpected(receiver, DYN)], type: DYN, loc };
1511
+ const key = { kind: "strLit", value: name, type: STRING, loc };
1512
+ return [{ kind: "if", cond: { kind: "libCall", fn: "dyn.hasOwn", args: [bag, key], type: BOOL, loc },
1513
+ then: [{ kind: "return", value: { kind: "libCall", fn: "dyn.bagGet", args: [bag, key, lowerer.coerceToExpected(receiver, DYN)], type: DYN, loc }, loc }], else_: null, loc },
1514
+ { kind: "return", value: boxed, loc }];
1515
+ }
1516
+ if (field && info.def.tracksOwnFields) {
1517
+ const instance = lowerer.coerceToExpected(receiver, DYN);
1518
+ const bag = { kind: "call", callee: this.propertyBag.name, args: [instance], type: DYN, loc };
1519
+ const symbol = symbolMemberKey(lowerer, info, name, loc);
1520
+ const key = symbol ?? { kind: "strLit", value: name, type: STRING, loc };
1521
+ return [{ kind: "return", value: { kind: "ternary", cond: { kind: "libCall", fn: symbol ? "dyn.hasOwnComputed" : "dyn.hasOwn", args: [bag, key], type: BOOL, loc },
1522
+ then: boxed, else_: { kind: "libCall", fn: symbol ? "dyn.reflectGet" : "dyn.bagGet", args: [bag, key, instance], type: DYN, loc }, type: DYN, loc }, loc }];
1523
+ }
686
1524
  return [{ kind: "return", value: boxed, loc }];
687
1525
  }
1526
+ if (!field && !accessor && isClassOwnEnumerableFieldName(name) && !name.startsWith("sym:") && !findMethodOn(lowerer, info, `get:${name}`)) {
1527
+ const bag = { kind: "call", callee: this.propertyBag.name,
1528
+ args: [lowerer.coerceToExpected(receiver, DYN)], type: DYN, loc };
1529
+ return [{ kind: "exprStmt", expr: { kind: "libCall", fn: "dyn.bagSet", args: [bag,
1530
+ { kind: "strLit", value: name, type: STRING, loc }, varRef("p.1", DYN, loc), lowerer.coerceToExpected(receiver, DYN)], type: VOID, loc }, loc },
1531
+ { kind: "return", value: null, loc }];
1532
+ }
688
1533
  if (!field && !accessor)
689
1534
  return [{ kind: "throw", value: {
690
1535
  kind: "libCall", fn: "error.new", args: [{
@@ -707,6 +1552,35 @@ export class ClassDynamicDispatch {
707
1552
  methodBody(lowerer, dispatch, info, receiver) {
708
1553
  const { method, loc } = dispatch.source;
709
1554
  const fence = () => [{ kind: "runtimeFence", code: "SC2020", message: `calling '${method}' on this native class through an untyped value is not supported yet`, loc }];
1555
+ if (info.builtinStream || emitterRooted(lowerer, info) && EMITTER_METHODS.has(method) && !findMethodOn(lowerer, info, method)) {
1556
+ const incoming = dispatch.source.args.map((_, i) => varRef(`p.${i + 1}`, DYN, loc));
1557
+ const registering = ["on", "addListener", "once", "prependListener", "prependOnceListener"].includes(method);
1558
+ const removing = method === "off" || method === "removeListener";
1559
+ const ret = (value) => [{ kind: "return", value: lowerer.coerceToExpected(value, DYN), loc }];
1560
+ if (method === "emit") {
1561
+ if (!incoming.length)
1562
+ return fence();
1563
+ return ret({ kind: "libCall", fn: "emitter.emitFlex", args: [receiver,
1564
+ lowerer.coerceToExpected(incoming[0], STRING), ...incoming.slice(1)], type: BOOL, loc });
1565
+ }
1566
+ if (registering || removing) {
1567
+ if (incoming.length < 2)
1568
+ return fence();
1569
+ const event = lowerer.coerceToExpected(incoming[0], STRING);
1570
+ const cb = incoming[1];
1571
+ return [{ kind: "exprStmt", expr: { kind: "libCall", fn: "emitter.checkListener", args: [cb], type: VOID, loc }, loc }, ...ret({ kind: "libCall", fn: registering ? info.builtinStream ? "stream.onDyn" : "emitter.onFlex" : "emitter.offDyn", args: [receiver, event, cb, ...(registering ? [
1572
+ { kind: "boolLit", value: method === "once" || method === "prependOnceListener", type: BOOL, loc },
1573
+ { kind: "boolLit", value: method.startsWith("prepend"), type: BOOL, loc },
1574
+ ] : [])], type: receiver.type, loc })];
1575
+ }
1576
+ if (method === "read")
1577
+ return ret({ kind: "libCall", fn: "readable.readDyn", args: [receiver, incoming[0] ?? dynUndefinedExpr(loc)], type: DYN, loc });
1578
+ if (method === "pause" || method === "resume" || method === "isPaused")
1579
+ return ret({ kind: "libCall", fn: method === "pause" ? "readable.pause" : method === "resume" ? "readable.resume" : "readable.isPaused", args: [receiver], type: method === "isPaused" ? BOOL : receiver.type, loc });
1580
+ if (method === "destroy" && (!incoming.length || incoming[0]?.kind === "unitLit"))
1581
+ return ret({ kind: "libCall", fn: "stream.destroy", args: [receiver], type: receiver.type, loc });
1582
+ return fence();
1583
+ }
710
1584
  const methodInfo = findMethodOn(lowerer, info, method);
711
1585
  if (this.asyncFree && (methodInfo?.sig.gen || methodInfo?.sig.async)) {
712
1586
  return [{ kind: "runtimeFence", code: "SC4005", message: `calling async or generator method '${method}' through an untyped library value is not supported`, loc }];
@@ -794,7 +1668,7 @@ export class ClassDynamicDispatch {
794
1668
  body.unshift({ kind: "if", cond: { kind: "dynTest", test: "undefined", negated: true, value: descriptor, type: BOOL, loc }, then: [
795
1669
  { kind: "return", value: { kind: "dynCall", callee: { kind: "dynKeyGet", value: descriptor,
796
1670
  key: { kind: "strLit", value: "value", type: STRING, loc }, type: DYN, loc }, receiver: varRef("p.0", DYN, loc),
797
- args: incoming, calleeName: dispatch.source.calleeName, type: DYN, loc }, loc },
1671
+ args: incoming, calleeName: dispatch.source.calleeName, calleeNameValue: varRef("p.calleeName", STRING, loc), type: DYN, loc }, loc },
798
1672
  ], else_: null, loc });
799
1673
  }
800
1674
  return body;