@scriptc/compiler 0.2.0 → 0.2.1

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 (194) hide show
  1. package/ambient/scriptc-node-fallback.d.ts +25 -15
  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 +5 -0
  7. package/dist/backend/llvm/dyn.js +166 -99
  8. package/dist/backend/llvm/dyn.js.map +1 -1
  9. package/dist/backend/llvm/emitter.d.ts +2 -2
  10. package/dist/backend/llvm/emitter.js +79 -20
  11. package/dist/backend/llvm/emitter.js.map +1 -1
  12. package/dist/backend/llvm/expr-bytes.d.ts +1 -1
  13. package/dist/backend/llvm/expr-bytes.js +28 -17
  14. package/dist/backend/llvm/expr-bytes.js.map +1 -1
  15. package/dist/backend/llvm/expr-control.js +1 -1
  16. package/dist/backend/llvm/expr-control.js.map +1 -1
  17. package/dist/backend/llvm/expr-dynamic.js +18 -9
  18. package/dist/backend/llvm/expr-dynamic.js.map +1 -1
  19. package/dist/backend/llvm/expr-primitives.js +11 -1
  20. package/dist/backend/llvm/expr-primitives.js.map +1 -1
  21. package/dist/backend/llvm/expr-records.js +11 -3
  22. package/dist/backend/llvm/expr-records.js.map +1 -1
  23. package/dist/backend/llvm/expr-stream-bridges.js +125 -17
  24. package/dist/backend/llvm/expr-stream-bridges.js.map +1 -1
  25. package/dist/backend/llvm/lib-process.js +23 -3
  26. package/dist/backend/llvm/lib-process.js.map +1 -1
  27. package/dist/backend/llvm/lib-shared.js +45 -2
  28. package/dist/backend/llvm/lib-shared.js.map +1 -1
  29. package/dist/backend/llvm/shapes.js +8 -2
  30. package/dist/backend/llvm/shapes.js.map +1 -1
  31. package/dist/backend/native-toolchain.d.ts +3 -6
  32. package/dist/backend/native-toolchain.js +53 -23
  33. package/dist/backend/native-toolchain.js.map +1 -1
  34. package/dist/backend/native-toolchain.test.js +72 -0
  35. package/dist/backend/native-toolchain.test.js.map +1 -1
  36. package/dist/backend/runtime-abi.d.ts +2 -2
  37. package/dist/backend/runtime-abi.js +2 -2
  38. package/dist/backend/runtime-pack-native.js +4 -1
  39. package/dist/backend/runtime-pack-native.js.map +1 -1
  40. package/dist/backend/runtime-pack-targets.test.js +1 -1
  41. package/dist/backend/runtime-pack.test.js +1 -1
  42. package/dist/backend/targets.js +10 -6
  43. package/dist/backend/targets.js.map +1 -1
  44. package/dist/backend/targets.test.js +7 -0
  45. package/dist/backend/targets.test.js.map +1 -1
  46. package/dist/compat/fetch-profile.js +34 -4
  47. package/dist/compat/fetch-profile.js.map +1 -1
  48. package/dist/diagnostics/diagnostic.js +2 -7
  49. package/dist/diagnostics/diagnostic.js.map +1 -1
  50. package/dist/frontend/lowering/class-callbacks.d.ts +3 -3
  51. package/dist/frontend/lowering/class-callbacks.js +26 -15
  52. package/dist/frontend/lowering/class-callbacks.js.map +1 -1
  53. package/dist/frontend/lowering/class-construction.d.ts +12 -0
  54. package/dist/frontend/lowering/class-construction.js +156 -0
  55. package/dist/frontend/lowering/class-construction.js.map +1 -0
  56. package/dist/frontend/lowering/class-descriptors.d.ts +1 -0
  57. package/dist/frontend/lowering/class-descriptors.js +46 -5
  58. package/dist/frontend/lowering/class-descriptors.js.map +1 -1
  59. package/dist/frontend/lowering/class-dynamic-dispatch.d.ts +11 -1
  60. package/dist/frontend/lowering/class-dynamic-dispatch.js +403 -48
  61. package/dist/frontend/lowering/class-dynamic-dispatch.js.map +1 -1
  62. package/dist/frontend/lowering/class-dynamic-dispatch.test.js +2 -1
  63. package/dist/frontend/lowering/class-dynamic-dispatch.test.js.map +1 -1
  64. package/dist/frontend/lowering/class-instance-constructor.js +4 -1
  65. package/dist/frontend/lowering/class-instance-constructor.js.map +1 -1
  66. package/dist/frontend/lowering/class-method-values.d.ts +4 -1
  67. package/dist/frontend/lowering/class-method-values.js +72 -7
  68. package/dist/frontend/lowering/class-method-values.js.map +1 -1
  69. package/dist/frontend/lowering/class-prototypes.d.ts +5 -2
  70. package/dist/frontend/lowering/class-prototypes.js +121 -13
  71. package/dist/frontend/lowering/class-prototypes.js.map +1 -1
  72. package/dist/frontend/lowering/class-runtime-statics.d.ts +8 -0
  73. package/dist/frontend/lowering/class-runtime-statics.js +66 -0
  74. package/dist/frontend/lowering/class-runtime-statics.js.map +1 -0
  75. package/dist/frontend/lowering/class-static-data.js +2 -2
  76. package/dist/frontend/lowering/class-static-data.js.map +1 -1
  77. package/dist/frontend/lowering/containers/bytes.js +49 -13
  78. package/dist/frontend/lowering/containers/bytes.js.map +1 -1
  79. package/dist/frontend/lowering/containers/string-and-regexp.js +19 -47
  80. package/dist/frontend/lowering/containers/string-and-regexp.js.map +1 -1
  81. package/dist/frontend/lowering/error-methods.d.ts +4 -1
  82. package/dist/frontend/lowering/error-methods.js +100 -1
  83. package/dist/frontend/lowering/error-methods.js.map +1 -1
  84. package/dist/frontend/lowering/expressions/native-proxy.js +3 -1
  85. package/dist/frontend/lowering/expressions/native-proxy.js.map +1 -1
  86. package/dist/frontend/lowering/expressions/nullish-assignment.js +19 -11
  87. package/dist/frontend/lowering/expressions/nullish-assignment.js.map +1 -1
  88. package/dist/frontend/lowering/expressions/object-literals.js +38 -7
  89. package/dist/frontend/lowering/expressions/object-literals.js.map +1 -1
  90. package/dist/frontend/lowering/expressions/optional-chains.d.ts +3 -4
  91. package/dist/frontend/lowering/expressions/optional-chains.js +69 -17
  92. package/dist/frontend/lowering/expressions/optional-chains.js.map +1 -1
  93. package/dist/frontend/lowering/function-context.d.ts +11 -3
  94. package/dist/frontend/lowering/function-context.js +11 -5
  95. package/dist/frontend/lowering/function-context.js.map +1 -1
  96. package/dist/frontend/lowering/generator-dynamic-dispatch.d.ts +1 -0
  97. package/dist/frontend/lowering/generator-dynamic-dispatch.js +39 -1
  98. package/dist/frontend/lowering/generator-dynamic-dispatch.js.map +1 -1
  99. package/dist/frontend/lowering/lib-boundary.d.ts +2 -0
  100. package/dist/frontend/lowering/lib-boundary.js +54 -1
  101. package/dist/frontend/lowering/lib-boundary.js.map +1 -1
  102. package/dist/frontend/lowering/lower-builtin-values.d.ts +7 -1
  103. package/dist/frontend/lowering/lower-builtin-values.js +113 -3
  104. package/dist/frontend/lowering/lower-builtin-values.js.map +1 -1
  105. package/dist/frontend/lowering/lower-builtins.d.ts +6 -5
  106. package/dist/frontend/lowering/lower-builtins.js +156 -41
  107. package/dist/frontend/lowering/lower-builtins.js.map +1 -1
  108. package/dist/frontend/lowering/lower-calls.d.ts +8 -5
  109. package/dist/frontend/lowering/lower-calls.js +448 -83
  110. package/dist/frontend/lowering/lower-calls.js.map +1 -1
  111. package/dist/frontend/lowering/lower-classes.d.ts +13 -13
  112. package/dist/frontend/lowering/lower-classes.js +650 -176
  113. package/dist/frontend/lowering/lower-classes.js.map +1 -1
  114. package/dist/frontend/lowering/lower-containers.d.ts +9 -0
  115. package/dist/frontend/lowering/lower-containers.js +135 -36
  116. package/dist/frontend/lowering/lower-containers.js.map +1 -1
  117. package/dist/frontend/lowering/lower-dgram.js +3 -2
  118. package/dist/frontend/lowering/lower-dgram.js.map +1 -1
  119. package/dist/frontend/lowering/lower-exprs.d.ts +2 -2
  120. package/dist/frontend/lowering/lower-exprs.js +531 -126
  121. package/dist/frontend/lowering/lower-exprs.js.map +1 -1
  122. package/dist/frontend/lowering/lower-generators.js +12 -1
  123. package/dist/frontend/lowering/lower-generators.js.map +1 -1
  124. package/dist/frontend/lowering/lower-island.d.ts +2 -0
  125. package/dist/frontend/lowering/lower-island.js +32 -10
  126. package/dist/frontend/lowering/lower-island.js.map +1 -1
  127. package/dist/frontend/lowering/lower-modules.js +220 -17
  128. package/dist/frontend/lowering/lower-modules.js.map +1 -1
  129. package/dist/frontend/lowering/lower-modules.test.d.ts +1 -0
  130. package/dist/frontend/lowering/lower-modules.test.js +39 -0
  131. package/dist/frontend/lowering/lower-modules.test.js.map +1 -0
  132. package/dist/frontend/lowering/lower-stmts.d.ts +7 -3
  133. package/dist/frontend/lowering/lower-stmts.js +135 -26
  134. package/dist/frontend/lowering/lower-stmts.js.map +1 -1
  135. package/dist/frontend/lowering/lower-stream.js +1 -1
  136. package/dist/frontend/lowering/lower-stream.js.map +1 -1
  137. package/dist/frontend/lowering/lowerer.d.ts +27 -1
  138. package/dist/frontend/lowering/lowerer.js +172 -50
  139. package/dist/frontend/lowering/lowerer.js.map +1 -1
  140. package/dist/frontend/lowering/lowerer.test.js +149 -3
  141. package/dist/frontend/lowering/lowerer.test.js.map +1 -1
  142. package/dist/frontend/lowering/surfaces.d.ts +2 -0
  143. package/dist/frontend/lowering/surfaces.js +25 -8
  144. package/dist/frontend/lowering/surfaces.js.map +1 -1
  145. package/dist/frontend/lowering/text-decoder-encoding.d.ts +2 -0
  146. package/dist/frontend/lowering/text-decoder-encoding.js +5 -0
  147. package/dist/frontend/lowering/text-decoder-encoding.js.map +1 -1
  148. package/dist/frontend/lowering/unregistered-class-types.test.js +3 -3
  149. package/dist/frontend/lowering/unregistered-class-types.test.js.map +1 -1
  150. package/dist/frontend/npm-static-prune.js +116 -34
  151. package/dist/frontend/npm-static-prune.js.map +1 -1
  152. package/dist/frontend/npm-static-prune.test.js +8 -0
  153. package/dist/frontend/npm-static-prune.test.js.map +1 -1
  154. package/dist/frontend/program.d.ts +1 -1
  155. package/dist/frontend/program.js +53 -24
  156. package/dist/frontend/program.js.map +1 -1
  157. package/dist/frontend/program.test.js +18 -0
  158. package/dist/frontend/program.test.js.map +1 -1
  159. package/dist/frontend/ts7/ast.js +4 -3
  160. package/dist/frontend/ts7/ast.js.map +1 -1
  161. package/dist/frontend/ts7/semantic-json.js +5 -10
  162. package/dist/frontend/ts7/semantic-json.js.map +1 -1
  163. package/dist/frontend/type-mapper.d.ts +9 -1
  164. package/dist/frontend/type-mapper.js +90 -13
  165. package/dist/frontend/type-mapper.js.map +1 -1
  166. package/dist/frontend/type-mapper.test.js +23 -0
  167. package/dist/frontend/type-mapper.test.js.map +1 -1
  168. package/dist/frontend/union-discriminants.d.ts +1 -1
  169. package/dist/frontend/union-discriminants.js +2 -2
  170. package/dist/frontend/union-discriminants.js.map +1 -1
  171. package/dist/ir/analysis.js +2 -0
  172. package/dist/ir/analysis.js.map +1 -1
  173. package/dist/ir/ir.d.ts +52 -33
  174. package/dist/ir/ir.js +142 -42
  175. package/dist/ir/ir.js.map +1 -1
  176. package/dist/ir/ir.test.js +35 -0
  177. package/dist/ir/ir.test.js.map +1 -1
  178. package/dist/ir/serialize.js +15 -3
  179. package/dist/ir/serialize.js.map +1 -1
  180. package/dist/ir/serialize.test.js +5 -1
  181. package/dist/ir/serialize.test.js.map +1 -1
  182. package/dist/ir/validate.js +80 -11
  183. package/dist/ir/validate.js.map +1 -1
  184. package/dist/ir/validate.test.js +61 -1
  185. package/dist/ir/validate.test.js.map +1 -1
  186. package/dist/native/sanitizer.js +3 -1
  187. package/dist/native/sanitizer.js.map +1 -1
  188. package/dist/native/toolchain.d.ts +1 -1
  189. package/dist/native/toolchain.js +26 -6
  190. package/dist/native/toolchain.js.map +1 -1
  191. package/dist/native/toolchain.test.js +53 -1
  192. package/dist/native/toolchain.test.js.map +1 -1
  193. package/package.json +20 -20
  194. package/surface-manifest.json +56 -81
@@ -1,26 +1,48 @@
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, 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
11
  import { classMethodValue } from "./class-method-values.js";
12
12
  import * as ts from "../ts7/adapter.js";
13
13
  import { SYMBOL_T } from "../../ir/ir.js";
14
14
  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))
15
+ import { refreshDescriptorGuards } from "./class-descriptors.js";
16
+ import { ClassConstructionDispatch } from "./class-construction.js";
17
+ const STREAM_METHODS = new Set(["on", "addListener", "once", "prependListener", "prependOnceListener", "off", "removeListener", "read", "pause", "resume", "isPaused", "destroy"]);
18
+ const STREAM_BOOL_PROPERTIES = new Set(["readable", "readableEnded", "writable", "writableEnded", "writableFinished", "writableNeedDrain", "destroyed", "closed", "readableObjectMode", "writableObjectMode", "allowHalfOpen"]);
19
+ const STREAM_NUM_PROPERTIES = new Set(["readableLength", "readableHighWaterMark", "writableLength", "writableHighWaterMark", "writableCorked"]);
20
+ const streamProperty = (name) => STREAM_BOOL_PROPERTIES.has(name) || STREAM_NUM_PROPERTIES.has(name);
21
+ export function classInstanceOf(lowerer, value, info, loc, classValue) {
22
+ const name = `%dyn.class.${classValue ? "valueInstanceof" : "instanceof"}:${info.def.name}`;
23
+ const params = [{ localId: "value", name: "value", type: DYN },
24
+ ...(classValue ? [{ localId: "class", name: "class", type: classValue.type }] : [])];
25
+ if (!lowerer.liftedFns.some((fn) => fn.name === name)) {
26
+ const candidate = varRef("candidate", DYN, loc);
27
+ const prototype = classPrototypeData(lowerer, info, loc, classValue ? varRef("class", classValue.type, loc) : undefined);
28
+ const body = prototype ? [
29
+ { kind: "varDecl", localId: "candidate", init: varRef("value", DYN, loc), loc },
30
+ { kind: "while", cond: { kind: "ternary", cond: { kind: "dynTest", test: "nullish", value: candidate, type: BOOL, loc },
31
+ then: { kind: "boolLit", value: false, type: BOOL, loc }, else_: { kind: "dynTest", test: "object", value: candidate, type: BOOL, loc }, type: BOOL, loc }, body: [
32
+ { kind: "assign", localId: "candidate", value: { kind: "libCall", fn: "dyn.getPrototype", args: [candidate], type: DYN, loc }, loc },
33
+ { kind: "if", cond: { kind: "dynScalarEq", left: candidate, right: prototype, type: BOOL, loc }, then: [
34
+ { kind: "return", value: { kind: "boolLit", value: true, type: BOOL, loc }, loc },
35
+ ], else_: null, loc },
36
+ ], loc },
37
+ ] : [];
38
+ body.push({ kind: "return", value: { kind: "boolLit", value: false, type: BOOL, loc }, loc });
18
39
  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,
40
+ name, params, returnType: BOOL,
41
+ locals: [...params.map((p) => ({ id: p.localId, name: p.name, type: p.type, mutable: false })),
42
+ ...(prototype ? [{ id: "candidate", name: "candidate", type: DYN, mutable: true }] : [])], body, loc,
22
43
  });
23
- return { kind: "call", callee: name, args: [value], type: BOOL, loc };
44
+ }
45
+ return { kind: "call", callee: name, args: [value, ...(classValue ? [classValue] : [])], type: BOOL, loc };
24
46
  }
25
47
  export function classPropertiesHelper(lowerer, loc) {
26
48
  const name = "%dyn.class.properties";
@@ -45,7 +67,9 @@ export class ClassDynamicDispatch {
45
67
  this.asyncFree = asyncFree;
46
68
  }
47
69
  generatorDispatch = new GeneratorDynamicDispatch();
70
+ constructionDispatch = new ClassConstructionDispatch();
48
71
  boxed = new Set();
72
+ boxedConstructors = new Set();
49
73
  dispatches = new Map();
50
74
  properties = new Map();
51
75
  computed = new Map();
@@ -55,10 +79,20 @@ export class ClassDynamicDispatch {
55
79
  bagInitializers = new Map();
56
80
  typedPropertyBags = new Map();
57
81
  generated = new Set();
82
+ constructDispatch = null;
83
+ constructors = new Map();
84
+ constructed = new Set();
58
85
  instanceTests = new Map();
86
+ jsonPrototypes = new Set();
87
+ descriptorHelper = null;
88
+ prototypeHelper = null;
89
+ prototypeClasses = new Set();
90
+ instancePrototypes = new Map();
59
91
  process(lowerer, functions) {
60
92
  let changed = refreshErrorMethodDispatch(lowerer, functions);
93
+ changed = refreshErrorPropertyDispatch(lowerer, functions) || changed;
61
94
  changed = this.generatorDispatch.process(lowerer, functions, this.generated) || changed;
95
+ changed = this.constructionDispatch.process(lowerer, functions) || changed;
62
96
  const seenTypes = new Set();
63
97
  const rewrite = new Set();
64
98
  const discover = (type) => {
@@ -66,8 +100,12 @@ export class ClassDynamicDispatch {
66
100
  if (seenTypes.has(key))
67
101
  return;
68
102
  seenTypes.add(key);
69
- if (isDynTypedRefType(type))
70
- this.boxed.add(type.className);
103
+ if (type.kind === "object") {
104
+ if (isDynTypedRefType(type))
105
+ this.boxed.add(type.className);
106
+ }
107
+ else if (type.kind === "classval")
108
+ this.boxedConstructors.add(type.className);
71
109
  else if (type.kind === "array")
72
110
  discover(type.elem);
73
111
  else if (type.kind === "record") {
@@ -100,8 +138,8 @@ export class ClassDynamicDispatch {
100
138
  rewrite.add(fn);
101
139
  break;
102
140
  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")
141
+ 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" ||
142
+ expr.fn === "dyn.hasKeyComputed" || expr.fn === "dyn.hasOwnComputed" || expr.fn === "dyn.propertyIsEnumerableComputed" || expr.fn === "dyn.defineProperty" || expr.fn === "dyn.getPrototype")
105
143
  rewrite.add(fn);
106
144
  break;
107
145
  }
@@ -111,9 +149,9 @@ export class ClassDynamicDispatch {
111
149
  if (this.boxed.size === 0 && rewrite.size === 0)
112
150
  return changed;
113
151
  for (const fn of functions) {
114
- if (!fn.name.startsWith("%dyn.class.instanceof:"))
152
+ if (!fn.name.startsWith("%dyn.class.instanceof:") && !fn.name.startsWith("%dyn.class.valueInstanceof:"))
115
153
  continue;
116
- const target = fn.name.slice("%dyn.class.instanceof:".length);
154
+ const target = fn.name.slice(fn.name.indexOf(":") + 1);
117
155
  let checked = this.instanceTests.get(fn.name);
118
156
  if (!checked) {
119
157
  checked = new Set();
@@ -129,8 +167,10 @@ export class ClassDynamicDispatch {
129
167
  const loc = fn.loc;
130
168
  const value = varRef("value", DYN, loc);
131
169
  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 };
170
+ const result = subtype && fn.params[1] && lowerer.classes.get(target)?.localClass
171
+ ? { kind: "instanceOfValue", value: { kind: "dynCheck", value, type, loc }, classValue: varRef("class", fn.params[1].type, loc), type: BOOL, loc }
172
+ : subtype ? { kind: "boolLit", value: true, type: BOOL, loc }
173
+ : { kind: "instanceOf", value: { kind: "dynCheck", value, type, loc }, className: target, type: BOOL, loc };
134
174
  fn.body.unshift({ kind: "if", cond: { kind: "libCall", fn: "dyn.typedRefIs", args: [value,
135
175
  { kind: "strLit", value: typeKey(type), type: STRING, loc }], type: BOOL, loc }, then: [{ kind: "return", value: result, loc }], else_: null, loc });
136
176
  changed = true;
@@ -145,33 +185,120 @@ export class ClassDynamicDispatch {
145
185
  const info = lowerer.classes.get(className);
146
186
  if (!info || info.builtinEmitter || info.builtinStream || info.builtinError)
147
187
  continue;
188
+ if (info.symbolFields && info.def.symbolFields === undefined) {
189
+ info.def.symbolFields = [...info.symbolFields].flatMap(([symbol, field]) => {
190
+ const global = lowerer.globalsBySymbol.get(symbol);
191
+ return global?.type.kind === "symbol" ? [{ field, globalId: global.id }] : [];
192
+ });
193
+ }
194
+ if (this.prototypeHelper && !this.prototypeClasses.has(className)) {
195
+ this.prototypeClasses.add(className);
196
+ const loc = this.prototypeHelper.loc;
197
+ const type = { kind: "object", className };
198
+ const value = varRef("value", DYN, loc);
199
+ const prototype = reflectClassPrototype(lowerer, info, loc, { kind: "dynCheck", value, type, loc });
200
+ if (prototype)
201
+ this.prototypeHelper.body.unshift({ kind: "if", cond: { kind: "libCall", fn: "dyn.typedRefIs", args: [value,
202
+ { kind: "strLit", value: typeKey(type), type: STRING, loc }], type: BOOL, loc },
203
+ then: [{ kind: "return", value: prototype, loc }], else_: null, loc });
204
+ changed = true;
205
+ }
206
+ if (!this.jsonPrototypes.has(className) && info.decl && findMethodOn(lowerer, info, "toJSON")) {
207
+ this.jsonPrototypes.add(className);
208
+ const loc = this.propertyBag.loc;
209
+ const method = classMethodValue(lowerer, info.decl, info, "toJSON", loc);
210
+ const prototype = method ? classPrototypeData(lowerer, info, loc) : null;
211
+ const helper = lowerer.liftedFns.find((fn) => fn.name === info.def.prototypeDataHelper);
212
+ const init = helper?.body[0];
213
+ if (!info.prototypeReflectionReady && method && prototype && init?.kind === "if" && canConvertToDyn(method.type, (id) => lowerer.shapes.get(id), (id) => lowerer.unions.get(id))) {
214
+ const descriptor = { kind: "exprStmt", expr: {
215
+ kind: "libCall", fn: "dyn.defineProperty", args: [prototype,
216
+ { kind: "dynFrom", value: { kind: "strLit", value: "toJSON", type: STRING, loc }, type: DYN, loc },
217
+ { kind: "dynObjLit", fields: [
218
+ { key: { kind: "strLit", value: "value", type: STRING, loc }, value: { kind: "dynFrom", value: method, type: DYN, loc } },
219
+ ...["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 } })),
220
+ ], type: DYN, loc },
221
+ ], type: DYN, loc
222
+ }, loc };
223
+ init.then.push(descriptor);
224
+ changed = true;
225
+ }
226
+ }
227
+ }
228
+ for (const plan of this.propertyReceivers(lowerer)) {
229
+ const info = plan.info;
230
+ const className = info.def.name;
231
+ let instancePrototype = this.instancePrototypes.get(plan.capsule.def.name);
232
+ if (!instancePrototype && hasClassPrototypeData(info)) {
233
+ const name = `%class.instancePrototype:${plan.capsule.def.name}`;
234
+ const type = { kind: "object", className: plan.capsule.def.name };
235
+ const loc = this.propertyBag.loc;
236
+ const fn = { name, params: [{ localId: "this", name: "this", type }],
237
+ locals: [{ id: "this", name: "this", type, mutable: false }], returnType: DYN,
238
+ body: [{ kind: "return", value: { kind: "libCall", fn: "dyn.getPrototype",
239
+ args: [{ kind: "dynObjLit", fields: [], type: DYN, loc }], type: DYN, loc }, loc }], loc };
240
+ plan.capsule.def.instancePrototypeHelper = name;
241
+ instancePrototype = { fn, classes: new Set() };
242
+ this.instancePrototypes.set(plan.capsule.def.name, instancePrototype);
243
+ this.generated.add(fn);
244
+ lowerer.liftedFns.push(fn);
245
+ }
246
+ if (instancePrototype && hasClassPrototypeData(info) && !instancePrototype.classes.has(className)) {
247
+ instancePrototype.classes.add(className);
248
+ const loc = instancePrototype.fn.loc;
249
+ const receiver = varRef("this", instancePrototype.fn.params[0].type, loc);
250
+ const prototype = classPrototypeData(lowerer, info, loc, info === plan.capsule ? receiver
251
+ : { kind: "downcast", value: receiver, type: { kind: "object", className }, loc });
252
+ const returned = { kind: "return", value: prototype ?? { kind: "libCall", fn: "dyn.getPrototype",
253
+ args: [{ kind: "dynObjLit", fields: [], type: DYN, loc }], type: DYN, loc }, loc };
254
+ if (info === plan.capsule)
255
+ instancePrototype.fn.body[instancePrototype.fn.body.length - 1] = returned;
256
+ else
257
+ instancePrototype.fn.body.unshift({ kind: "if",
258
+ cond: { kind: "instanceOf", value: receiver, className, type: BOOL, loc }, then: [returned], else_: null, loc });
259
+ changed = true;
260
+ }
261
+ const needsPrototype = (current) => hasClassPrototypeData(current) || current.subclasses.some(needsPrototype);
262
+ const prototypeFor = (current, receiver, loc) => {
263
+ const currentReceiver = receiver.type.kind === "object" && receiver.type.className !== current.def.name
264
+ ? { kind: "downcast", value: receiver, type: { kind: "object", className: current.def.name }, loc }
265
+ : receiver;
266
+ let result = classPrototypeData(lowerer, current, loc, currentReceiver);
267
+ for (const child of current.subclasses) {
268
+ const selected = prototypeFor(child, receiver, loc);
269
+ if (selected && result)
270
+ result = { kind: "ternary", cond: { kind: "instanceOf", value: receiver, className: child.def.name, type: BOOL, loc },
271
+ then: selected, else_: result, type: DYN, loc };
272
+ }
273
+ return result;
274
+ };
148
275
  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);
276
+ if (existingInit.value.kind === "dynObjLit" && needsPrototype(info)) {
277
+ const prototype = prototypeFor(info, existingInit.obj, existingInit.loc);
151
278
  if (prototype) {
152
279
  existingInit.value = { kind: "libCall", fn: "dyn.objCreate", args: [prototype], type: DYN, loc: existingInit.loc };
153
280
  changed = true;
154
281
  }
155
282
  }
156
283
  }
157
- if (this.bagClasses.has(className))
284
+ if (this.bagClasses.has(plan.key))
158
285
  continue;
159
- this.bagClasses.add(className);
286
+ this.bagClasses.add(plan.key);
160
287
  this.ensurePropertyBag(info);
161
288
  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 };
289
+ const { receiver, condition } = this.propertyReceiver(plan, loc);
165
290
  const bag = { kind: "fieldGet", obj: receiver, className, field: PROPERTY_BAG, type: DYN, loc };
166
- const prototype = hasClassPrototypeData(info) ? classPrototypeData(lowerer, info, loc, receiver) : null;
291
+ const prototype = needsPrototype(info) ? prototypeFor(info, receiver, loc) : null;
167
292
  const initialize = {
168
293
  kind: "fieldSet", obj: receiver, className, field: PROPERTY_BAG, value: prototype
169
294
  ? { kind: "libCall", fn: "dyn.objCreate", args: [prototype], type: DYN, loc }
170
295
  : { kind: "dynObjLit", fields: [], type: DYN, loc }, loc,
171
296
  };
172
- this.bagInitializers.set(className, [initialize]);
297
+ const initializers = this.bagInitializers.get(className) ?? [];
298
+ initializers.push(initialize);
299
+ this.bagInitializers.set(className, initializers);
173
300
  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 },
301
+ kind: "if", cond: condition,
175
302
  then: [
176
303
  { kind: "if", cond: { kind: "dynTest", test: "undefined", value: bag, type: BOOL, loc }, then: [
177
304
  initialize,
@@ -181,6 +308,7 @@ export class ClassDynamicDispatch {
181
308
  });
182
309
  changed = true;
183
310
  }
311
+ refreshDescriptorGuards(lowerer);
184
312
  const byMethod = new Map();
185
313
  const candidates = (method) => {
186
314
  const found = byMethod.get(method);
@@ -188,8 +316,10 @@ export class ClassDynamicDispatch {
188
316
  return found;
189
317
  const matching = [...this.boxed].flatMap((name) => {
190
318
  const info = lowerer.classes.get(name);
191
- if (!info || info.fields.has(method) || info.builtinEmitter || info.builtinStream || info.builtinError)
319
+ if (!info || info.fields.has(method) || info.builtinEmitter || info.builtinError)
192
320
  return [];
321
+ if (info.builtinStream)
322
+ return STREAM_METHODS.has(method) ? [info] : [];
193
323
  return findMethodOn(lowerer, info, method) || findGenericMethodOn(lowerer, info, method) ? [info] : [];
194
324
  });
195
325
  byMethod.set(method, matching);
@@ -204,6 +334,18 @@ export class ClassDynamicDispatch {
204
334
  fn.body = transformStmtList(fn.body, {
205
335
  stmt: (stmt) => stmt,
206
336
  expr: (expr) => {
337
+ if (expr.kind === "libCall" && expr.fn === "dyn.getPrototype") {
338
+ if (!this.prototypeHelper) {
339
+ const loc = expr.loc;
340
+ this.prototypeHelper = { name: "%dyn.class.getPrototype", params: [{ localId: "value", name: "value", type: DYN }],
341
+ locals: [{ id: "value", name: "value", type: DYN, mutable: false }], returnType: DYN, loc,
342
+ body: [{ kind: "return", value: { ...expr, args: [varRef("value", DYN, loc)] }, loc }] };
343
+ lowerer.liftedFns.push(this.prototypeHelper);
344
+ this.generated.add(this.prototypeHelper);
345
+ changed = true;
346
+ }
347
+ return { kind: "call", callee: this.prototypeHelper.name, args: expr.args, type: DYN, loc: expr.loc };
348
+ }
207
349
  if (expr.kind === "libCall" && (expr.fn === "dyn.toString" || expr.fn === "dyn.stringConstructor") && lowerer.classes.has("%Error")) {
208
350
  const key = JSON.stringify([expr.fn, ...expr.args.map((arg) => typeKey(arg.type))]);
209
351
  let errorString = this.errorStrings.get(key);
@@ -239,6 +381,43 @@ export class ClassDynamicDispatch {
239
381
  }
240
382
  }
241
383
  }
384
+ if (expr.kind === "libCall" && expr.fn === "bytes.construct") {
385
+ if (!this.constructDispatch) {
386
+ const loc = expr.loc;
387
+ const params = [DYN, DYN, STRING].map((type, i) => ({ localId: `p.${i}`, name: `p${i}`, type }));
388
+ this.constructDispatch = { name: "%dyn.class.construct", params, returnType: DYN,
389
+ locals: params.map((p) => ({ id: p.localId, name: p.name, type: p.type, mutable: false })),
390
+ body: [{ kind: "return", value: { ...expr, args: params.map((p) => varRef(p.localId, p.type, loc)) }, loc }], loc };
391
+ this.generated.add(this.constructDispatch);
392
+ lowerer.liftedFns.push(this.constructDispatch);
393
+ changed = true;
394
+ }
395
+ return { kind: "call", callee: this.constructDispatch.name, args: expr.args, type: DYN, loc: expr.loc };
396
+ }
397
+ if (expr.kind === "libCall" && expr.fn === "dyn.defineProperty") {
398
+ const loc = expr.loc;
399
+ if (!this.descriptorHelper) {
400
+ const params = ["target", "key", "descriptor"].map((name) => ({ localId: name, name, type: DYN }));
401
+ const target = varRef("target", DYN, loc);
402
+ const key = varRef("key", DYN, loc);
403
+ const descriptor = varRef("descriptor", DYN, loc);
404
+ this.descriptorHelper = {
405
+ name: "%dyn.class.defineProperty", params, returnType: DYN,
406
+ locals: params.map((param) => ({ id: param.localId, name: param.name, type: DYN, mutable: false })),
407
+ body: [
408
+ { kind: "if", cond: { kind: "dynTest", test: "symbol", value: key, type: BOOL, loc }, then: [
409
+ { kind: "exprStmt", expr: { ...expr, args: [{ kind: "call", callee: this.propertyBag.name, args: [target], type: DYN, loc }, key, descriptor] }, loc },
410
+ { kind: "return", value: target, loc },
411
+ ], else_: null, loc },
412
+ { kind: "return", value: { ...expr, args: [target, key, descriptor] }, loc },
413
+ ], loc,
414
+ };
415
+ this.generated.add(this.descriptorHelper);
416
+ lowerer.liftedFns.push(this.descriptorHelper);
417
+ changed = true;
418
+ }
419
+ return { kind: "call", callee: this.descriptorHelper.name, args: expr.args, type: DYN, loc };
420
+ }
242
421
  const computedRead = expr.kind === "dynKeyGet" && expr.key.kind !== "strLit" ? expr : null;
243
422
  const computedWrite = expr.kind === "libCall" && (expr.fn === "dyn.keySetComputed" || expr.fn === "dyn.keySet" && expr.args[1]?.kind !== "strLit") ? expr : null;
244
423
  const computedProbe = expr.kind === "libCall" && ["dyn.hasKeyComputed", "dyn.hasOwnComputed", "dyn.propertyIsEnumerableComputed"].includes(expr.fn) ? expr : null;
@@ -385,11 +564,16 @@ export class ClassDynamicDispatch {
385
564
  const type = { kind: "object", className: info.def.name };
386
565
  const receiver = varRef("p.0", DYN, loc);
387
566
  if (isClassCallback(lowerer, info, dispatch.source.method)) {
567
+ const prototypeMethod = lowerer.prototypeMethodAccesses.has(dispatch.source.method);
568
+ if (prototypeMethod)
569
+ classPrototypeData(lowerer, info, loc);
388
570
  const bag = { kind: "call", callee: this.propertyBag.name, args: [receiver], type: DYN, loc };
389
571
  dispatch.callback.body.unshift({ kind: "if", cond: {
390
572
  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 });
573
+ }, then: [{ kind: "return", value: { kind: "ternary", cond: prototypeMethod ? { kind: "boolLit", value: true, type: BOOL, loc } : { kind: "libCall", fn: "dyn.hasKey", args: [bag,
574
+ { kind: "strLit", value: dispatch.source.method, type: STRING, loc }], type: BOOL, loc }, then: { kind: "dynObjLit", fields: [
575
+ { 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 } },
576
+ ], type: DYN, loc }, else_: dynUndefinedExpr(loc), type: DYN, loc }, loc }], else_: null, loc });
393
577
  }
394
578
  const before = lowerer.diags.length;
395
579
  let branch;
@@ -413,11 +597,27 @@ export class ClassDynamicDispatch {
413
597
  }
414
598
  }
415
599
  for (const dispatch of this.properties.values()) {
416
- for (const plan of this.propertyReceivers(lowerer)) {
600
+ if (!dispatch.write)
601
+ for (const name of this.boxedConstructors) {
602
+ const info = lowerer.classes.get(name);
603
+ const field = info && findStaticOn(lowerer, info, dispatch.name)?.field;
604
+ const key = `constructor:${name}`;
605
+ if (!field || dispatch.name.startsWith("#") || dispatch.classes.has(key))
606
+ continue;
607
+ dispatch.classes.add(key);
608
+ const loc = dispatch.fn.loc;
609
+ const value = varRef("p.0", DYN, loc);
610
+ const type = { kind: "classval", className: name };
611
+ dispatch.fn.body.unshift({ kind: "if", cond: { kind: "libCall", fn: "dyn.classIs", args: [value,
612
+ { kind: "strLit", value: typeKey(type), type: STRING, loc }], type: BOOL, loc }, then: [
613
+ { kind: "return", value: lowerer.coerceToExpected(varRef(field.globalId, field.type, loc), DYN), loc },
614
+ ], else_: null, loc });
615
+ changed = true;
616
+ }
617
+ for (const plan of this.propertyReceivers(lowerer, true)) {
417
618
  const { info } = plan;
418
619
  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))
620
+ !(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
621
  continue;
422
622
  if (dispatch.classes.has(plan.key))
423
623
  continue;
@@ -444,16 +644,37 @@ export class ClassDynamicDispatch {
444
644
  }
445
645
  }
446
646
  for (const dispatch of this.computed.values()) {
447
- for (const plan of this.propertyReceivers(lowerer)) {
647
+ for (const plan of this.propertyReceivers(lowerer, true)) {
448
648
  if (dispatch.classes.has(plan.key))
449
649
  continue;
450
650
  const { info } = plan;
451
651
  dispatch.classes.add(plan.key);
452
652
  const loc = dispatch.fn.loc;
453
653
  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);
654
+ const memberBody = (name) => {
655
+ if (dispatch.probe && (name === "message" || name === "name") && lowerer.isSubclassOf(info.def.name, "%Error") && dispatch.probe !== "in")
656
+ return [{
657
+ kind: "return", value: dispatch.probe === "own"
658
+ ? { kind: "fieldGet", obj: lowerer.upcastTo(receiver, "%Error"), className: "%Error", field: `%${name}Present`, type: BOOL, loc }
659
+ : { kind: "fieldGet", obj: lowerer.upcastTo(receiver, "%Error"), className: "%Error", field: `%${name}Enumerable`, type: BOOL, loc }, loc,
660
+ }];
661
+ if (info.builtinStream !== undefined && streamProperty(name) && dispatch.probe)
662
+ return [{ kind: "return", value: { kind: "boolLit", value: dispatch.probe === "in", type: BOOL, loc }, loc }];
663
+ if (!dispatch.probe)
664
+ return this.propertyBody(lowerer, { ...dispatch, name }, info, receiver);
665
+ const prototypeMethod = lowerer.prototypeMethodAccesses.has(name);
666
+ if (info.fields.has(name) || dispatch.probe === "in" && !prototypeMethod) {
667
+ return [{ kind: "return", value: { kind: "boolLit", value: true, type: BOOL, loc }, loc }];
668
+ }
669
+ if (prototypeMethod)
670
+ classPrototypeData(lowerer, info, loc, receiver);
671
+ const bag = { kind: "call", callee: this.propertyBag.name,
672
+ args: [lowerer.coerceToExpected(receiver, DYN)], type: DYN, loc };
673
+ const symbol = name.startsWith("sym:") || name.startsWith("%symbol:");
674
+ const fn = dispatch.probe === "in" ? "dyn.hasKey" : dispatch.probe === "own" ? "dyn.hasOwn" : "dyn.propertyIsEnumerable";
675
+ return [{ kind: "return", value: { kind: "libCall", fn: symbol ? `${fn}Computed` : fn,
676
+ args: [bag, symbol ? varRef("p.key", DYN, loc) : { kind: "strLit", value: name, type: STRING, loc }], type: BOOL, loc }, loc }];
677
+ };
457
678
  if (dispatch.dynamicKey) {
458
679
  const symbols = new Map();
459
680
  const symbolEntries = [];
@@ -480,6 +701,16 @@ export class ClassDynamicDispatch {
480
701
  }
481
702
  }
482
703
  const branches = [];
704
+ if (!dispatch.write) {
705
+ const value = varRef("p.0", DYN, loc);
706
+ const key = varRef("p.key", DYN, loc);
707
+ const bag = { kind: "call", callee: this.propertyBag.name, args: [value], type: DYN, loc };
708
+ branches.push({ kind: "if", cond: { kind: "libCall", fn: "dyn.hasOwnComputed", args: [bag, key], type: BOOL, loc }, then: [
709
+ { kind: "return", value: dispatch.probe ? {
710
+ kind: "libCall", fn: dispatch.probe === "enumerable" ? "dyn.propertyIsEnumerableComputed" : "dyn.hasOwnComputed", args: [bag, key], type: BOOL, loc,
711
+ } : { kind: "dynKeyGet", value: bag, key, type: DYN, loc }, loc },
712
+ ], else_: null, loc });
713
+ }
483
714
  for (const [name, symbol] of symbols) {
484
715
  const before = lowerer.diags.length;
485
716
  let then;
@@ -504,6 +735,9 @@ export class ClassDynamicDispatch {
504
735
  }
505
736
  }
506
737
  const names = new Set([...info.fields.keys()].filter(isClassOwnEnumerableFieldName));
738
+ if (info.builtinStream)
739
+ for (const name of [...STREAM_BOOL_PROPERTIES, ...STREAM_NUM_PROPERTIES])
740
+ names.add(name);
507
741
  for (let owner = info; owner; owner = owner.base) {
508
742
  const methodNames = [...owner.methods.keys()];
509
743
  if (owner.genericMethods)
@@ -533,16 +767,71 @@ export class ClassDynamicDispatch {
533
767
  kind: "strEq", left: varRef(dispatch.keyLocal, STRING, loc), right: { kind: "strLit", value: name, type: STRING, loc }, negated: false, type: BOOL, loc,
534
768
  }, then: body, else_: null, loc });
535
769
  }
536
- dispatch.fn.body.splice(dispatch.branchIndex, 0, { kind: "if", cond: condition, then: branch, else_: null, loc });
770
+ // Keep each class's key table in a separate function. A single
771
+ // function containing every boxed class grows quadratically during
772
+ // LLVM's control-flow optimization for large library workloads.
773
+ const params = [...dispatch.fn.params];
774
+ if (dispatch.keyLocal !== "p.key")
775
+ params.push({ localId: dispatch.keyLocal, name: "key", type: STRING });
776
+ const helper = {
777
+ name: `%dyn.class.keys.${lowerer.liftedFns.length}`, params, returnType: dispatch.fn.returnType,
778
+ locals: params.map((p) => ({ id: p.localId, name: p.name, type: p.type, mutable: false })),
779
+ body: [...branch, ...dispatch.fn.body.slice(dispatch.write ? -2 : -1)], loc,
780
+ };
781
+ this.generated.add(helper);
782
+ lowerer.liftedFns.push(helper);
783
+ const call = { kind: "call", callee: helper.name,
784
+ args: params.map((p) => varRef(p.localId, p.type, loc)), type: helper.returnType, loc };
785
+ const then = dispatch.write
786
+ ? [{ kind: "exprStmt", expr: call, loc }, { kind: "return", value: null, loc }]
787
+ : [{ kind: "return", value: call, loc }];
788
+ dispatch.fn.body.splice(dispatch.branchIndex, 0, { kind: "if", cond: condition, then, else_: null, loc });
537
789
  changed = true;
538
790
  }
539
791
  }
792
+ if (this.constructDispatch)
793
+ for (const [name, info] of this.constructors) {
794
+ if (this.constructed.has(name))
795
+ continue;
796
+ this.constructed.add(name);
797
+ const loc = this.constructDispatch.loc;
798
+ const callee = varRef("p.0", DYN, loc), input = varRef("p.1", DYN, loc);
799
+ const args = [];
800
+ let supported = true;
801
+ for (let i = 0; i < info.ctorParams.length; i++) {
802
+ const param = info.ctorParams[i];
803
+ if (param.mode !== "required" && param.mode !== "omittable") {
804
+ supported = false;
805
+ break;
806
+ }
807
+ const value = { kind: "dynKeyGet", value: input, key: { kind: "strLit", value: String(i), type: STRING, loc }, type: DYN, loc };
808
+ const arg = lowerer.coerceToExpected(value, param.type);
809
+ if (!typeEquals(arg.type, param.type)) {
810
+ supported = false;
811
+ break;
812
+ }
813
+ args.push(arg);
814
+ }
815
+ if (!supported || !info.decl)
816
+ continue;
817
+ const cls = classValueRef(lowerer, info, info.decl);
818
+ const result = { kind: "new", className: name, args, type: { kind: "object", className: name }, loc };
819
+ this.constructDispatch.body.unshift({ kind: "if", cond: { kind: "dynScalarEq", left: callee,
820
+ right: lowerer.coerceToExpected(cls, DYN), type: BOOL, loc }, then: [
821
+ { kind: "return", value: lowerer.coerceToExpected(result, DYN), loc },
822
+ ], else_: null, loc });
823
+ changed = true;
824
+ }
540
825
  return changed;
541
826
  }
542
827
  /** A known native class can access its shared bag directly. Keep the
543
828
  * receiver as an owned parameter so calls and temporary instances retain
544
829
  * their ordinary evaluation and lifetime rules without a boxed capsule. */
545
830
  typedPropertyBag(lowerer, type, loc) {
831
+ // A superclass constructor can be the first code to access this bag.
832
+ // Its receiver still needs the actual subclass's prototype.
833
+ if (lowerer.classes.get(type.className)?.subclasses.length)
834
+ return null;
546
835
  const existing = this.typedPropertyBags.get(type.className);
547
836
  if (existing)
548
837
  return existing;
@@ -551,11 +840,8 @@ export class ClassDynamicDispatch {
551
840
  return null;
552
841
  const receiver = varRef("p.0", type, loc);
553
842
  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 };
843
+ const initialize = { ...initializers[0], obj: receiver,
844
+ 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
845
  const helper = {
560
846
  name: `%class.properties:${type.className}`, params: [{ localId: "p.0", name: "value", type }], returnType: DYN,
561
847
  locals: [{ id: "p.0", name: "value", type, mutable: false }], loc,
@@ -592,11 +878,11 @@ export class ClassDynamicDispatch {
592
878
  }
593
879
  /** A capsule retains its static type, while its object may be a subclass.
594
880
  * Preorder insertion lets later branches test the most derived layout first. */
595
- propertyReceivers(lowerer) {
881
+ propertyReceivers(lowerer, streams = false) {
596
882
  const plans = [];
597
883
  for (const name of this.boxed) {
598
884
  const capsule = lowerer.classes.get(name);
599
- if (!capsule || capsule.builtinEmitter || capsule.builtinStream || capsule.builtinError)
885
+ if (!capsule || capsule.builtinEmitter || capsule.builtinStream && !streams || capsule.builtinError)
600
886
  continue;
601
887
  const visit = (info) => {
602
888
  plans.push({ info, capsule, key: JSON.stringify([name, info.def.name]) });
@@ -624,6 +910,15 @@ export class ClassDynamicDispatch {
624
910
  propertyBody(lowerer, dispatch, info, receiver) {
625
911
  const { name, write } = dispatch;
626
912
  const loc = dispatch.fn.loc;
913
+ if (info.builtinStream !== undefined && streamProperty(name)) {
914
+ if (write)
915
+ return [{ kind: "runtimeFence", code: "SC2020", message: `writing stream property '${name}' through an untyped value is not supported`, loc }];
916
+ 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 }];
917
+ }
918
+ if ((name === "message" || name === "name") && lowerer.isSubclassOf(info.def.name, "%Error"))
919
+ return write
920
+ ? [errorPropertyWrite(lowerer, receiver, lowerer.coerceToExpected(varRef("p.1", DYN, loc), STRING), name), { kind: "return", value: null, loc }]
921
+ : [{ kind: "return", value: lowerer.coerceToExpected(errorPropertyRead(lowerer, receiver, name), DYN), loc }];
627
922
  const field = info.fields.get(name);
628
923
  const member = `${write ? "set" : "get"}:${name}`;
629
924
  const accessor = field ? null : findMethodOn(lowerer, info, member);
@@ -631,6 +926,14 @@ export class ClassDynamicDispatch {
631
926
  const getRecord = (id) => lowerer.shapes.get(id);
632
927
  const getUnion = (id) => lowerer.unions.get(id);
633
928
  if (!write) {
929
+ if (name === "constructor" && !field && !accessor && info.decl && !info.generic && !info.localClass && !info.classDecorators) {
930
+ this.constructors.set(info.def.name, info);
931
+ const cls = lowerer.coerceToExpected(classValueRef(lowerer, info, info.decl), DYN);
932
+ const bag = { kind: "call", callee: this.propertyBag.name, args: [lowerer.coerceToExpected(receiver, DYN)], type: DYN, loc };
933
+ const key = { kind: "strLit", value: name, type: STRING, loc };
934
+ return [{ kind: "return", value: { kind: "ternary", cond: { kind: "libCall", fn: "dyn.hasKey", args: [bag, key], type: BOOL, loc },
935
+ then: { kind: "dynKeyGet", value: bag, key, type: DYN, loc }, else_: cls, type: DYN, loc }, loc }];
936
+ }
634
937
  if (name === "toString" && findMethodOn(lowerer, info, name)?.declarer.builtinError) {
635
938
  return [{ kind: "return", value: errorToStringMethod(lowerer, receiver), loc }];
636
939
  }
@@ -659,6 +962,20 @@ export class ClassDynamicDispatch {
659
962
  (candidate.methods.has(name) || candidate.genericMethods?.has(name)));
660
963
  overrides.sort((a, b) => lowerer.isSubclassOf(a.def.name, b.def.name) ? -1 : lowerer.isSubclassOf(b.def.name, a.def.name) ? 1 : 0);
661
964
  const body = [];
965
+ if (isClassOwnEnumerableFieldName(name) && !name.startsWith("sym:")) {
966
+ const prototypeMethod = lowerer.prototypeMethodAccesses.has(name);
967
+ if (prototypeMethod)
968
+ classPrototypeData(lowerer, info, loc, receiver);
969
+ const bag = { kind: "call", callee: this.propertyBag.name,
970
+ args: [lowerer.coerceToExpected(receiver, DYN)], type: DYN, loc };
971
+ const key = { kind: "strLit", value: name, type: STRING, loc };
972
+ body.push({ kind: "if", cond: { kind: "libCall", fn: "dyn.hasKey", args: [bag, key], type: BOOL, loc },
973
+ then: [{ kind: "return", value: { kind: "dynKeyGet", value: bag, key, type: DYN, loc }, loc }], else_: null, loc });
974
+ if (prototypeMethod) {
975
+ body.push({ kind: "return", value: dynUndefinedExpr(loc), loc });
976
+ return body;
977
+ }
978
+ }
662
979
  for (const candidate of overrides) {
663
980
  const selected = classMethodValue(lowerer, info.decl, candidate, name, loc);
664
981
  const branch = selected && canConvertToDyn(selected.type, getRecord, getUnion)
@@ -683,8 +1000,23 @@ export class ClassDynamicDispatch {
683
1000
  (t.kind === "union" && (getUnion(t.unionId)?.arms.some(mutable) ?? false));
684
1001
  if (boxed.kind === "dynFrom" && mutable(boxed.value.type))
685
1002
  boxed.liveRef = true;
1003
+ if (accessor) {
1004
+ const bag = { kind: "call", callee: this.propertyBag.name,
1005
+ args: [lowerer.coerceToExpected(receiver, DYN)], type: DYN, loc };
1006
+ const key = { kind: "strLit", value: name, type: STRING, loc };
1007
+ return [{ kind: "if", cond: { kind: "libCall", fn: "dyn.hasOwn", args: [bag, key], type: BOOL, loc },
1008
+ then: [{ kind: "return", value: { kind: "dynKeyGet", value: bag, key, type: DYN, loc }, loc }], else_: null, loc },
1009
+ { kind: "return", value: boxed, loc }];
1010
+ }
686
1011
  return [{ kind: "return", value: boxed, loc }];
687
1012
  }
1013
+ if (!field && !accessor && isClassOwnEnumerableFieldName(name) && !name.startsWith("sym:") && !findMethodOn(lowerer, info, `get:${name}`)) {
1014
+ const bag = { kind: "call", callee: this.propertyBag.name,
1015
+ args: [lowerer.coerceToExpected(receiver, DYN)], type: DYN, loc };
1016
+ return [{ kind: "exprStmt", expr: { kind: "libCall", fn: "dyn.keySet", args: [bag,
1017
+ { kind: "strLit", value: name, type: STRING, loc }, varRef("p.1", DYN, loc)], type: VOID, loc }, loc },
1018
+ { kind: "return", value: null, loc }];
1019
+ }
688
1020
  if (!field && !accessor)
689
1021
  return [{ kind: "throw", value: {
690
1022
  kind: "libCall", fn: "error.new", args: [{
@@ -707,6 +1039,29 @@ export class ClassDynamicDispatch {
707
1039
  methodBody(lowerer, dispatch, info, receiver) {
708
1040
  const { method, loc } = dispatch.source;
709
1041
  const fence = () => [{ kind: "runtimeFence", code: "SC2020", message: `calling '${method}' on this native class through an untyped value is not supported yet`, loc }];
1042
+ if (info.builtinStream) {
1043
+ const incoming = dispatch.source.args.map((_, i) => varRef(`p.${i + 1}`, DYN, loc));
1044
+ const registering = ["on", "addListener", "once", "prependListener", "prependOnceListener"].includes(method);
1045
+ const removing = method === "off" || method === "removeListener";
1046
+ const ret = (value) => [{ kind: "return", value: lowerer.coerceToExpected(value, DYN), loc }];
1047
+ if (registering || removing) {
1048
+ if (incoming.length < 2)
1049
+ return fence();
1050
+ const event = lowerer.coerceToExpected(incoming[0], STRING);
1051
+ const cb = incoming[1];
1052
+ return [{ kind: "exprStmt", expr: { kind: "libCall", fn: "emitter.checkListener", args: [cb], type: VOID, loc }, loc }, ...ret({ kind: "libCall", fn: registering ? "stream.onDyn" : "emitter.offDyn", args: [receiver, event, cb, ...(registering ? [
1053
+ { kind: "boolLit", value: method === "once" || method === "prependOnceListener", type: BOOL, loc },
1054
+ { kind: "boolLit", value: method.startsWith("prepend"), type: BOOL, loc },
1055
+ ] : [])], type: receiver.type, loc })];
1056
+ }
1057
+ if (method === "read")
1058
+ return ret({ kind: "libCall", fn: "readable.readDyn", args: [receiver, incoming[0] ?? dynUndefinedExpr(loc)], type: DYN, loc });
1059
+ if (method === "pause" || method === "resume" || method === "isPaused")
1060
+ return ret({ kind: "libCall", fn: method === "pause" ? "readable.pause" : method === "resume" ? "readable.resume" : "readable.isPaused", args: [receiver], type: method === "isPaused" ? BOOL : receiver.type, loc });
1061
+ if (method === "destroy" && (!incoming.length || incoming[0]?.kind === "unitLit"))
1062
+ return ret({ kind: "libCall", fn: "stream.destroy", args: [receiver], type: receiver.type, loc });
1063
+ return fence();
1064
+ }
710
1065
  const methodInfo = findMethodOn(lowerer, info, method);
711
1066
  if (this.asyncFree && (methodInfo?.sig.gen || methodInfo?.sig.async)) {
712
1067
  return [{ kind: "runtimeFence", code: "SC4005", message: `calling async or generator method '${method}' through an untyped library value is not supported`, loc }];