@scriptc/compiler 0.0.0 → 0.0.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 (206) hide show
  1. package/LICENSE +202 -0
  2. package/ambient/package.json +3 -0
  3. package/ambient/scriptc-node-fallback.d.ts +3001 -0
  4. package/ambient/scriptc-overrides.d.ts +95 -0
  5. package/ambient/scriptc.d.ts +74 -0
  6. package/dist/backend/cc.d.ts +169 -0
  7. package/dist/backend/cc.js +897 -0
  8. package/dist/backend/cc.js.map +1 -0
  9. package/dist/backend/emission/emit-async.d.ts +145 -0
  10. package/dist/backend/emission/emit-async.js +996 -0
  11. package/dist/backend/emission/emit-async.js.map +1 -0
  12. package/dist/backend/emission/emit-exprs.d.ts +3 -0
  13. package/dist/backend/emission/emit-exprs.js +5947 -0
  14. package/dist/backend/emission/emit-exprs.js.map +1 -0
  15. package/dist/backend/emission/emit-island.d.ts +45 -0
  16. package/dist/backend/emission/emit-island.js +271 -0
  17. package/dist/backend/emission/emit-island.js.map +1 -0
  18. package/dist/backend/emission/emit-shapes.d.ts +142 -0
  19. package/dist/backend/emission/emit-shapes.js +575 -0
  20. package/dist/backend/emission/emit-shapes.js.map +1 -0
  21. package/dist/backend/emission/emit-stmts.d.ts +78 -0
  22. package/dist/backend/emission/emit-stmts.js +960 -0
  23. package/dist/backend/emission/emit-stmts.js.map +1 -0
  24. package/dist/backend/emission/emit-types.d.ts +65 -0
  25. package/dist/backend/emission/emit-types.js +652 -0
  26. package/dist/backend/emission/emit-types.js.map +1 -0
  27. package/dist/backend/emission/emit-walkers.d.ts +154 -0
  28. package/dist/backend/emission/emit-walkers.js +1587 -0
  29. package/dist/backend/emission/emit-walkers.js.map +1 -0
  30. package/dist/backend/emission/emitter.d.ts +452 -0
  31. package/dist/backend/emission/emitter.js +1249 -0
  32. package/dist/backend/emission/emitter.js.map +1 -0
  33. package/dist/backend/emission/may-throw.d.ts +15 -0
  34. package/dist/backend/emission/may-throw.js +250 -0
  35. package/dist/backend/emission/may-throw.js.map +1 -0
  36. package/dist/backend/llvm/blocks.d.ts +21 -0
  37. package/dist/backend/llvm/blocks.js +68 -0
  38. package/dist/backend/llvm/blocks.js.map +1 -0
  39. package/dist/backend/llvm/classes.d.ts +84 -0
  40. package/dist/backend/llvm/classes.js +438 -0
  41. package/dist/backend/llvm/classes.js.map +1 -0
  42. package/dist/backend/llvm/dyn.d.ts +123 -0
  43. package/dist/backend/llvm/dyn.js +2624 -0
  44. package/dist/backend/llvm/dyn.js.map +1 -0
  45. package/dist/backend/llvm/emitter.d.ts +3 -0
  46. package/dist/backend/llvm/emitter.js +10087 -0
  47. package/dist/backend/llvm/emitter.js.map +1 -0
  48. package/dist/backend/llvm/shapes.d.ts +78 -0
  49. package/dist/backend/llvm/shapes.js +754 -0
  50. package/dist/backend/llvm/shapes.js.map +1 -0
  51. package/dist/backend/llvm/unsupported.d.ts +6 -0
  52. package/dist/backend/llvm/unsupported.js +12 -0
  53. package/dist/backend/llvm/unsupported.js.map +1 -0
  54. package/dist/backend/llvm/walkers.d.ts +58 -0
  55. package/dist/backend/llvm/walkers.js +793 -0
  56. package/dist/backend/llvm/walkers.js.map +1 -0
  57. package/dist/backend/mangle.d.ts +124 -0
  58. package/dist/backend/mangle.js +232 -0
  59. package/dist/backend/mangle.js.map +1 -0
  60. package/dist/coverage/report.d.ts +64 -0
  61. package/dist/coverage/report.js +226 -0
  62. package/dist/coverage/report.js.map +1 -0
  63. package/dist/diagnostics/diagnostic.d.ts +140 -0
  64. package/dist/diagnostics/diagnostic.js +458 -0
  65. package/dist/diagnostics/diagnostic.js.map +1 -0
  66. package/dist/diagnostics/render.d.ts +11 -0
  67. package/dist/diagnostics/render.js +58 -0
  68. package/dist/diagnostics/render.js.map +1 -0
  69. package/dist/frontend/cjs-lexer.d.ts +20 -0
  70. package/dist/frontend/cjs-lexer.js +813 -0
  71. package/dist/frontend/cjs-lexer.js.map +1 -0
  72. package/dist/frontend/lowering/http2-constants.d.ts +1 -0
  73. package/dist/frontend/lowering/http2-constants.js +251 -0
  74. package/dist/frontend/lowering/http2-constants.js.map +1 -0
  75. package/dist/frontend/lowering/lib-boundary.d.ts +6 -0
  76. package/dist/frontend/lowering/lib-boundary.js +143 -0
  77. package/dist/frontend/lowering/lib-boundary.js.map +1 -0
  78. package/dist/frontend/lowering/lower-assert.d.ts +18 -0
  79. package/dist/frontend/lowering/lower-assert.js +1269 -0
  80. package/dist/frontend/lowering/lower-assert.js.map +1 -0
  81. package/dist/frontend/lowering/lower-builtins.d.ts +511 -0
  82. package/dist/frontend/lowering/lower-builtins.js +5249 -0
  83. package/dist/frontend/lowering/lower-builtins.js.map +1 -0
  84. package/dist/frontend/lowering/lower-calls.d.ts +581 -0
  85. package/dist/frontend/lowering/lower-calls.js +6281 -0
  86. package/dist/frontend/lowering/lower-calls.js.map +1 -0
  87. package/dist/frontend/lowering/lower-classes.d.ts +727 -0
  88. package/dist/frontend/lowering/lower-classes.js +4701 -0
  89. package/dist/frontend/lowering/lower-classes.js.map +1 -0
  90. package/dist/frontend/lowering/lower-comptime.d.ts +53 -0
  91. package/dist/frontend/lowering/lower-comptime.js +244 -0
  92. package/dist/frontend/lowering/lower-comptime.js.map +1 -0
  93. package/dist/frontend/lowering/lower-containers.d.ts +483 -0
  94. package/dist/frontend/lowering/lower-containers.js +6281 -0
  95. package/dist/frontend/lowering/lower-containers.js.map +1 -0
  96. package/dist/frontend/lowering/lower-dgram.d.ts +20 -0
  97. package/dist/frontend/lowering/lower-dgram.js +319 -0
  98. package/dist/frontend/lowering/lower-dgram.js.map +1 -0
  99. package/dist/frontend/lowering/lower-emitter.d.ts +10 -0
  100. package/dist/frontend/lowering/lower-emitter.js +688 -0
  101. package/dist/frontend/lowering/lower-emitter.js.map +1 -0
  102. package/dist/frontend/lowering/lower-enums.d.ts +22 -0
  103. package/dist/frontend/lowering/lower-enums.js +235 -0
  104. package/dist/frontend/lowering/lower-enums.js.map +1 -0
  105. package/dist/frontend/lowering/lower-expando.d.ts +35 -0
  106. package/dist/frontend/lowering/lower-expando.js +276 -0
  107. package/dist/frontend/lowering/lower-expando.js.map +1 -0
  108. package/dist/frontend/lowering/lower-exprs.d.ts +524 -0
  109. package/dist/frontend/lowering/lower-exprs.js +8099 -0
  110. package/dist/frontend/lowering/lower-exprs.js.map +1 -0
  111. package/dist/frontend/lowering/lower-generators.d.ts +50 -0
  112. package/dist/frontend/lowering/lower-generators.js +421 -0
  113. package/dist/frontend/lowering/lower-generators.js.map +1 -0
  114. package/dist/frontend/lowering/lower-inspect.d.ts +20 -0
  115. package/dist/frontend/lowering/lower-inspect.js +1069 -0
  116. package/dist/frontend/lowering/lower-inspect.js.map +1 -0
  117. package/dist/frontend/lowering/lower-island.d.ts +108 -0
  118. package/dist/frontend/lowering/lower-island.js +650 -0
  119. package/dist/frontend/lowering/lower-island.js.map +1 -0
  120. package/dist/frontend/lowering/lower-mixins.d.ts +96 -0
  121. package/dist/frontend/lowering/lower-mixins.js +534 -0
  122. package/dist/frontend/lowering/lower-mixins.js.map +1 -0
  123. package/dist/frontend/lowering/lower-modules.d.ts +135 -0
  124. package/dist/frontend/lowering/lower-modules.js +1613 -0
  125. package/dist/frontend/lowering/lower-modules.js.map +1 -0
  126. package/dist/frontend/lowering/lower-namespaces.d.ts +168 -0
  127. package/dist/frontend/lowering/lower-namespaces.js +706 -0
  128. package/dist/frontend/lowering/lower-namespaces.js.map +1 -0
  129. package/dist/frontend/lowering/lower-server.d.ts +90 -0
  130. package/dist/frontend/lowering/lower-server.js +3471 -0
  131. package/dist/frontend/lowering/lower-server.js.map +1 -0
  132. package/dist/frontend/lowering/lower-stmts.d.ts +361 -0
  133. package/dist/frontend/lowering/lower-stmts.js +5341 -0
  134. package/dist/frontend/lowering/lower-stmts.js.map +1 -0
  135. package/dist/frontend/lowering/lower-stream.d.ts +88 -0
  136. package/dist/frontend/lowering/lower-stream.js +1533 -0
  137. package/dist/frontend/lowering/lower-stream.js.map +1 -0
  138. package/dist/frontend/lowering/lower-test.d.ts +27 -0
  139. package/dist/frontend/lowering/lower-test.js +353 -0
  140. package/dist/frontend/lowering/lower-test.js.map +1 -0
  141. package/dist/frontend/lowering/lowerer.d.ts +1748 -0
  142. package/dist/frontend/lowering/lowerer.js +6256 -0
  143. package/dist/frontend/lowering/lowerer.js.map +1 -0
  144. package/dist/frontend/lowering/surfaces.d.ts +272 -0
  145. package/dist/frontend/lowering/surfaces.js +1096 -0
  146. package/dist/frontend/lowering/surfaces.js.map +1 -0
  147. package/dist/frontend/npm-static.d.ts +55 -0
  148. package/dist/frontend/npm-static.js +300 -0
  149. package/dist/frontend/npm-static.js.map +1 -0
  150. package/dist/frontend/npm.d.ts +330 -0
  151. package/dist/frontend/npm.js +1259 -0
  152. package/dist/frontend/npm.js.map +1 -0
  153. package/dist/frontend/program.d.ts +186 -0
  154. package/dist/frontend/program.js +2172 -0
  155. package/dist/frontend/program.js.map +1 -0
  156. package/dist/frontend/provenance-registry.d.ts +48 -0
  157. package/dist/frontend/provenance-registry.js +87 -0
  158. package/dist/frontend/provenance-registry.js.map +1 -0
  159. package/dist/frontend/provenance.d.ts +6 -0
  160. package/dist/frontend/provenance.js +459 -0
  161. package/dist/frontend/provenance.js.map +1 -0
  162. package/dist/frontend/resolve.d.ts +56 -0
  163. package/dist/frontend/resolve.js +682 -0
  164. package/dist/frontend/resolve.js.map +1 -0
  165. package/dist/frontend/shared.d.ts +60 -0
  166. package/dist/frontend/shared.js +232 -0
  167. package/dist/frontend/shared.js.map +1 -0
  168. package/dist/frontend/ts7/adapter.d.ts +7 -0
  169. package/dist/frontend/ts7/adapter.js +54 -0
  170. package/dist/frontend/ts7/adapter.js.map +1 -0
  171. package/dist/frontend/ts7/ast.d.ts +50 -0
  172. package/dist/frontend/ts7/ast.js +211 -0
  173. package/dist/frontend/ts7/ast.js.map +1 -0
  174. package/dist/frontend/ts7/census-check.d.ts +1 -0
  175. package/dist/frontend/ts7/census-check.js +12 -0
  176. package/dist/frontend/ts7/census-check.js.map +1 -0
  177. package/dist/frontend/ts7/checker.d.ts +140 -0
  178. package/dist/frontend/ts7/checker.js +544 -0
  179. package/dist/frontend/ts7/checker.js.map +1 -0
  180. package/dist/frontend/ts7/enums.d.ts +18 -0
  181. package/dist/frontend/ts7/enums.js +43 -0
  182. package/dist/frontend/ts7/enums.js.map +1 -0
  183. package/dist/frontend/ts7/program.d.ts +99 -0
  184. package/dist/frontend/ts7/program.js +278 -0
  185. package/dist/frontend/ts7/program.js.map +1 -0
  186. package/dist/frontend/ts7/world-check.d.ts +3 -0
  187. package/dist/frontend/ts7/world-check.js +34 -0
  188. package/dist/frontend/ts7/world-check.js.map +1 -0
  189. package/dist/frontend/types.d.ts +205 -0
  190. package/dist/frontend/types.js +2485 -0
  191. package/dist/frontend/types.js.map +1 -0
  192. package/dist/index.d.ts +85 -0
  193. package/dist/index.js +424 -0
  194. package/dist/index.js.map +1 -0
  195. package/dist/ir/nodes.d.ts +4334 -0
  196. package/dist/ir/nodes.js +1906 -0
  197. package/dist/ir/nodes.js.map +1 -0
  198. package/dist/ir/serialize.d.ts +4 -0
  199. package/dist/ir/serialize.js +34 -0
  200. package/dist/ir/serialize.js.map +1 -0
  201. package/dist/ir/validate.d.ts +36 -0
  202. package/dist/ir/validate.js +4884 -0
  203. package/dist/ir/validate.js.map +1 -0
  204. package/package.json +30 -6
  205. package/README.md +0 -3
  206. package/index.js +0 -2
@@ -0,0 +1,4701 @@
1
+ /* Class lowering: shape collection over the single-inheritance graph
2
+ * (fields, methods, accessors, overrides), constructor/member lowering with
3
+ * synthesized derived ctors and field initializers, super calls and super
4
+ * accessor access, upcasts, `new` expressions, and the builtin Error
5
+ * hierarchy registration. */
6
+ import * as ts from "../ts7/adapter.js";
7
+ import { BOOL, DYN, F64, JSVAL, RUNTIME_EMITTER_CLASS, RUNTIME_ERROR_CLASSES, RUNTIME_STREAM_CLASSES, STRING, UNDEFINED_T, URL_T, VOID, arrayOf, isSupportedMapKey, isUnitType, typeEquals } from "../../ir/nodes.js";
8
+ import { MAX_GENERIC_INSTANCES, genericCallInstance, implicitAnyParamSymbolsOf, implicitCallInstance, implicitMonoFile, omittedArgFor } from "./lower-calls.js";
9
+ import { typeKey } from "../types.js";
10
+ import { cjsClassExprWholeExportOf, isCjsJsFile, isJsSourceFile, isModuleExportsAccess, isNodeTypesPath, locOf } from "../program.js";
11
+ import { PoisonError, dynFallbackType, dynUndefinedExpr, newFnCtx, own } from "./lowerer.js";
12
+ import { bufEncoding, lowerMapSeedArrayNew } from "./lower-containers.js";
13
+ import { lowerSearchParamsNew } from "./lower-builtins.js";
14
+ import { requiresDynamicPackageDiag, unsupportedDiag } from "../../diagnostics/diagnostic.js";
15
+ import { STREAM_API_MEMBERS, STREAM_PROP_MEMBERS, UNDERSCORE_METHODS, lowerStreamNew, lowerStreamSuperCall, streamCtorShape } from "./lower-stream.js";
16
+ import { declSymbolOf } from "./lower-modules.js";
17
+ import { uniqueSymbolKeyOf } from "./lower-exprs.js";
18
+ import { lowerHttpServerNew } from "./lower-server.js";
19
+ import { ambientNsRootOf, ambientUndefReadType, ambientUndefVarRootOf, ambientUndefinedFnSymbolOf, fenceEarlyAliasUse, fenceEarlyNsMemberRef, nsMemberIdentOf, nsUndefRead } from "./lower-namespaces.js";
20
+ import { mixinResultBindingClassOf } from "./lower-mixins.js";
21
+ /** The KEY symbol behind a LATE-BOUND (`__@name@id`) property: resolved
22
+ * from the argument of the element-access assignment that declared it
23
+ * (`this[kLimit] = v` — the declaration list holds the BinaryExpression
24
+ * or the ElementAccessExpression itself). Null when no declaration has
25
+ * that shape (well-known-symbol members like `[Symbol.iterator]`). */
26
+ function lateBoundKeySymOf(L, p) {
27
+ for (const d of L.checker.declarationsOf(p)) {
28
+ const access = ts.isBinaryExpression(d) && ts.isElementAccessExpression(d.left)
29
+ ? d.left
30
+ : ts.isElementAccessExpression(d)
31
+ ? d
32
+ : null;
33
+ if (!access || !ts.isIdentifier(access.argumentExpression))
34
+ continue;
35
+ const sym = L.resolveValueSymbol(access.argumentExpression);
36
+ if (sym)
37
+ return sym;
38
+ }
39
+ return null;
40
+ }
41
+ /** Symbol-slot RETURN refinement (5.9.3 ABI parity): tsgo synthesizes no
42
+ * late-bound property for a JS class's `this[k] = v` declaration (the
43
+ * finding-5 family), so an unannotated method whose returns read a
44
+ * declared symbol-keyed slot infers `any` — the checked-dynamic
45
+ * fallback would box a value whose static type the class layout already
46
+ * knows (5.9.3 inferred it through the late-bound property; runtime
47
+ * output was identical either way, but the method ABI carried a dyn
48
+ * box). Recovered here from the layout itself, under a shape that
49
+ * cannot mis-type: an unannotated, non-async, non-generator JS method
50
+ * whose LAST top-level statement is a return (no fall-through
51
+ * `undefined` completion), where EVERY return statement (nested
52
+ * functions excluded — they return elsewhere) returns `this[k]` with a
53
+ * statically-resolved key declared in symbolFields, and all the slots
54
+ * agree on one IR type. Null when the shape doesn't hold — the value
55
+ * stays checked-dynamic exactly as before. */
56
+ function symbolSlotReturnType(L, fnLike, symbolFields, fields) {
57
+ if (symbolFields.size === 0)
58
+ return null;
59
+ if (fnLike.type !== undefined || !fnLike.body)
60
+ return null;
61
+ if (!isJsSourceFile(fnLike.getSourceFile()))
62
+ return null;
63
+ if (fnLike.asteriskToken !== undefined)
64
+ return null;
65
+ if (fnLike.modifiers?.some((m) => m.kind === ts.SyntaxKind.AsyncKeyword))
66
+ return null;
67
+ const stmts = fnLike.body.statements;
68
+ const last = stmts[stmts.length - 1];
69
+ if (!last || !ts.isReturnStatement(last))
70
+ return null;
71
+ const returns = [];
72
+ const visit = (n) => {
73
+ if (ts.isFunctionLike(n))
74
+ return;
75
+ if (ts.isReturnStatement(n))
76
+ returns.push(n);
77
+ n.forEachChild(visit);
78
+ };
79
+ fnLike.body.forEachChild(visit);
80
+ let out = null;
81
+ for (const r of returns) {
82
+ let e = r.expression;
83
+ while (e !== undefined && ts.isParenthesizedExpression(e))
84
+ e = e.expression;
85
+ if (e === undefined || !ts.isElementAccessExpression(e))
86
+ return null;
87
+ if (e.expression.kind !== ts.SyntaxKind.ThisKeyword)
88
+ return null;
89
+ const key = uniqueSymbolKeyOf(L, e.argumentExpression);
90
+ const fieldName = key ? symbolFields.get(key.sym) : undefined;
91
+ const t = fieldName !== undefined ? fields.get(fieldName) : undefined;
92
+ if (t === undefined || t.kind === "dyn")
93
+ return null;
94
+ if (out !== null && !typeEquals(out, t))
95
+ return null;
96
+ out = t;
97
+ }
98
+ return out;
99
+ }
100
+ /** The builtin Error hierarchy (Error + TypeError/RangeError/SyntaxError)
101
+ * as eagerly-registered ClassInfos: mapType names them the moment a lib
102
+ * Error type appears, so the infos must exist before any lowering. They
103
+ * are runtime-provided — no decl, no lowerable bodies; `new`/super()/
104
+ * toString reach them through dedicated error.* libCall lowerings, and
105
+ * user classes extend them like any base (the emitted subclass struct
106
+ * embeds ScrError's prefix). */
107
+ export function registerBuiltinErrorClasses(L) {
108
+ const loc = { file: "<builtin>", start: 0, end: 0 };
109
+ for (const [irName, rec] of RUNTIME_ERROR_CLASSES) {
110
+ const base = rec.base ? (L.classes.get(rec.base) ?? null) : null;
111
+ const info = {
112
+ def: {
113
+ name: irName,
114
+ runtime: true,
115
+ ...(rec.base ? { base: rec.base } : {}),
116
+ // Layout only — `%code` is ScrError's third slot (NULL = absent;
117
+ // fs/exec throw sites stamp it): subclass structs embed it in
118
+ // their prefix, and teardown releases it NULL-guarded like any
119
+ // string field. The '%' name keeps it out of user reach (a
120
+ // subclass declaring its own `code` field lays out AFTER it,
121
+ // never colliding), and it is NOT in the fields map below: the
122
+ // READ has its own `string | undefined` lowering (error.code),
123
+ // never a plain-string field access.
124
+ fields: [
125
+ { name: "name", type: STRING },
126
+ { name: "message", type: STRING },
127
+ { name: "%code", type: STRING },
128
+ ],
129
+ loc,
130
+ },
131
+ fields: new Map([
132
+ ["name", STRING],
133
+ ["message", STRING],
134
+ ]),
135
+ fieldOrder: [],
136
+ // Only the root declares toString — subclasses (builtin and user)
137
+ // reach it through the base-chain walk, so its declarer is always
138
+ // %Error and calls lower to the one runtime implementation.
139
+ methods: rec.base === null
140
+ ? new Map([["toString", { params: [], ret: STRING }]])
141
+ : new Map(),
142
+ decl: null,
143
+ builtinError: true,
144
+ ctor: null,
145
+ // Display shape of `new Error(message?)`. Construction and super()
146
+ // never complete against this — errorMessageArg owns those (the
147
+ // runtime ABI is one plain string; "" when omitted, like Node).
148
+ ctorParams: [{ type: STRING, mode: "omittable" }],
149
+ base,
150
+ subclasses: [],
151
+ throwingSetters: [],
152
+ staticFields: [],
153
+ };
154
+ if (base)
155
+ base.subclasses.push(info);
156
+ L.classes.set(irName, info);
157
+ }
158
+ }
159
+ /** The runtime-provided node:events EventEmitter as an eagerly-registered
160
+ * ClassInfo (the error-hierarchy story): mapType names `%EventEmitter`
161
+ * the moment an emitter type appears, so the info must exist before any
162
+ * lowering. No decl, no lowerable bodies — `new`/super() reach it
163
+ * through emitter.* libCalls, the method surface lowers through
164
+ * lower-emitter.ts, and user classes extend it like any base (the
165
+ * emitted subclass struct embeds ScrEmitter's registry/name prefix —
166
+ * carried by the BACKEND, not by IR fields, so the fields list stays
167
+ * empty and subclass field layout starts right after the prefix). */
168
+ export function registerBuiltinEmitterClass(L) {
169
+ const loc = { file: "<builtin>", start: 0, end: 0 };
170
+ const info = {
171
+ def: { name: RUNTIME_EMITTER_CLASS, runtime: true, fields: [], loc },
172
+ fields: new Map(),
173
+ fieldOrder: [],
174
+ methods: new Map(),
175
+ decl: null,
176
+ builtinEmitter: true,
177
+ ctor: null,
178
+ // `new EventEmitter()` — zero-argument (the options bag fences at
179
+ // construction sites; the checker may admit it via @types/node).
180
+ ctorParams: [],
181
+ base: null,
182
+ subclasses: [],
183
+ throwingSetters: [],
184
+ staticFields: [],
185
+ };
186
+ L.classes.set(RUNTIME_EMITTER_CLASS, info);
187
+ }
188
+ /** The runtime-provided node:stream classes as eagerly-registered
189
+ * ClassInfos (the emitter story): mapType names `%Readable` et al the
190
+ * moment a stream type appears, so the infos must exist before any
191
+ * lowering. Each roots at the emitter through its base chain, so the
192
+ * EventEmitter method surface, upcasts, and instanceof intervals apply
193
+ * unchanged; the stream method/property surface lowers through
194
+ * lower-stream.ts. No decl, no lowerable bodies, empty field lists —
195
+ * every instance is runtime-allocated (user `extends` is fenced). */
196
+ export function registerBuiltinStreamClasses(L) {
197
+ const loc = { file: "<builtin>", start: 0, end: 0 };
198
+ for (const [irName, rec] of RUNTIME_STREAM_CLASSES) {
199
+ const base = L.classes.get(rec.base) ?? null;
200
+ const info = {
201
+ def: { name: irName, runtime: true, base: rec.base, fields: [], loc },
202
+ fields: new Map(),
203
+ fieldOrder: [],
204
+ methods: new Map(),
205
+ decl: null,
206
+ builtinStream: rec.sides,
207
+ ctor: null,
208
+ // `new Readable(opts?)` — the options bag is parsed structurally
209
+ // by the stream spoke (lowerNew never completes against this).
210
+ ctorParams: [],
211
+ base,
212
+ subclasses: [],
213
+ throwingSetters: [],
214
+ staticFields: [],
215
+ };
216
+ if (base)
217
+ base.subclasses.push(info);
218
+ L.classes.set(irName, info);
219
+ }
220
+ }
221
+ /** The stream ClassInfo a VALUE symbol refers to (`new Readable(...)`,
222
+ * `x instanceof Writable`) — any import spelling resolves to the
223
+ * ambient class. Provenance: a stdlib-file CLASS declaration inside the
224
+ * "stream" ambient module, EXCLUDING @types/node's (whose stream.Readable
225
+ * also types child stdio — under @types/node the childStream mapping
226
+ * keeps priority and the static stream classes stand down; the shipped
227
+ * fallback declarations are the supported surface). */
228
+ export function builtinStreamInfoOf(L, symbol) {
229
+ if (!symbol)
230
+ return null;
231
+ if (!L.isStdlibSymbol(symbol)) {
232
+ // A const ALIAS of a namespace member (`const Writable =
233
+ // stream.Writable` — the two-step spelling; the one-step
234
+ // require('stream').Writable rides the same walk): follow the
235
+ // member to the stdlib class symbol. The declaration itself is
236
+ // alias plumbing (streamClassAliasDecl — both declaration walks
237
+ // skip it).
238
+ const decl = L.checker.valueDeclarationOf(symbol);
239
+ if (decl && ts.isVariableDeclaration(decl) &&
240
+ (ts.getCombinedNodeFlags(decl) & ts.NodeFlags.Const) !== 0 &&
241
+ decl.initializer !== undefined &&
242
+ ts.isPropertyAccessExpression(decl.initializer) &&
243
+ !decl.initializer.questionDotToken &&
244
+ L.builtinNamespaceModuleOf(decl.initializer.expression) === "stream") {
245
+ const mSym = L.checker.getSymbolAtLocation(decl.initializer.name);
246
+ const target = mSym && mSym.flags & ts.SymbolFlags.Alias ? L.checker.getAliasedSymbol(mSym) : mSym;
247
+ if (target && target !== symbol)
248
+ return builtinStreamInfoOf(L, target);
249
+ }
250
+ return null;
251
+ }
252
+ let irName = null;
253
+ for (const [name, rec] of RUNTIME_STREAM_CLASSES) {
254
+ if (rec.lib === symbol.name)
255
+ irName = name;
256
+ }
257
+ if (!irName)
258
+ return null;
259
+ const declared = L.checker.declarationsOf(symbol).some((d) => {
260
+ if (!ts.isClassDeclaration(d))
261
+ return false;
262
+ if (isNodeTypesPath(d.getSourceFile().fileName))
263
+ return false;
264
+ let node = d.parent;
265
+ while (node) {
266
+ if (ts.isModuleDeclaration(node) && ts.isStringLiteral(node.name)) {
267
+ return node.name.text === "stream" || node.name.text === "node:stream";
268
+ }
269
+ node = node.parent;
270
+ }
271
+ return false;
272
+ });
273
+ return declared ? (L.classes.get(irName) ?? null) : null;
274
+ }
275
+ /** The undefined-armed union of a JS class property's inferred type — the
276
+ * honest slot for a field first assigned outside the constructor's top
277
+ * level (undefined until the write runs, Node-exact). Null when the
278
+ * inference is unmappable, checked-dynamic (dyn stays out of class
279
+ * fields — KEEP NARROW), or an arm-less kind that cannot join a union
280
+ * (genResultRecord's list). */
281
+ function undefArmedFieldType(L, p) {
282
+ const t = L.checker.getTypeOfSymbol(p);
283
+ const mapped = L.mapTypeOf(t);
284
+ if (!mapped || mapped.kind === "void" || mapped.kind === "dyn")
285
+ return null;
286
+ const byKey = new Map();
287
+ const arms = mapped.kind === "union" ? (L.unions.get(mapped.unionId)?.arms ?? []) : [mapped];
288
+ for (const a of arms) {
289
+ if (a.kind === "map" || a.kind === "regex" || a.kind === "jsval" || a.kind === "generator") {
290
+ return null;
291
+ }
292
+ byKey.set(typeKey(a), a);
293
+ }
294
+ byKey.set(typeKey(UNDEFINED_T), UNDEFINED_T);
295
+ const sorted = [...byKey.values()].sort((a, b) => (typeKey(a) < typeKey(b) ? -1 : 1));
296
+ return { kind: "union", unionId: L.unions.intern(sorted) };
297
+ }
298
+ /** The emitter ClassInfo a VALUE symbol refers to (`new EventEmitter`,
299
+ * `extends EventEmitter`, `x instanceof EventEmitter`) — any import
300
+ * spelling (named/default/namespace member, CJS require) resolves to
301
+ * the ambient class. Provenance-checked like the error classes: only a
302
+ * stdlib-file declaration inside the "events" ambient module counts. */
303
+ export function builtinEmitterInfoOf(L, symbol) {
304
+ if (!symbol)
305
+ return null;
306
+ if (!L.isStdlibSymbol(symbol)) {
307
+ // A const ALIAS of the emitter class member (`const EventEmitter =
308
+ // require('node:events').EventEmitter` — commander's spelling; the
309
+ // two-step `const EE = events.EventEmitter` rides the same walk):
310
+ // follow the member off the module namespace. The declaration
311
+ // itself is alias plumbing (builtinMemberRequireDecl — both
312
+ // declaration walks skip it).
313
+ const decl = L.checker.valueDeclarationOf(symbol);
314
+ if (decl !== undefined && ts.isVariableDeclaration(decl) &&
315
+ (ts.getCombinedNodeFlags(decl) & ts.NodeFlags.Const) !== 0 &&
316
+ decl.initializer !== undefined &&
317
+ ts.isPropertyAccessExpression(decl.initializer) &&
318
+ !decl.initializer.questionDotToken &&
319
+ decl.initializer.name.text === "EventEmitter" &&
320
+ L.builtinNamespaceModuleOf(decl.initializer.expression) === "events") {
321
+ return L.classes.get(RUNTIME_EMITTER_CLASS) ?? null;
322
+ }
323
+ return null;
324
+ }
325
+ if (symbol.name !== "EventEmitter")
326
+ return null;
327
+ const declared = L.checker.declarationsOf(symbol).some((d) => {
328
+ if (!ts.isClassDeclaration(d) && !ts.isInterfaceDeclaration(d))
329
+ return false;
330
+ let node = d.parent;
331
+ while (node) {
332
+ if (ts.isModuleDeclaration(node) && ts.isStringLiteral(node.name)) {
333
+ return node.name.text === "events" || node.name.text === "node:events";
334
+ }
335
+ node = node.parent;
336
+ }
337
+ return false;
338
+ });
339
+ return declared ? (L.classes.get(RUNTIME_EMITTER_CLASS) ?? null) : null;
340
+ }
341
+ /** The builtin error ClassInfo a VALUE symbol refers to (`new Error`,
342
+ * `extends TypeError`, `x instanceof RangeError`), or null. Provenance-
343
+ * checked: only the standard library's declarations count — a user's own
344
+ * `class Error` resolves through classBySymbol instead. */
345
+ export function builtinErrorInfoOf(L, symbol) {
346
+ if (!symbol || !L.isStdlibSymbol(symbol))
347
+ return null;
348
+ for (const [irName, rec] of RUNTIME_ERROR_CLASSES) {
349
+ if (rec.lib === symbol.name)
350
+ return L.classes.get(irName) ?? null;
351
+ }
352
+ return null;
353
+ }
354
+ /** The instance-method surface the runtime EventEmitter owns — subclass
355
+ * members with these names are fenced (collectClassShapeInner) and calls
356
+ * to them on emitter-rooted receivers lower through lower-emitter.ts. */
357
+ export const EMITTER_API_MEMBERS = new Set([
358
+ "on", "addListener", "once", "prependListener", "prependOnceListener",
359
+ "off", "removeListener", "removeAllListeners", "emit", "listenerCount",
360
+ "listeners", "rawListeners", "eventNames", "setMaxListeners", "getMaxListeners",
361
+ ]);
362
+ /** The decorators of a class-like or member node (they live in
363
+ * `modifiers` since TS 4.8). */
364
+ export function decoratorNodesOf(n) {
365
+ return (n.modifiers ?? []).filter((m) => m.kind === ts.SyntaxKind.Decorator);
366
+ }
367
+ /** The AMBIENT name a decorator expression's evaluation throws on, or
368
+ * null. Node erases ambient declarations (`declare let dec: any`,
369
+ * `declare const instance: T`, `declare function dec<T>(t: T): T`), so
370
+ * reading the name is a ReferenceError. Factory spellings ride along —
371
+ * `@dec(...)` evaluates the CALLEE before any argument — and property
372
+ * chains throw at their ROOT (`@instance.decorate` reads `instance`
373
+ * first). */
374
+ export function ambientDecoratorThrowNameOf(L, dExpr) {
375
+ let e = dExpr;
376
+ while (ts.isParenthesizedExpression(e))
377
+ e = e.expression;
378
+ const target = ts.isCallExpression(e) ? e.expression : e;
379
+ const root = ambientUndefVarRootOf(L, target);
380
+ if (root)
381
+ return root.text;
382
+ let callee = target;
383
+ while (ts.isParenthesizedExpression(callee))
384
+ callee = callee.expression;
385
+ if (ts.isIdentifier(callee) && ambientUndefinedFnSymbolOf(L, callee) !== null)
386
+ return callee.text;
387
+ return null;
388
+ }
389
+ /** The guaranteed decoration THROW of a decorated class, or null. Walks
390
+ * the class definition's evaluation-order items — class decorators
391
+ * (source order), the heritage expression, then per member in body
392
+ * order its decorators and computed key (the verified TC39/tsc-downlevel
393
+ * order) — and answers the first AMBIENT decorator name, provided every
394
+ * item BEFORE it is provably effect-free and non-throwing: bare
395
+ * identifier decorators over defined values (a pure read), an absent /
396
+ * `null` / bare-identifier heritage, literal or bare-identifier
397
+ * computed keys. Anything richer (factory calls over defined values,
398
+ * property-access reads, computed-key calls) stops the proof — the
399
+ * named fences answer instead. */
400
+ export function guaranteedDecorationThrow(L, decl) {
401
+ const stripParens = (e) => {
402
+ let x = e;
403
+ while (ts.isParenthesizedExpression(x))
404
+ x = x.expression;
405
+ return x;
406
+ };
407
+ const decoratorVerdict = (d) => {
408
+ const name = ambientDecoratorThrowNameOf(L, d.expression);
409
+ if (name !== null)
410
+ return { name, node: d };
411
+ const e = stripParens(d.expression);
412
+ // A bare identifier over a DEFINED value: a pure read.
413
+ if (ts.isIdentifier(e))
414
+ return "effectFree";
415
+ return "opaque";
416
+ };
417
+ for (const d of decoratorNodesOf(decl)) {
418
+ const v = decoratorVerdict(d);
419
+ if (v === "opaque")
420
+ return null;
421
+ if (v !== "effectFree")
422
+ return v;
423
+ }
424
+ const heritage = decl.heritageClauses
425
+ ?.find((c) => c.token === ts.SyntaxKind.ExtendsKeyword)
426
+ ?.types[0];
427
+ if (heritage) {
428
+ const h = stripParens(heritage.expression);
429
+ if (h.kind !== ts.SyntaxKind.NullKeyword && !ts.isIdentifier(h))
430
+ return null;
431
+ }
432
+ for (const member of decl.members) {
433
+ for (const d of decoratorNodesOf(member)) {
434
+ const v = decoratorVerdict(d);
435
+ if (v === "opaque")
436
+ return null;
437
+ if (v !== "effectFree")
438
+ return v;
439
+ }
440
+ const name = member.name;
441
+ if (name && ts.isComputedPropertyName(name)) {
442
+ const k = stripParens(name.expression);
443
+ const literalKey = ts.isStringLiteralLike(k) || ts.isNumericLiteral(k) || ts.isIdentifier(k);
444
+ if (!literalKey)
445
+ return null;
446
+ }
447
+ }
448
+ return null;
449
+ }
450
+ export function collectClassShape(L, decl) {
451
+ const symbol = L.collectDeferring(() => declSymbolOf(L, decl), () => L.collectClassShapeInner(decl));
452
+ // Typed receivers and module retention know the class only by its
453
+ // qualified IR name — index the deferral under it too.
454
+ if (symbol)
455
+ L.deferredClassByName.set(L.classNamer(decl), symbol);
456
+ // A poisoned class containing a static BLOCK or a DECORATOR must report
457
+ // EAGERLY: deferral's premise ("an unreached broken declaration costs
458
+ // nothing") fails here — Node runs the block (and calls the decorator)
459
+ // when the class statement evaluates, referenced or not, so silently
460
+ // dropping the declaration would drop observable side effects (the
461
+ // classStaticBlock13/28 miscompiles).
462
+ const hasDeclTimeCode = (n) => ts.isClassStaticBlockDeclaration(n) ||
463
+ (n.modifiers ?? []).some((m) => m.kind === ts.SyntaxKind.Decorator);
464
+ if (symbol && (hasDeclTimeCode(decl) || decl.members.some(hasDeclTimeCode))) {
465
+ const diags = L.deferredDiags.get(symbol);
466
+ if (diags) {
467
+ L.deferredDiags.delete(symbol);
468
+ if (!L.alreadyFlushed.has(symbol)) {
469
+ L.flushedSymbols.add(symbol);
470
+ for (const d of diags)
471
+ L.pushDiag(d);
472
+ }
473
+ }
474
+ }
475
+ }
476
+ export function collectClassShapeInner(L, decl, jsNameOverride, inst,
477
+ /** MIXIN instantiation mode (lower-mixins.ts): the class inside a
478
+ * mixin function, collected per call site — `base` is the ARGUMENT
479
+ * class (the heritage clause names the mixin's parameter and is
480
+ * resolved here, never through the loop below), `name` the
481
+ * position-derived instance name. */
482
+ mixin) {
483
+ {
484
+ // Anonymous class EXPRESSIONS are ordinary (their .name follows
485
+ // NamedEvaluation — jsNameOverride carries it). The one legal
486
+ // nameless class DECLARATION is `export default class {}` — its
487
+ // symbol is the module's default export (declSymbolOf) and it
488
+ // registers under classNamer's "%anon" spelling (unique per file).
489
+ if (!decl.name && ts.isClassDeclaration(decl) && declSymbolOf(L, decl) === undefined) {
490
+ L.unsupported("SC1090", decl, "anonymous classes");
491
+ }
492
+ // Decorators are declaration-time CALLS (they run when the class
493
+ // statement evaluates and may replace the declaration outright).
494
+ // CLASS decorators lower statically (collected here, analyzed
495
+ // post-collection, emitted in %init at the class statement's
496
+ // position — see ClassDecorationInfo). A decoration that PROVABLY
497
+ // throws before anything else evaluates (an ambient decorator name,
498
+ // the corpus's dominant shape — class-level or MEMBER-level) makes
499
+ // the whole declaration a shell whose %init is exactly the throw.
500
+ // Remaining MEMBER decorators stay named fences — a method/field
501
+ // replacement would have to rebind vtable slots and initializer
502
+ // chains at declaration time, and the standard context object
503
+ // (addInitializer, access) has no static story yet. Parameter
504
+ // decorators are not valid ES decorators — the checker rejects
505
+ // them first.
506
+ const classDecoratorNodes = [];
507
+ {
508
+ classDecoratorNodes.push(...decoratorNodesOf(decl));
509
+ const decoratedMembers = decl.members.filter((m) => decoratorNodesOf(m).length > 0);
510
+ if (classDecoratorNodes.length > 0 || decoratedMembers.length > 0) {
511
+ // Node itself cannot execute decorator syntax in a JavaScript
512
+ // source (V8 has not shipped the proposal; the type-stripping
513
+ // loaders leave `@dec` in place) — there is no runtime behavior
514
+ // to be exact against.
515
+ if (isJsSourceFile(decl.getSourceFile())) {
516
+ L.unsupported("SC1090", (classDecoratorNodes[0] ?? decoratorNodesOf(decoratedMembers[0])[0]), "decorators in JavaScript sources (V8 has not shipped decorators — Node cannot execute this file)");
517
+ }
518
+ // The guaranteed-throw SHELL: declarations only (expressions
519
+ // lower their throw at the expression — lowerClassExpression),
520
+ // never instantiations/mixins (they share a family declaration).
521
+ if (inst === undefined && mixin === undefined &&
522
+ ts.isClassDeclaration(decl) && decl.typeParameters === undefined) {
523
+ const thrown = guaranteedDecorationThrow(L, decl);
524
+ if (thrown) {
525
+ const className = L.classNamer(decl);
526
+ const info = {
527
+ def: {
528
+ name: className,
529
+ jsName: jsNameOverride ?? decl.name?.text ?? "",
530
+ fields: [],
531
+ loc: locOf(decl),
532
+ },
533
+ fields: new Map(),
534
+ fieldOrder: [],
535
+ methods: new Map(),
536
+ decl,
537
+ ctor: null,
538
+ ctorParams: [],
539
+ base: null,
540
+ subclasses: [],
541
+ throwingSetters: [],
542
+ staticFields: [],
543
+ decorationThrows: { name: thrown.name },
544
+ // The existing ambientThrow emission (lowerClassDecoration)
545
+ // owns the %init: earlier expressions are all pure reads,
546
+ // so the throw is the first observable effect.
547
+ classDecorators: {
548
+ nodes: [thrown.node],
549
+ shapes: [{ kind: "ambientThrow", name: thrown.name }],
550
+ },
551
+ };
552
+ L.classes.set(className, info);
553
+ const classSymbol = decl.name
554
+ ? L.checker.getSymbolAtLocation(decl.name)
555
+ : declSymbolOf(L, decl);
556
+ if (classSymbol)
557
+ L.classBySymbol.set(classSymbol, info);
558
+ return;
559
+ }
560
+ }
561
+ for (const member of decoratedMembers) {
562
+ const dec = decoratorNodesOf(member)[0];
563
+ const kind = ts.isMethodDeclaration(member)
564
+ ? "method decorators"
565
+ : ts.isGetAccessorDeclaration(member) || ts.isSetAccessorDeclaration(member)
566
+ ? "accessor decorators"
567
+ : ts.isPropertyDeclaration(member)
568
+ ? (member.modifiers?.some((m) => m.kind === ts.SyntaxKind.AccessorKeyword)
569
+ ? "auto-accessor decorators"
570
+ : "field decorators")
571
+ : "member decorators";
572
+ L.unsupported("SC1090", dec, `${kind} (the standard context object and member replacement have no static lowering — class decorators and provably-throwing ambient decorations compile)`);
573
+ }
574
+ }
575
+ if (classDecoratorNodes.length > 0) {
576
+ // Each evaluation of a class EXPRESSION decorates a freshly
577
+ // minted class; only once-evaluated declarations have a single
578
+ // decoration event to lower.
579
+ if (!ts.isClassDeclaration(decl)) {
580
+ L.unsupported("SC1090", classDecoratorNodes[0], "decorators on class expressions (each evaluation decorates a distinct class)");
581
+ }
582
+ // A generic class declares ONCE in JS (one decoration event over
583
+ // the one runtime Box) but compiles per instantiation here — the
584
+ // family object is never constructed and the instantiations were
585
+ // never individually decorated.
586
+ if (decl.typeParameters !== undefined) {
587
+ L.unsupported("SC1090", classDecoratorNodes[0], "decorators on generic classes (JS decorates the one runtime class; the compiled family instantiates per type argument)");
588
+ }
589
+ }
590
+ }
591
+ // An abstract class is a class nothing constructs directly — tsc
592
+ // rejects `new` on it (through class values too), so no runtime
593
+ // trap exists to lower. It collects like any class; only the flag
594
+ // is recorded (abstract MEMBERS are per-member, below).
595
+ const abstractClass = ts.getModifiers(decl)?.some((m) => m.kind === ts.SyntaxKind.AbstractKeyword) === true;
596
+ // A GENERIC class declaration collects as its FAMILY (statics + the
597
+ // instanceof interval; no instance members — those collect per
598
+ // instantiation, `inst` set). Generic class EXPRESSIONS stay fenced:
599
+ // each evaluation mints a distinct class in JS, and a distinct
600
+ // FAMILY of classes has no once-evaluated story.
601
+ const familyMode = decl.typeParameters !== undefined && inst === undefined;
602
+ if (familyMode && !ts.isClassDeclaration(decl)) {
603
+ L.unsupported("SC1090", decl, "generic class expressions");
604
+ }
605
+ const className = inst ? inst.name : mixin ? mixin.name : L.classNamer(decl); // program-wide qualified name
606
+ // Single inheritance: `extends` of a class declared in the program.
607
+ // tsc guarantees the base is declared before the derived class (its
608
+ // use-before-declaration error), and collection runs in module order,
609
+ // so the base's ClassInfo already exists here. An INSTANTIATION's
610
+ // base is its family (whose base is the declared one) — the heritage
611
+ // clause resolved when the family collected.
612
+ let base = inst ? inst.family : mixin ? mixin.base : null;
613
+ // A family whose `extends` clause mentions its OWN type parameters
614
+ // (`class D<T> extends Box<T>`) would need a different base per
615
+ // instantiation — no single family interval can sit above all of
616
+ // them. Named fence at the declaration.
617
+ if (familyMode && decl.heritageClauses !== undefined) {
618
+ const tpSyms = new Set();
619
+ for (const tp of decl.typeParameters) {
620
+ const s = L.checker.getSymbolAtLocation(tp.name);
621
+ if (s)
622
+ tpSyms.add(s);
623
+ }
624
+ for (const clause of decl.heritageClauses) {
625
+ if (clause.token !== ts.SyntaxKind.ExtendsKeyword)
626
+ continue;
627
+ for (const t of clause.types) {
628
+ let mentions = false;
629
+ ts.walkPreorder(t, (n) => {
630
+ const s = ts.isIdentifier(n) ? L.checker.getSymbolAtLocation(n) : undefined;
631
+ if (s && tpSyms.has(s)) {
632
+ mentions = true;
633
+ return "stop";
634
+ }
635
+ return undefined;
636
+ });
637
+ if (mentions) {
638
+ L.unsupported("SC1090", t, "generic classes whose 'extends' clause mentions their own type parameters (each instantiation would need a different base)");
639
+ }
640
+ }
641
+ }
642
+ }
643
+ for (const clause of inst || mixin ? [] : (decl.heritageClauses ?? [])) {
644
+ // `implements` is pure type-world: tsc checked the conformance and
645
+ // the clause erases — nothing about the runtime class changes.
646
+ // (Assigning an instance INTO an interface-typed slot is a separate
647
+ // question, owned by the shape-coercion fences at those sites.)
648
+ if (clause.token === ts.SyntaxKind.ImplementsKeyword)
649
+ continue;
650
+ const t = clause.types[0];
651
+ // `extends events.EventEmitter` — the namespace-member spelling of
652
+ // the ambient emitter base resolves like the named import.
653
+ if (t && ts.isPropertyAccessExpression(t.expression) && ts.isIdentifier(t.expression.name)) {
654
+ const memberSym = L.checker.getSymbolAtLocation(t.expression.name);
655
+ const resolved = memberSym && memberSym.flags & ts.SymbolFlags.Alias
656
+ ? L.checker.getAliasedSymbol(memberSym)
657
+ : memberSym;
658
+ const emitterBase = L.builtinEmitterInfoOf(resolved);
659
+ const streamBaseNs = builtinStreamInfoOf(L, resolved);
660
+ if (emitterBase || streamBaseNs) {
661
+ if (t.typeArguments)
662
+ L.unsupported("SC1090", t, "extending generic classes");
663
+ base = (emitterBase ?? streamBaseNs);
664
+ continue;
665
+ }
666
+ // `class Tower extends Shapes.Cube` — the namespace-qualified
667
+ // base: the member resolves to the registered program class
668
+ // (import= alias chains included), with the source-order guard
669
+ // (the class statement evaluates at its init position; a base
670
+ // block below it would still be uninitialized in Node).
671
+ if (!t.expression.questionDotToken && nsMemberIdentOf(L, t.expression)) {
672
+ if (t.typeArguments)
673
+ L.unsupported("SC1090", t, "extending generic classes");
674
+ if (memberSym)
675
+ fenceEarlyNsMemberRef(L, t.expression, memberSym);
676
+ const nsBase = resolved ? L.classBySymbol.get(resolved) : undefined;
677
+ if (!nsBase) {
678
+ L.unsupported("SC1090", t, `extending the namespace member '${t.expression.name.text}' (no class lowering)`);
679
+ }
680
+ base = nsBase;
681
+ continue;
682
+ }
683
+ // `Common.O = class extends Common.I {}` — the base is a
684
+ // PROPERTY-ASSIGNED class expression (the salsa expando form and
685
+ // its CJS spellings `exports.I` / `module.exports.I`): the
686
+ // member's single top-level assignment pins the class, so the
687
+ // base resolves like a declaration. Source order guards the
688
+ // same-file case — Node evaluates the extends clause at THIS
689
+ // statement, and a base assigned below it is still undefined
690
+ // here (TypeError at runtime; the fence is the honest answer).
691
+ // Reassigned properties never reach this branch (the resolver
692
+ // answers null for them) and keep the computed-expression fence:
693
+ // the runtime base is whichever assignment ran last.
694
+ if (!t.expression.questionDotToken) {
695
+ const propBase = propertyAssignedClassInfoOf(L, memberSym);
696
+ if (propBase) {
697
+ if (t.typeArguments)
698
+ L.unsupported("SC1090", t, "extending generic classes");
699
+ const baseDecl = propBase.decl;
700
+ if (baseDecl != null &&
701
+ baseDecl.getSourceFile() === decl.getSourceFile() &&
702
+ baseDecl.getStart() > decl.getStart()) {
703
+ L.unsupported("SC1090", t, `extending '${t.expression.getText()}' above the statement that assigns it (the property is still undefined when this class evaluates — Node throws here; assign the base first)`);
704
+ }
705
+ base = propBase;
706
+ continue;
707
+ }
708
+ // The REASSIGNED spelling of the same family gets its own
709
+ // fence (the generic computed-expression one below would hide
710
+ // what actually blocks it).
711
+ const rebinds = memberSym !== undefined &&
712
+ L.checker
713
+ .declarationsOf(memberSym)
714
+ .filter((d) => ts.isBinaryExpression(d) &&
715
+ d.operatorToken.kind === ts.SyntaxKind.EqualsToken).length > 1;
716
+ if (rebinds) {
717
+ L.unsupported("SC1090", t, `extending the reassigned property '${t.expression.getText()}' (the runtime base is whichever assignment ran last — bind the class exactly once)`);
718
+ }
719
+ }
720
+ }
721
+ // `class extends class {…} {…}` — a class-EXPRESSION base:
722
+ // collect it recursively (JS evaluates the extends clause first,
723
+ // so its statics queue ahead of the derived class's — the
724
+ // recursion order delivers exactly that).
725
+ if (t && ts.isClassExpression(t.expression)) {
726
+ if (t.typeArguments)
727
+ L.unsupported("SC1090", t, "extending generic classes");
728
+ const baseExpr = L.lowerClassExpressionInfo(t.expression);
729
+ base = baseExpr;
730
+ continue;
731
+ }
732
+ // `class D extends Mixin(Base)` — a MIXIN call as the base: the
733
+ // call's per-site instantiation (its heritage the argument class)
734
+ // is the base — interval nesting, fields, and methods compose
735
+ // through the monomorphized chain (lower-mixins.ts). A call whose
736
+ // callee is NOT a mixin function keeps the computed-expression
737
+ // fence below.
738
+ if (t && ts.isCallExpression(t.expression) && !t.typeArguments) {
739
+ const mixinBase = L.mixinCallClassInfoOf(t.expression);
740
+ if (mixinBase) {
741
+ base = mixinBase;
742
+ continue;
743
+ }
744
+ }
745
+ if (!t || !ts.isIdentifier(t.expression)) {
746
+ L.unsupported("SC1090", clause, "extending computed expressions");
747
+ }
748
+ const symbol = L.resolveValueSymbol(t.expression);
749
+ // Extending a REBINDABLE decorated class (analysis already ran —
750
+ // this collection is a class expression or a generic
751
+ // instantiation demanded during lowering): the runtime base is
752
+ // the decoration result, not the declaration. Declared
753
+ // subclasses collected BEFORE analysis meet the same fence from
754
+ // analyzeClassDecoration's subclasses check.
755
+ {
756
+ const directBase = symbol && L.classBySymbol.get(symbol);
757
+ if (directBase && directBase.classDecorators?.valueGlobalId !== undefined) {
758
+ L.unsupported("SC1090", t, `extending the decorated class '${directBase.def.jsName ?? directBase.def.name}' (its decorators may replace it — the runtime base would be the decoration result)`);
759
+ }
760
+ if (directBase)
761
+ fenceDecorationThrows(L, directBase, t);
762
+ }
763
+ // `extends DOMException`: the runtime instance carries hidden
764
+ // slots (the legacy code, the cause) BEYOND the ScrError prefix
765
+ // the IR fields describe — a subclass layout would overlap them.
766
+ if (L.builtinErrorInfoOf(symbol)?.def.name === "%DOMException") {
767
+ L.unsupported("SC1090", t, "extending DOMException (its runtime layout carries hidden slots a subclass would overlap — extend Error and set name/code yourself)");
768
+ }
769
+ const named = (symbol && L.classBySymbol.get(symbol)) ?? L.builtinErrorInfoOf(symbol) ??
770
+ L.builtinEmitterInfoOf(symbol) ?? builtinStreamInfoOf(L, symbol) ??
771
+ // A const BINDING holding exactly one class (`const B = Animal`,
772
+ // `const B = class {…}`): the base is that class — extends
773
+ // through the alias is the declaration story (a general class
774
+ // VALUE stays fenced: the runtime base would be dynamic).
775
+ exactClassOfReceiver(L, t.expression) ??
776
+ // A require BINDING of a class-expression whole export
777
+ // (`const C = require('./x')` over `module.exports = class {…}`):
778
+ // the alias resolves to the expression's own symbol — the same
779
+ // declaration story, collected on demand.
780
+ propertyAssignedClassInfoOf(L, symbol) ??
781
+ // A const BINDING of a mixin call (`const Tagged = M(Base);
782
+ // class D extends Tagged {}`): the binding pins that call's
783
+ // instantiation — collected on demand (lower-mixins.ts).
784
+ mixinResultBindingClassOf(L, symbol) ?? null;
785
+ // `extends Box<number>` — a GENERIC program class as the base: the
786
+ // base is the concrete INSTANTIATION, resolved through the heritage
787
+ // type (mapType registers/reuses `Box%0`).
788
+ if (named?.generic) {
789
+ const instT = L.checker.getTypeAtLocation(t);
790
+ const mappedBase = L.mapTypeOf(instT);
791
+ const instBase = mappedBase?.kind === "object" ? L.classes.get(mappedBase.className) : undefined;
792
+ if (!instBase || instBase.generic) {
793
+ L.unsupported("SC1090", t, `extending the generic class '${t.expression.text}' without a compiled concrete instantiation (the type arguments must map — see the instantiation's own diagnostic)`);
794
+ }
795
+ base = instBase;
796
+ continue;
797
+ }
798
+ if (t.typeArguments)
799
+ L.unsupported("SC1090", t, "extending generic classes");
800
+ base = named;
801
+ if (!base) {
802
+ L.unsupported("SC1090", t, `extending classes not declared in the program ('${t.expression.text}')`);
803
+ }
804
+ }
805
+ const fields = new Map(base ? base.fields : []);
806
+ const symbolFields = new Map(base?.symbolFields ?? []);
807
+ const fieldOrder = [];
808
+ const methods = new Map();
809
+ // Own accessor declarations ("get:x"/"set:x" → node), for the
810
+ // partial-override analysis below (diagnostics need the node).
811
+ const accessorNodes = new Map();
812
+ /** Non-override methods whose collected return stayed dyn — the
813
+ * symbol-slot refinement retries them after the constructor scan
814
+ * declares this class's OWN symbol-keyed slots. */
815
+ const dynRetMethods = new Map();
816
+ let ctor = null;
817
+ /** Initializer-less fields whose type cannot hold undefined and whose
818
+ * definite assignment tsc did NOT verify (a `!` assertion, or
819
+ * strictPropertyInitialization off) — checked against the
820
+ * constructor's top-level assignments after the member loop. */
821
+ const unguardedFields = [];
822
+ /** Parameter properties, in parameter order — spliced in FRONT of the
823
+ * declared fields after the member loop (Node's layout, probed: the
824
+ * transform hoists their definitions above every declared field). */
825
+ const paramProps = [];
826
+ const staticFields = [];
827
+ const staticMethods = new Map();
828
+ const staticBlocks = [];
829
+ // GENERIC methods (own type parameters), instance and static: only
830
+ // the SYNTAX is checked here — parameter/return types mention the
831
+ // type parameters and cannot map yet; bodies lower per call-site
832
+ // instantiation (collectGenericSignature's rule, member form). The
833
+ // `member.cls` backlink fills after the ClassInfo assembles below.
834
+ const genericMethods = new Map();
835
+ const genericStatics = new Map();
836
+ const collectGenericMember = (member, isStatic) => {
837
+ if (!ts.isIdentifier(member.name)) {
838
+ L.unsupported("SC1090", member, "computed generic method names");
839
+ }
840
+ if (member.asteriskToken)
841
+ L.unsupported("SC1071", member);
842
+ if (member.modifiers?.some((m) => m.kind === ts.SyntaxKind.AsyncKeyword)) {
843
+ L.unsupported("SC1090", member, "async methods");
844
+ }
845
+ const mName = member.name.text;
846
+ const typeParams = [];
847
+ for (const tp of member.typeParameters) {
848
+ const sym = L.checker.getSymbolAtLocation(tp.name);
849
+ if (!sym)
850
+ L.unsupported("SC1090", member, "this method form");
851
+ typeParams.push(sym);
852
+ }
853
+ for (const param of member.parameters) {
854
+ if (!ts.isIdentifier(param.name))
855
+ L.unsupported("SC1031", param);
856
+ }
857
+ (isStatic ? genericStatics : genericMethods).set(mName, {
858
+ decl: member,
859
+ baseName: mName,
860
+ qualifiedName: `%${className}.${isStatic ? "static:" : ""}${mName}`,
861
+ typeParams,
862
+ instances: new Map(),
863
+ });
864
+ };
865
+ for (const member of decl.members) {
866
+ if (ts.isClassStaticBlockDeclaration(member)) {
867
+ // Statics live on the FAMILY (JS has one class, one static
868
+ // storage, however many instantiations exist) — instantiations
869
+ // skip them.
870
+ if (inst)
871
+ continue;
872
+ // A static block is declaration-time CODE — Node runs it when the
873
+ // class statement evaluates, referenced or not — so it collects
874
+ // for %init lowering (lowerStaticFieldInits) instead of fencing.
875
+ // `this` (and super) inside the block means the class constructor
876
+ // value, which has no value form here: fenced at the reference,
877
+ // with arrow functions transparent (they inherit the block's
878
+ // `this`) and this-binding function forms opaque (their `this` is
879
+ // their own).
880
+ const checkThis = (n) => {
881
+ if (ts.isFunctionExpression(n) || ts.isFunctionDeclaration(n) ||
882
+ ts.isMethodDeclaration(n) || ts.isConstructorDeclaration(n) ||
883
+ ts.isGetAccessor(n) || ts.isSetAccessor(n) ||
884
+ ts.isClassDeclaration(n) || ts.isClassExpression(n)) {
885
+ return;
886
+ }
887
+ if (n.kind === ts.SyntaxKind.ThisKeyword || n.kind === ts.SyntaxKind.SuperKeyword) {
888
+ L.unsupported("SC1090", n, "'this' in class static blocks (it names the class — reference the class by name instead)");
889
+ }
890
+ n.forEachChild(checkThis);
891
+ };
892
+ member.body.forEachChild(checkThis);
893
+ staticBlocks.push(member);
894
+ continue;
895
+ }
896
+ const modifiers = ts.canHaveModifiers(member) ? ts.getModifiers(member) : undefined;
897
+ if (modifiers?.some((m) => m.kind === ts.SyntaxKind.StaticKeyword)) {
898
+ // Statics live on the FAMILY (one storage location for every
899
+ // instantiation — JS's one class); instantiations skip them and
900
+ // reach them through the base chain (findStaticOn).
901
+ if (inst)
902
+ continue;
903
+ // The honest static subset: a field WITH an initializer is a
904
+ // module global (mutable when not readonly) assigned once at
905
+ // the class statement's position in module init and read as
906
+ // `C.name` anywhere; a static METHOD is an ordinary module
907
+ // function `%C.static:m`. No per-class runtime property table
908
+ // exists, so the members that would need one — accessors, and
909
+ // initializer-less fields (undefined until someone assigns
910
+ // them) — keep the fence, each named at its use site.
911
+ if (ts.isPropertyDeclaration(member) &&
912
+ ts.isIdentifier(member.name) &&
913
+ member.initializer &&
914
+ member.postfixToken?.kind !== ts.SyntaxKind.QuestionToken) {
915
+ const type = L.irTypeOf(member.name);
916
+ if (type.kind === "void")
917
+ L.badType(member.name, L.typeOf(member.name));
918
+ if (type.kind === "dyn") {
919
+ L.unsupported("SC1090", member.name, "'unknown'-typed static fields");
920
+ }
921
+ staticFields.push({
922
+ name: member.name.text,
923
+ type,
924
+ initializer: member.initializer,
925
+ globalId: `%g.s.${L.classNamer(decl)}.${member.name.text}`,
926
+ readonly: modifiers.some((m) => m.kind === ts.SyntaxKind.ReadonlyKeyword),
927
+ });
928
+ }
929
+ // Async statics collect like any static method: the module
930
+ // function `%C.static:m` is an async IrFunction (fiber spawn
931
+ // wrapper), no vtable in sight — statics never dispatch.
932
+ if (ts.isMethodDeclaration(member) &&
933
+ ts.isIdentifier(member.name) &&
934
+ member.body &&
935
+ member.typeParameters === undefined &&
936
+ member.asteriskToken === undefined) {
937
+ const { shapes, funcType: ft } = L.lambdaSignature(member);
938
+ staticMethods.set(member.name.text, { params: shapes, ret: ft.ret, member });
939
+ }
940
+ // GENERIC static methods monomorphize like top-level generic
941
+ // functions (`%C.static:m%n`); async ones keep the async-method
942
+ // use-site fence below.
943
+ if (ts.isMethodDeclaration(member) &&
944
+ ts.isIdentifier(member.name) &&
945
+ member.body &&
946
+ member.typeParameters !== undefined &&
947
+ !member.modifiers?.some((m) => m.kind === ts.SyntaxKind.AsyncKeyword)) {
948
+ collectGenericMember(member, true);
949
+ }
950
+ // Statics that don't qualify for the module-global/module-
951
+ // function treatment (accessors, initializer-less fields,
952
+ // async/generic methods) never live on instances, so they must
953
+ // not poison the class either — constructions and instance
954
+ // members stay compilable, and each USE of an unsupported
955
+ // static fences at its own site.
956
+ continue;
957
+ }
958
+ // INSTANCE members of a generic class collect per instantiation
959
+ // (`inst` set, the type-parameter bindings threaded through every
960
+ // mapType) — the family declares none.
961
+ if (familyMode)
962
+ continue;
963
+ // 7's ClassElement base carries no `name`; read it structurally
964
+ // (every named member kind stores a PropertyName there).
965
+ const memberName = member.name;
966
+ if (memberName && ts.isPrivateIdentifier(memberName)) {
967
+ L.unsupported("SC1090", member, "#private class members");
968
+ }
969
+ // The EventEmitter API surface is runtime-provided: a subclass
970
+ // member with one of its names would shadow behavior the runtime
971
+ // dispatches internally (meta events, once removal), so the
972
+ // override is fenced rather than silently split-brained.
973
+ if (memberName && ts.isIdentifier(memberName) &&
974
+ EMITTER_API_MEMBERS.has(memberName.text) &&
975
+ (() => {
976
+ for (let c = base; c; c = c.base)
977
+ if (c.builtinEmitter)
978
+ return true;
979
+ return false;
980
+ })()) {
981
+ L.unsupported("SC1090", memberName, `overriding the EventEmitter member '${memberName.text}' (the runtime owns the emitter surface)`);
982
+ }
983
+ // The stream surface is likewise runtime-provided on stream-rooted
984
+ // subclasses: API members (push/read/write/...) and the property
985
+ // family (readableEnded/destroyed/...) dispatch into the runtime
986
+ // state, so an override or shadowing field would split-brain.
987
+ // Underscore methods are the SUPPORTED override form — but only
988
+ // the ones the class's own base consumes (a `_read` on a
989
+ // Transform, or `_writev`/`_construct` anywhere, would be consumed
990
+ // by Node machinery that has no lowering here).
991
+ if (memberName && ts.isIdentifier(memberName)) {
992
+ const streamBase = (() => {
993
+ for (let c = base; c; c = c.base)
994
+ if (c.builtinStream)
995
+ return c;
996
+ return null;
997
+ })();
998
+ if (streamBase) {
999
+ const name = memberName.text;
1000
+ if (STREAM_API_MEMBERS.has(name) || STREAM_PROP_MEMBERS.has(name)) {
1001
+ L.unsupported("SC1090", memberName, `overriding the stream member '${name}' (the runtime owns the stream surface)`);
1002
+ }
1003
+ if (name === "_writev" || name === "_construct") {
1004
+ L.unsupported("SC1090", memberName, `declaring '${name}' on a stream subclass (${name === "_writev" ? "batched writes are" : "deferred construction is"} not lowered — writes deliver one chunk at a time)`);
1005
+ }
1006
+ const accepted = streamCtorShape(streamBase.def.name).accepted;
1007
+ for (const [option, methodName] of UNDERSCORE_METHODS) {
1008
+ if (name === methodName && !accepted.includes(option)) {
1009
+ L.unsupported("SC1090", memberName, `declaring '${name}' on a ${streamBase.def.name.slice(1)} subclass (its constructor consumes ${accepted.map((a) => `'${UNDERSCORE_METHODS.get(a)}'`).join("/")})`);
1010
+ }
1011
+ }
1012
+ }
1013
+ }
1014
+ if (ts.isPropertyDeclaration(member)) {
1015
+ // An ABSTRACT property declaration is erased at runtime — Node
1016
+ // defines NO field for it (verified: `abstract p: number` in the
1017
+ // base leaves the concrete subclass's own `p = 3` as the only
1018
+ // property, at the SUBCLASS's position in inspect order). So it
1019
+ // contributes nothing to the layout; the concrete subclass's
1020
+ // declaration is an ordinary OWN field (tsc guarantees every
1021
+ // instantiable subclass declares it and, under
1022
+ // strictPropertyInitialization, initializes it). Reads through
1023
+ // ABSTRACT-typed receivers have no slot to read and keep a
1024
+ // per-site fence.
1025
+ if (modifiers?.some((m) => m.kind === ts.SyntaxKind.AbstractKeyword))
1026
+ continue;
1027
+ if (modifiers?.some((m) => m.kind === ts.SyntaxKind.AccessorKeyword)) {
1028
+ // `accessor x = 1` desugars (in JS) to a private slot plus a
1029
+ // get/set pair — declare the field and accessors explicitly.
1030
+ L.unsupported("SC1090", member, "auto-accessor fields ('accessor x')");
1031
+ }
1032
+ if (!ts.isIdentifier(member.name))
1033
+ L.unsupported("SC1090", member, "computed field names");
1034
+ // OPTIONAL fields (`a?: string`) are the record-field precedent
1035
+ // applied to class shapes: the checker already types the slot
1036
+ // `string | undefined`, the allocation writes the interned
1037
+ // undefined arm (undefFieldInitLineC — Node defines the property
1038
+ // as undefined on construction, verified), and reads/writes ride
1039
+ // the ordinary undefined-armed union machinery.
1040
+ const type = L.irTypeOf(member.name);
1041
+ if (type.kind === "void")
1042
+ L.badType(member.name, L.typeOf(member.name));
1043
+ // dyn stays out of class fields (KEEP NARROW; record
1044
+ // fields and array elements are unmappable via mapType already).
1045
+ if (type.kind === "dyn") {
1046
+ L.unsupported("SC1090", member.name, "'unknown'-typed class fields");
1047
+ }
1048
+ if (fields.has(member.name.text)) {
1049
+ L.unsupported("SC1090", member.name, "redeclaring inherited fields");
1050
+ }
1051
+ if (L.findMethodOn(base, member.name.text)) {
1052
+ L.unsupported("SC1090", member.name, "fields shadowing inherited methods");
1053
+ }
1054
+ // Initializer-less fields whose type ADMITS undefined start as
1055
+ // JS's undefined (the allocation writes the interned undefined
1056
+ // arm — see the backend's undefFieldInitLineC), exactly Node's
1057
+ // fresh-instance read. A field whose type CANNOT hold undefined
1058
+ // has no honest pre-assignment value in these monomorphic
1059
+ // layouts — zeroed memory would read as garbage (0, NULL) where
1060
+ // Node reads undefined — so it needs a definite-assignment
1061
+ // guarantee. tsc's strictPropertyInitialization is that
1062
+ // guarantee; where the program waives it — a `x!: T` assertion,
1063
+ // or a project tsconfig with the option off (scriptc adopts the
1064
+ // project's strictness knobs) — the field goes on the deferred
1065
+ // list checked against the constructor after the member loop
1066
+ // (the constructor may be declared later in the class body).
1067
+ const admitsUndefined = (type.kind === "union" &&
1068
+ (L.unions.get(type.unionId)?.arms.some((a) => a.kind === "undefinedT") ?? false)) ||
1069
+ type.kind === "jsval";
1070
+ if (!member.initializer && !admitsUndefined) {
1071
+ const opts = L.program.getCompilerOptions();
1072
+ const spi = opts.strictPropertyInitialization ?? opts.strict ?? false;
1073
+ if (member.postfixToken?.kind === ts.SyntaxKind.ExclamationToken) {
1074
+ unguardedFields.push({
1075
+ node: member.name,
1076
+ name: member.name.text,
1077
+ why: `definite assignment assertions on fields not assigned at the constructor's top level ('${member.name.text}!' defers the first assignment past construction — the field would hold garbage, not undefined, until it runs; assign it in the constructor or include undefined in its type)`,
1078
+ });
1079
+ }
1080
+ else if (!spi) {
1081
+ unguardedFields.push({
1082
+ node: member.name,
1083
+ name: member.name.text,
1084
+ why: `initializer-less fields not assigned at the constructor's top level when strictPropertyInitialization is off (nothing guarantees '${member.name.text}' is assigned before a read — enable the option, assign it unconditionally at the top of the constructor, or include undefined in its type)`,
1085
+ });
1086
+ }
1087
+ }
1088
+ fields.set(member.name.text, type);
1089
+ fieldOrder.push({ name: member.name.text, type, initializer: member.initializer });
1090
+ }
1091
+ else if (ts.isConstructorDeclaration(member)) {
1092
+ // A body-less constructor is an OVERLOAD SIGNATURE: type-world,
1093
+ // lowers to nothing — tsc resolved each `new` against the
1094
+ // signatures, and construction flows through the implementation's
1095
+ // ABI (its parameter types are supersets by the
1096
+ // overload-compatibility rules).
1097
+ if (!member.body)
1098
+ continue;
1099
+ if (ctor)
1100
+ L.unsupported("SC1090", member, "constructor overloads");
1101
+ // PARAMETER PROPERTIES (`constructor(public x: number)`): pure
1102
+ // sugar — the parameter declares a field and assigns it from the
1103
+ // parameter's value. Visibility (public/private/protected) and
1104
+ // readonly/override are type-world; the field is an ordinary
1105
+ // property at runtime. The field's type is the parameter's BODY
1106
+ // type (paramShape's contract: the plain T of a defaulted
1107
+ // `public x = e`, the `T | undefined` union of `public x?: T`) —
1108
+ // exactly what the ctor's body local carries, so the synthesized
1109
+ // assignment is slot-exact. Layout/inspect position and
1110
+ // assignment order are Node's, probed exactly: the fields define
1111
+ // FIRST (before every declared field, as undefined), and the
1112
+ // assignments run after super() and the field initializers, in
1113
+ // parameter order (see paramPropInitStmts).
1114
+ for (const p of member.parameters) {
1115
+ const isParamProp = p.modifiers?.some((m) => m.kind === ts.SyntaxKind.PublicKeyword ||
1116
+ m.kind === ts.SyntaxKind.PrivateKeyword ||
1117
+ m.kind === ts.SyntaxKind.ProtectedKeyword ||
1118
+ m.kind === ts.SyntaxKind.ReadonlyKeyword ||
1119
+ m.kind === ts.SyntaxKind.OverrideKeyword);
1120
+ if (!isParamProp) {
1121
+ // Non-keyword modifiers (parameter decorators) are rejected
1122
+ // by tsc under standard decorators; defensive.
1123
+ if (p.modifiers?.length)
1124
+ L.unsupported("SC1090", p, "this parameter form");
1125
+ continue;
1126
+ }
1127
+ // tsc rejects binding patterns (TS1187) and rest params
1128
+ // (TS1317) as parameter properties; defensive.
1129
+ if (!ts.isIdentifier(p.name) || p.dotDotDotToken) {
1130
+ L.unsupported("SC1090", p, "this parameter property form");
1131
+ }
1132
+ const name = p.name.text;
1133
+ const shape = L.paramShape(p);
1134
+ const type = shape.bodyType ?? shape.type;
1135
+ if (type.kind === "void")
1136
+ L.badType(p.name, L.typeOf(p.name));
1137
+ // The class-field dyn rule verbatim (KEEP NARROW).
1138
+ if (type.kind === "dyn") {
1139
+ L.unsupported("SC1090", p.name, "'unknown'-typed class fields");
1140
+ }
1141
+ // `override x` (and any same-named inherited member) would
1142
+ // redeclare a base slot — the declared-field rule verbatim.
1143
+ if (fields.has(name)) {
1144
+ L.unsupported("SC1090", p.name, "redeclaring inherited fields");
1145
+ }
1146
+ if (L.findMethodOn(base, name)) {
1147
+ L.unsupported("SC1090", p.name, "fields shadowing inherited methods");
1148
+ }
1149
+ paramProps.push({ name, type, param: p });
1150
+ }
1151
+ ctor = member;
1152
+ }
1153
+ else if (ts.isMethodDeclaration(member)) {
1154
+ const mName = classMemberNameOf(L, member.name);
1155
+ if (mName === null)
1156
+ L.unsupported("SC1090", member, "computed method names");
1157
+ // Generator METHODS stay fenced (virtualCall dispatch over
1158
+ // gen-spawn wrappers has no story yet); module-level function*
1159
+ // and object-literal *methods compile.
1160
+ if (member.asteriskToken !== undefined) {
1161
+ L.unsupported("SC1071", member, "generator methods (declare a module-level function* and call it from the method)");
1162
+ }
1163
+ // An ABSTRACT method is a signature with no body — type-world,
1164
+ // except that it declares the vtable slot: calls through
1165
+ // base-typed receivers are ordinary virtual dispatch, and tsc
1166
+ // guarantees every instantiable subclass implements it (so a
1167
+ // dispatch can never land on the empty declaration). It enters
1168
+ // `methods` (marked abstract) for slot declaration and the
1169
+ // override-exactness rule; no module function ever exists.
1170
+ if (ts.getModifiers(member)?.some((m) => m.kind === ts.SyntaxKind.AbstractKeyword)) {
1171
+ // A GENERIC abstract method has no body to monomorphize —
1172
+ // per-call-site instantiation needs a nearest declarer WITH a
1173
+ // body, which an abstract declaration never has.
1174
+ if (member.typeParameters !== undefined) {
1175
+ L.unsupported("SC1090", member, "abstract generic methods (generic methods monomorphize from the nearest declaration's body, and an abstract declaration has none)");
1176
+ }
1177
+ const { shapes, ret } = abstractMemberSignature(L, member);
1178
+ if (fields.has(mName)) {
1179
+ L.unsupported("SC1090", member.name, "methods shadowing inherited fields");
1180
+ }
1181
+ if (findGenericMethodOn(L, base, mName)) {
1182
+ L.unsupported("SC1090", member.name, `overriding the inherited generic method '${mName}' with a non-generic method (generic methods dispatch statically and would never reach this override)`);
1183
+ }
1184
+ // Abstract re-declarations keep the overridden ABI exactly,
1185
+ // like any override (a concrete implementation below must
1186
+ // agree with BOTH, which exactness makes one constraint).
1187
+ const overridden = L.findMethodOn(base, mName);
1188
+ if (overridden &&
1189
+ (overridden.sig.params.length !== shapes.length ||
1190
+ !overridden.sig.params.every((p, i) => typeEquals(p.type, shapes[i].type)) ||
1191
+ !typeEquals(overridden.sig.ret, ret))) {
1192
+ L.unsupported("SC1090", member.name, "overriding a method with a different signature (parameter and return types must match the base declaration exactly)");
1193
+ }
1194
+ methods.set(mName, { params: shapes, ret, abstract: true });
1195
+ continue;
1196
+ }
1197
+ // A body-less method is an OVERLOAD SIGNATURE (abstract methods
1198
+ // collected above): type-world, exactly the constructor story.
1199
+ if (!member.body)
1200
+ continue;
1201
+ // GENERIC methods (own type parameters): collected aside — never
1202
+ // in `methods` (no single ABI signature, no vtable slot); bodies
1203
+ // lower per call-site instantiation as `%C.m%n`. Mixing generic
1204
+ // and non-generic declarations of one name across the hierarchy
1205
+ // fences (the two dispatch worlds — static per-instantiation
1206
+ // calls vs vtable slots — cannot see each other's overrides).
1207
+ if (member.typeParameters !== undefined) {
1208
+ if (fields.has(mName)) {
1209
+ L.unsupported("SC1090", member.name, "methods shadowing inherited fields");
1210
+ }
1211
+ if (L.findMethodOn(base, mName)) {
1212
+ L.unsupported("SC1090", member.name, `overriding the inherited method '${mName}' with a generic method (generic methods dispatch statically, so the base's vtable slot could never reach this override)`);
1213
+ }
1214
+ collectGenericMember(member, false);
1215
+ continue;
1216
+ }
1217
+ // Async METHODS in JS classes simply do not COLLECT — each call
1218
+ // fences at its own site (the JS deferral stance, the
1219
+ // async-static precedent above), so a class whose driven
1220
+ // surface is synchronous still compiles (commander: parse()
1221
+ // works, parseAsync() traps where called). TS async methods
1222
+ // collect below like any method: the body is an async
1223
+ // IrFunction (fiber spawn wrapper, `this` as param 0), calls
1224
+ // dispatch STATICALLY — override chains fence (the vtable slot
1225
+ // machinery has no fiber-spawn story), so every call site is a
1226
+ // direct call the emitter routes through the spawn wrapper.
1227
+ if (member.modifiers?.some((m) => m.kind === ts.SyntaxKind.AsyncKeyword) &&
1228
+ isJsSourceFile(decl.getSourceFile())) {
1229
+ continue;
1230
+ }
1231
+ // IMPLICIT-ANY monomorphization (npm-static JS): a method whose
1232
+ // signature carries bindable untyped params collects like a
1233
+ // GENERIC method — into genericMethods, no vtable slot, one
1234
+ // instance per call-site type tuple (the untyped params ARE the
1235
+ // type parameters; see lower-calls' implicit section). DECLINES
1236
+ // (falls through to the normal all-dyn ABI) wherever the two
1237
+ // dispatch worlds could meet: an inherited declaration of the
1238
+ // name (the override stays on the vtable), a shadowed field, or
1239
+ // a generic-class instantiation's member.
1240
+ if (implicitMonoFile(decl.getSourceFile()) &&
1241
+ ts.isIdentifier(member.name) &&
1242
+ inst === undefined && decl.typeParameters === undefined &&
1243
+ !fields.has(member.name.text) &&
1244
+ !L.findMethodOn(base, member.name.text) &&
1245
+ !findGenericMethodOn(L, base, member.name.text)) {
1246
+ const implicit = implicitAnyParamSymbolsOf(L, member);
1247
+ if (implicit) {
1248
+ genericMethods.set(member.name.text, {
1249
+ decl: member,
1250
+ baseName: member.name.text,
1251
+ qualifiedName: `%${className}.${member.name.text}`,
1252
+ typeParams: [],
1253
+ instances: new Map(),
1254
+ implicitParams: implicit,
1255
+ });
1256
+ continue;
1257
+ }
1258
+ }
1259
+ const { shapes, funcType: ft } = L.lambdaSignature(member);
1260
+ if (fields.has(mName)) {
1261
+ L.unsupported("SC1090", member.name, "methods shadowing inherited fields");
1262
+ }
1263
+ if (findGenericMethodOn(L, base, mName)) {
1264
+ L.unsupported("SC1090", member.name, `overriding the inherited generic method '${mName}' with a non-generic method (generic methods dispatch statically and would never reach this override)`);
1265
+ }
1266
+ // Symbol-slot return refinement, INHERITED slots (own ctor-declared
1267
+ // slots refine in the post-scan pass below — the constructor hasn't
1268
+ // been scanned yet here, but base slots are already in
1269
+ // symbolFields/fields). Doing it before the exactness check keeps
1270
+ // a derived override of a refined base method agreeing.
1271
+ if (ft.ret.kind === "dyn") {
1272
+ const refined = symbolSlotReturnType(L, member, symbolFields, fields);
1273
+ if (refined)
1274
+ ft.ret = refined;
1275
+ }
1276
+ // Overrides keep the EXACT overridden ABI signature. tsc's method
1277
+ // bivariance would let a narrowed parameter type through, and a
1278
+ // vtable-dispatched call could then hand the override a base
1279
+ // instance it reads out-of-bounds fields from — exactness keeps
1280
+ // every slot sound (covariant returns can come later). Comparing
1281
+ // ABI types only (not modes) is deliberate: call sites complete
1282
+ // against the STATIC receiver's shape, so `m(x?: number)` and
1283
+ // `m(x: number | undefined)` interchange soundly in overrides.
1284
+ const overridden = L.findMethodOn(base, mName);
1285
+ if (overridden?.declarer.builtinError) {
1286
+ // Error.prototype.toString is a runtime implementation with no
1287
+ // vtable slot — calls to it are direct, so an override could
1288
+ // never be reached through a base-typed receiver.
1289
+ L.unsupported("SC1090", member.name, `overriding the builtin Error method '${mName}'`);
1290
+ }
1291
+ if (overridden &&
1292
+ (overridden.sig.params.length !== shapes.length ||
1293
+ !overridden.sig.params.every((p, i) => typeEquals(p.type, shapes[i].type)) ||
1294
+ !typeEquals(overridden.sig.ret, ft.ret))) {
1295
+ L.unsupported("SC1090", member.name, "overriding a method with a different signature (parameter and return types must match the base declaration exactly)");
1296
+ }
1297
+ const asyncMember = member.modifiers?.some((m) => m.kind === ts.SyntaxKind.AsyncKeyword) === true;
1298
+ // Async methods dispatch STATICALLY (the body enters through its
1299
+ // fiber spawn wrapper; vtable slots hold raw implementations) —
1300
+ // an override chain touching an async method on either end would
1301
+ // put a spawn wrapper behind a virtual slot, so it fences.
1302
+ if (overridden && (asyncMember || overridden.sig.async === true)) {
1303
+ L.unsupported("SC1090", member.name, `overriding ${overridden.sig.async === true ? "the async method" : "a method with an async method"} '${mName}' (async methods dispatch statically — the vtable slot machinery has no fiber-spawn story)`);
1304
+ }
1305
+ methods.set(mName, asyncMember ? { params: shapes, ret: ft.ret, async: true } : { params: shapes, ret: ft.ret });
1306
+ // Overrides keep the inherited ABI exactly, so only non-override
1307
+ // methods may still refine once the ctor scan runs.
1308
+ if (ft.ret.kind === "dyn" && !overridden) {
1309
+ dynRetMethods.set(mName, member);
1310
+ }
1311
+ }
1312
+ else if (ts.isGetAccessor(member) || ts.isSetAccessor(member)) {
1313
+ // Accessors are methods with property syntax: `get x()` collects
1314
+ // as the method entry "get:x" (a name no user identifier can
1315
+ // spell, so it can never collide with a real method) and `set x`
1316
+ // as "set:x" — every downstream mechanism (override exactness,
1317
+ // whole-program devirtualization, vtable slots, may-throw) then
1318
+ // applies verbatim, with the get and set halves independent.
1319
+ const isGet = ts.isGetAccessor(member);
1320
+ if (!ts.isIdentifier(member.name)) {
1321
+ L.unsupported("SC1090", member, "computed accessor names");
1322
+ }
1323
+ // ABSTRACT accessors are the abstract-method story with property
1324
+ // syntax: body-less by definition, they enter `methods` (marked
1325
+ // abstract) as their "get:x"/"set:x" halves — slot declaration
1326
+ // and override exactness verbatim; no module function exists.
1327
+ const abstractAccessor = ts.getModifiers(member)?.some((m) => m.kind === ts.SyntaxKind.AbstractKeyword) === true;
1328
+ if (!member.body && !abstractAccessor)
1329
+ L.unsupported("SC1090", member, "bodyless accessors");
1330
+ const prop = member.name.text;
1331
+ const mName = `${isGet ? "get" : "set"}:${prop}`;
1332
+ if (fields.has(prop)) {
1333
+ // tsc rejects field/accessor mixing (TS2610/2611); defensive.
1334
+ L.unsupported("SC1090", member.name, "accessors sharing a name with a field");
1335
+ }
1336
+ let sig;
1337
+ if (isGet) {
1338
+ const ret = L.declaredReturnType(member, member.name);
1339
+ if (ret.kind === "void")
1340
+ L.badType(member.name, L.typeOf(member.name));
1341
+ sig = { params: [], ret };
1342
+ }
1343
+ else {
1344
+ // tsc rejects optional/default/rest setter params (TS1051-53).
1345
+ sig = { params: L.paramShapes(member.parameters), ret: VOID };
1346
+ }
1347
+ // One property, ONE type: tsc (5.1+) admits get/set pairs with
1348
+ // unrelated annotated types; a property slot here has a single
1349
+ // IR type, so the pair must agree exactly.
1350
+ const twin = methods.get(`${isGet ? "set" : "get"}:${prop}`);
1351
+ const twinType = twin ? (isGet ? twin.params[0].type : twin.ret) : null;
1352
+ const ownType = isGet ? sig.ret : sig.params[0].type;
1353
+ if (twinType && !typeEquals(twinType, ownType)) {
1354
+ L.unsupported("SC1090", member.name, `getter/setter pairs with different types (the property '${prop}' must have one type)`);
1355
+ }
1356
+ // Same exactness rule as methods — an accessor override keeps
1357
+ // the overridden accessor's type (getter return / setter param).
1358
+ const overridden = L.findMethodOn(base, mName);
1359
+ if (overridden &&
1360
+ (overridden.sig.params.length !== sig.params.length ||
1361
+ !overridden.sig.params.every((p, i) => typeEquals(p.type, sig.params[i].type)) ||
1362
+ !typeEquals(overridden.sig.ret, sig.ret))) {
1363
+ L.unsupported("SC1090", member.name, "overriding an accessor with a different type (the property type must match the base declaration exactly)");
1364
+ }
1365
+ methods.set(mName, abstractAccessor ? { ...sig, abstract: true } : sig);
1366
+ // Abstract accessors stay OUT of accessorNodes: they are erased
1367
+ // at runtime (nothing shadows an inherited pair, nothing needs a
1368
+ // synthesized throwing setter) — the partial-override analysis
1369
+ // below reasons about accessors that EXIST on the instance.
1370
+ if (!abstractAccessor)
1371
+ accessorNodes.set(mName, member);
1372
+ }
1373
+ else if (ts.isIndexSignatureDeclaration(member)) {
1374
+ L.unsupported("SC1090", member, "index signatures");
1375
+ }
1376
+ else if (!ts.isSemicolonClassElement(member)) {
1377
+ L.unsupported("SC1090", member, `syntax '${ts.SyntaxKind[member.kind]}'`);
1378
+ }
1379
+ }
1380
+ // Parameter properties join the shape FIRST among own fields —
1381
+ // Node's transform hoists their definitions above every declared
1382
+ // field (probed: `constructor(public x, private w)` after a declared
1383
+ // `z` still prints `{ x, w, z }`), so layout/inspect order follows.
1384
+ // No definite-assignment analysis applies: the constructor assigns
1385
+ // them unconditionally (paramPropInitStmts).
1386
+ if (paramProps.length > 0) {
1387
+ for (const pp of paramProps)
1388
+ fields.set(pp.name, pp.type);
1389
+ fieldOrder.unshift(...paramProps.map((pp) => ({ name: pp.name, type: pp.type, initializer: undefined })));
1390
+ }
1391
+ // The deferred definite-assignment check: a field on the unguarded
1392
+ // list passes only with an unconditional `this.x = ...` at the
1393
+ // constructor's TOP LEVEL — the same standard the JS-class path
1394
+ // below applies to constructor-declared fields. Anything less
1395
+ // (conditional branches, assignment in a method, no constructor at
1396
+ // all) leaves a window where Node reads undefined and these layouts
1397
+ // would read zeroed memory, so it fences instead.
1398
+ if (unguardedFields.length > 0) {
1399
+ const topAssigned = new Set();
1400
+ for (const stmt of ctor?.body?.statements ?? []) {
1401
+ if (ts.isExpressionStatement(stmt) &&
1402
+ ts.isBinaryExpression(stmt.expression) &&
1403
+ stmt.expression.operatorToken.kind === ts.SyntaxKind.EqualsToken &&
1404
+ ts.isPropertyAccessExpression(stmt.expression.left) &&
1405
+ stmt.expression.left.expression.kind === ts.SyntaxKind.ThisKeyword) {
1406
+ topAssigned.add(stmt.expression.left.name.text);
1407
+ }
1408
+ }
1409
+ for (const f of unguardedFields) {
1410
+ if (!topAssigned.has(f.name))
1411
+ L.unsupported("SC1090", f.node, f.why);
1412
+ }
1413
+ }
1414
+ // Partial overrides of an inherited accessor pair. JS gives the
1415
+ // derived class ONE own accessor property that SHADOWS the whole
1416
+ // inherited pair — the missing half does NOT resolve to the base's
1417
+ // (verified against Node):
1418
+ // - getter-only override where the chain has a setter: a write
1419
+ // through a base-typed reference (tsc-clean — the base has a
1420
+ // setter) throws TypeError at runtime. Matched exactly: a
1421
+ // synthesized throwing setter fills the derived class's slot.
1422
+ // - setter-only override where the chain has a getter: a read
1423
+ // through a base-typed reference yields undefined — a value
1424
+ // these property types cannot represent. Rejected.
1425
+ const throwingSetters = [];
1426
+ for (const [mName, node] of accessorNodes) {
1427
+ const prop = mName.slice(4);
1428
+ if (mName.startsWith("get:") && !accessorNodes.has(`set:${prop}`)) {
1429
+ // An ABSTRACT inherited setter is erased at runtime — there is
1430
+ // no accessor pair to shadow, so no throwing setter to
1431
+ // synthesize (tsc makes an instantiable class implement it, and
1432
+ // that implementation shadows nothing either).
1433
+ const baseSet = L.findMethodOn(base, `set:${prop}`);
1434
+ if (baseSet && baseSet.sig.abstract !== true) {
1435
+ if (!typeEquals(baseSet.sig.params[0].type, methods.get(mName).ret)) {
1436
+ // Unreachable when the base pair agrees (induction through
1437
+ // the exactness rule); a base setter-only + new getter of a
1438
+ // different type would break the slot signature.
1439
+ L.unsupported("SC1090", node.name, "accessors whose getter and inherited setter types differ");
1440
+ }
1441
+ methods.set(`set:${prop}`, { params: [baseSet.sig.params[0]], ret: VOID });
1442
+ throwingSetters.push(prop);
1443
+ }
1444
+ }
1445
+ if (mName.startsWith("set:") && !accessorNodes.has(`get:${prop}`)) {
1446
+ // The abstract-inherited-getter case is the same erasure story.
1447
+ const baseGet = L.findMethodOn(base, `get:${prop}`);
1448
+ if (baseGet && baseGet.sig.abstract !== true) {
1449
+ L.unsupported("SC1090", node.name, `overriding only the setter of an inherited accessor pair (JS shadows the inherited getter — reads of '${prop}' would yield undefined; declare the getter too)`);
1450
+ }
1451
+ }
1452
+ }
1453
+ // JavaScript classes declare fields by ASSIGNMENT: `this.x = v` in
1454
+ // the constructor IS the declaration (checkJs infers the property —
1455
+ // its type is the checker's, exactly like an annotated field). The
1456
+ // supported form is a definite assignment at the constructor's TOP
1457
+ // LEVEL, in source order — the layout is then as fixed as a TS field
1458
+ // list and the assignment itself doubles as the initializer (fields
1459
+ // are zero until the ctor body runs, same as TS's ctor-assigned
1460
+ // declared fields). Properties the checker infers from anywhere else
1461
+ // (conditional branches, methods) would be readable before any
1462
+ // assignment ran — the zeroed-memory trap a TS declaration order
1463
+ // forbids via strictPropertyInitialization — so they keep a named
1464
+ // fence instead of a silent undefined.
1465
+ if (isJsSourceFile(decl.getSourceFile())) {
1466
+ // Named classes (declarations and self-binding expressions) resolve
1467
+ // by name; the nameless default-export declaration by its module's
1468
+ // default-export symbol.
1469
+ const classSym = decl.name ? L.checker.getSymbolAtLocation(decl.name) : ts.isClassDeclaration(decl) ? declSymbolOf(L, decl) : undefined;
1470
+ const instType = classSym ? L.checker.getDeclaredTypeOfSymbol(classSym) : undefined;
1471
+ // LATE-BOUND properties (the checker's `__@name@id` spelling —
1472
+ // `this[kLimit] = v` where kLimit is a unique symbol const) by
1473
+ // their KEY symbol: the scan below needs the checker's property
1474
+ // type (tsc types computed declarations with unique-symbol keys
1475
+ // statically, exactly like named ones) but getSymbolAtLocation
1476
+ // answers null on element-access declaration sites, so the link
1477
+ // goes through the key.
1478
+ const lateBoundByKey = new Map();
1479
+ for (const p of instType ? L.checker.getPropertiesOfType(instType) : []) {
1480
+ if (!p.name.startsWith("__@"))
1481
+ continue;
1482
+ const keySym = lateBoundKeySymOf(L, p);
1483
+ if (keySym)
1484
+ lateBoundByKey.set(keySym, p);
1485
+ }
1486
+ if (ctor?.body) {
1487
+ for (const stmt of ctor.body.statements) {
1488
+ const lhs = ts.isExpressionStatement(stmt) &&
1489
+ ts.isBinaryExpression(stmt.expression) &&
1490
+ stmt.expression.operatorToken.kind === ts.SyntaxKind.EqualsToken
1491
+ ? stmt.expression.left
1492
+ : null;
1493
+ if (lhs && ts.isPropertyAccessExpression(lhs) &&
1494
+ lhs.expression.kind === ts.SyntaxKind.ThisKeyword) {
1495
+ const assign = lhs;
1496
+ const name = assign.name.text;
1497
+ // Later assignments to an already-declared field (own or
1498
+ // inherited) are writes, not declarations.
1499
+ if (fields.has(name))
1500
+ continue;
1501
+ if (methods.has(name) || L.findMethodOn(base, name)) {
1502
+ L.unsupported("SC1090", assign, "constructor-assigned fields shadowing methods");
1503
+ }
1504
+ const sym = L.checker.getSymbolAtLocation(assign);
1505
+ const t = sym ? L.checker.getTypeOfSymbol(sym) : undefined;
1506
+ // Implicit-any fields (assigned from UNTYPED ctor params —
1507
+ // countdown.js's `this.limit = limit`) take the JS checked-
1508
+ // dynamic fallback like every JS binding: the slot holds a dyn
1509
+ // box, reads validate per use, writes convert in (dynFrom).
1510
+ // TS-annotated `unknown` fields keep their fence (KEEP NARROW
1511
+ // applies where an annotation could say better).
1512
+ let type = t ? (L.mapTypeOf(t) ?? dynFallbackType(L, assign, t)) : null;
1513
+ if (!type || type.kind === "void")
1514
+ L.badType(assign, t ?? L.typeOf(assign));
1515
+ // A JSDoc claim the BODY contradicts (`@type {Command}`
1516
+ // assigned `undefined` — the lazy-init idiom): the
1517
+ // representation follows the body — the field widens to the
1518
+ // undefined-armed union, so the declaring assignment and
1519
+ // every pre-init read carry Node's actual undefined.
1520
+ // Trust-but-verify: the claim never silently narrows the
1521
+ // runtime value.
1522
+ {
1523
+ const rhsT = L.typeOf(stmt.expression.right);
1524
+ const assignsUndef = (rhsT.flags & ts.TypeFlags.Undefined) !== 0;
1525
+ const admitsUndef = type.kind === "dyn" ||
1526
+ isUnitType(type) ||
1527
+ (type.kind === "union" && L.armTag(type.unionId, UNDEFINED_T) >= 0);
1528
+ if (assignsUndef && !admitsUndef) {
1529
+ const widened = L.withUndefinedArmOf(type);
1530
+ if (widened !== null)
1531
+ type = widened;
1532
+ }
1533
+ }
1534
+ fields.set(name, type);
1535
+ fieldOrder.push({ name, type, initializer: undefined });
1536
+ continue;
1537
+ }
1538
+ // `this[kLimit] = v` at the constructor's top level with a
1539
+ // STATICALLY-RESOLVABLE unique-symbol key (uniqueSymbolKeyOf's
1540
+ // contract — the countdown.js idiom): the key is a compile-time
1541
+ // identity, so the member is an ordinary hidden field of the
1542
+ // static layout under Node's inspect spelling; no runtime
1543
+ // symbol table exists. Its type is the checker's late-bound
1544
+ // property type, through the same JS checked-dynamic fallback
1545
+ // as named fields. Keys that DON'T resolve fall through to the
1546
+ // late-bound fence below.
1547
+ if (lhs && ts.isElementAccessExpression(lhs) &&
1548
+ lhs.expression.kind === ts.SyntaxKind.ThisKeyword) {
1549
+ const key = uniqueSymbolKeyOf(L, lhs.argumentExpression);
1550
+ if (!key)
1551
+ continue;
1552
+ // A key already declared (own or inherited) makes later
1553
+ // assignments writes, not declarations.
1554
+ if (symbolFields.has(key.sym))
1555
+ continue;
1556
+ if (fields.has(key.fieldName)) {
1557
+ // Two DISTINCT Symbol(...) consts with one description in
1558
+ // one layout would need one printable name for two slots.
1559
+ L.unsupported("SC1090", lhs, `distinct symbol keys sharing the printable name '${key.fieldName}' in one class`);
1560
+ }
1561
+ const propSym = lateBoundByKey.get(key.sym);
1562
+ // tsgo does not synthesize the late-bound `__@name@id`
1563
+ // property for a JS `this[k] = v` declaration (the finding-5
1564
+ // family: no expando/late-bound synthesis in its stricter
1565
+ // CJS-JS modeling), so when the key found no property the
1566
+ // field's type comes from the SAME inference source 5.9.3's
1567
+ // property type did — the declaring assignment's RHS, widened.
1568
+ const rhs = ts.isExpressionStatement(stmt) && ts.isBinaryExpression(stmt.expression)
1569
+ ? stmt.expression.right
1570
+ : undefined;
1571
+ const t = propSym
1572
+ ? L.checker.getTypeOfSymbol(propSym)
1573
+ : rhs
1574
+ ? L.checker.getBaseTypeOfLiteralType(L.checker.getTypeAtLocation(rhs))
1575
+ : undefined;
1576
+ const type = t ? (L.mapTypeOf(t) ?? dynFallbackType(L, lhs, t)) : null;
1577
+ if (!type || type.kind === "void")
1578
+ L.badType(lhs, t ?? L.typeOf(lhs));
1579
+ fields.set(key.fieldName, type);
1580
+ symbolFields.set(key.sym, key.fieldName);
1581
+ fieldOrder.push({ name: key.fieldName, type, initializer: undefined });
1582
+ }
1583
+ }
1584
+ }
1585
+ // Every OTHER inferred instance property — assigned only in
1586
+ // methods, only in conditional constructor positions, or via
1587
+ // computed keys — is undefined until its first write, which these
1588
+ // static layouts cannot represent. Named fence, at the first
1589
+ // assignment site.
1590
+ for (const p of instType ? L.checker.getPropertiesOfType(instType) : []) {
1591
+ if (fields.has(p.name) || methods.has(p.name))
1592
+ continue;
1593
+ if (methods.has(`get:${p.name}`) || methods.has(`set:${p.name}`))
1594
+ continue;
1595
+ if (base && (base.fields.has(p.name) || L.findMethodOn(base, p.name)))
1596
+ continue;
1597
+ const site = L.checker.declarationsOf(p).find((d) => ts.isPropertyAccessExpression(d) || ts.isBinaryExpression(d) ||
1598
+ ts.isElementAccessExpression(d));
1599
+ if (!site)
1600
+ continue;
1601
+ // Late-bound properties: the ones the scan above collected are
1602
+ // real fields under their printable names — skip. The rest keep
1603
+ // a fence that names the supported form: keys that are runtime
1604
+ // identities (symbol parameters, Symbol.for consts, computed
1605
+ // descriptions) or assignments outside the constructor's top
1606
+ // level.
1607
+ if (p.name.startsWith("__@")) {
1608
+ const keySym = lateBoundKeySymOf(L, p);
1609
+ if (keySym && symbolFields.has(keySym))
1610
+ continue;
1611
+ L.unsupported("SC1090", site, "symbol-keyed class fields outside the supported form (a module-level `const k = Symbol('desc')` key, assigned unconditionally at the top of the constructor)");
1612
+ }
1613
+ // JS classes: a property first assigned in a method or a
1614
+ // conditional constructor position holds `undefined` until the
1615
+ // write runs — exactly representable as the undefined-armed
1616
+ // union of the inferred property type, so the field COLLECTS
1617
+ // (pre-write reads answer undefined, like Node) instead of
1618
+ // poisoning the class (commander's `this.required` switch
1619
+ // assignment, `this.runningCommand` method assignment).
1620
+ // Unmappable inferences and arm-less kinds keep the fence.
1621
+ // TypeScript classes keep the loud fence too: an annotated
1622
+ // program can spell `T | undefined` itself.
1623
+ if (isJsSourceFile(decl.getSourceFile())) {
1624
+ const armed = undefArmedFieldType(L, p);
1625
+ if (armed !== null) {
1626
+ fields.set(p.name, armed);
1627
+ fieldOrder.push({ name: p.name, type: armed, initializer: undefined });
1628
+ continue;
1629
+ }
1630
+ }
1631
+ L.unsupported("SC1090", site, `fields assigned outside the constructor's top level ('this.${p.name}' would be undefined until the first assignment runs — assign it unconditionally at the top of the constructor)`);
1632
+ }
1633
+ }
1634
+ // Second refinement chance, OWN symbol slots: the member loop ran
1635
+ // before the constructor scan declared this class's own symbol-keyed
1636
+ // fields, so methods returning those slots (1731's `extra()` —
1637
+ // `return this[kExtra]`) retry here with the layout complete.
1638
+ for (const [mName, node] of dynRetMethods) {
1639
+ const sig = methods.get(mName);
1640
+ if (!sig || sig.ret.kind !== "dyn")
1641
+ continue;
1642
+ const refined = symbolSlotReturnType(L, node, symbolFields, fields);
1643
+ if (refined)
1644
+ sig.ret = refined;
1645
+ }
1646
+ // The mixin FORWARDING constructor — `constructor(...args: any[]) {
1647
+ // super(...args); … }`: under monomorphization the base's signature
1648
+ // is known, so the instantiation's ABI IS the base's — synthetic
1649
+ // params forward to super unchanged (defaults apply in the base's
1650
+ // own prologue, exactly JS's raw-argument forwarding) and the rest
1651
+ // parameter never materializes. A rest constructor in a mixin that
1652
+ // is NOT the pure forwarding shape has no static story — named
1653
+ // fence, never a mis-typed array.
1654
+ const mixinForwarding = mixin !== undefined && ctor !== null && mixinForwardingCtor(L, ctor);
1655
+ if (mixin && ctor && !mixinForwarding && ctor.parameters.some((p) => p.dotDotDotToken)) {
1656
+ L.unsupported("SC1090", ctor.parameters.find((p) => p.dotDotDotToken), "mixin constructors whose rest parameter does anything but forward (`super(...args)` as the first statement is the compiled shape)");
1657
+ }
1658
+ // Constructor omitted on a derived class: it inherits the base's
1659
+ // (tsc types `new Derived(...)` against the inherited signature; the
1660
+ // synthesized constructor forwards the same params to super).
1661
+ const ctorParams = ctor && !mixinForwarding
1662
+ ? L.paramShapes(ctor.parameters)
1663
+ : (base?.ctorParams ?? []);
1664
+ const info = {
1665
+ def: {
1666
+ name: className,
1667
+ // The JS-observable .name (the class object's name string and
1668
+ // what `C.name` folds to): the declared name, or NamedEvaluation's
1669
+ // answer for class expressions ("" when truly anonymous). An
1670
+ // INSTANTIATION prints its family's name — JS has one `Box`.
1671
+ jsName: jsNameOverride ?? decl.name?.text ?? "",
1672
+ ...(base ? { base: base.def.name } : {}),
1673
+ // Layout order: the base chain's fields as an IDENTICAL prefix,
1674
+ // then this class's own — what makes an upcast a reinterpret.
1675
+ fields: [
1676
+ ...(base?.def.fields ?? []),
1677
+ ...fieldOrder.map((f) => ({ name: f.name, type: f.type })),
1678
+ ],
1679
+ ...(methods.size > 0 ? { methods: [...methods.keys()] } : {}),
1680
+ ...(abstractClass ? { abstract: true } : {}),
1681
+ ...(() => {
1682
+ const am = [...methods.entries()].filter(([, s]) => s.abstract === true).map(([n]) => n);
1683
+ return am.length > 0 ? { abstractMethods: am } : {};
1684
+ })(),
1685
+ ...(inst ? { genericOf: inst.family.def.name } : {}),
1686
+ loc: locOf(decl),
1687
+ },
1688
+ fields,
1689
+ fieldOrder,
1690
+ methods,
1691
+ decl,
1692
+ ctor,
1693
+ ctorParams,
1694
+ ...(paramProps.length > 0 ? { paramProps } : {}),
1695
+ base,
1696
+ subclasses: [],
1697
+ throwingSetters,
1698
+ staticFields,
1699
+ ...(staticMethods.size > 0 ? { staticMethods } : {}),
1700
+ ...(staticBlocks.length > 0 ? { staticBlocks } : {}),
1701
+ ...(symbolFields.size > 0 ? { symbolFields } : {}),
1702
+ ...(classDecoratorNodes.length > 0 ? { classDecorators: { nodes: classDecoratorNodes } } : {}),
1703
+ };
1704
+ // GENERIC members get their declaring-class backlink now that the
1705
+ // info exists (instance lowering reads it for `this` typing and the
1706
+ // generic-class binding merge).
1707
+ if (genericMethods.size > 0) {
1708
+ for (const gm of genericMethods.values())
1709
+ gm.member = { cls: info, kind: "method" };
1710
+ info.genericMethods = genericMethods;
1711
+ }
1712
+ if (genericStatics.size > 0) {
1713
+ for (const gs of genericStatics.values())
1714
+ gs.member = { cls: info, kind: "static" };
1715
+ info.genericStatics = genericStatics;
1716
+ }
1717
+ if (inst) {
1718
+ info.genericInstance = {
1719
+ family: inst.family,
1720
+ bindings: inst.bindings,
1721
+ typeArgsText: inst.typeArgsText,
1722
+ ordinal: inst.ordinal,
1723
+ };
1724
+ }
1725
+ if (mixin) {
1726
+ info.mixinInstance = {
1727
+ call: mixin.call,
1728
+ bindings: mixin.bindings,
1729
+ context: mixin.context,
1730
+ ordinal: mixin.ordinal,
1731
+ ...(mixinForwarding ? { forwardingCtor: true } : {}),
1732
+ };
1733
+ }
1734
+ if (familyMode) {
1735
+ const typeParams = [];
1736
+ for (const tp of decl.typeParameters) {
1737
+ const sym = L.checker.getSymbolAtLocation(tp.name);
1738
+ if (!sym)
1739
+ L.unsupported("SC1090", tp, "this type parameter form");
1740
+ typeParams.push(sym);
1741
+ }
1742
+ info.generic = {
1743
+ decl: decl,
1744
+ baseName: decl.name?.text ?? "%anon",
1745
+ typeParams,
1746
+ family: info,
1747
+ instances: new Map(),
1748
+ };
1749
+ L.genericClassByDecl.set(decl, info.generic);
1750
+ }
1751
+ if (base)
1752
+ base.subclasses.push(info);
1753
+ L.classes.set(className, info);
1754
+ // A NAMED class binds its name (declarations in their scope, class
1755
+ // expressions inside their own bodies — tsc resolves both to this
1756
+ // symbol); a nameless default-export declaration binds its module's
1757
+ // default-export symbol; anonymous expressions have nothing to bind.
1758
+ // Instantiations bind nothing — the FAMILY owns the symbol. A mixin
1759
+ // instantiation binds nothing either: the inner class's name would
1760
+ // alias EVERY instantiation (self-references by name inside mixin
1761
+ // classes fence at their use sites).
1762
+ const classSymbol = inst || mixin
1763
+ ? undefined
1764
+ : decl.name ? L.checker.getSymbolAtLocation(decl.name) : ts.isClassDeclaration(decl) ? declSymbolOf(L, decl) : undefined;
1765
+ if (classSymbol)
1766
+ L.classBySymbol.set(classSymbol, info);
1767
+ // Static-field storage registers with the module's globals only
1768
+ // once the whole shape collected (a poisoned class never leaves a
1769
+ // half-registered global behind).
1770
+ for (const f of staticFields) {
1771
+ L.globalsList.push({ id: f.globalId, name: `${info.def.jsName ?? className}.${f.name}`, type: f.type, mutable: !f.readonly });
1772
+ }
1773
+ }
1774
+ }
1775
+ /** mapType's generic-class hook: the INSTANTIATION a concrete type
1776
+ * reference (`Box<number>`) names — registered on first demand. The
1777
+ * instance's NAME reserves its key before the shape collects, so
1778
+ * self-referential layouts (`next: Box<T> | null`) re-enter here and
1779
+ * take the name without recursing; a poisoned collection (a field type
1780
+ * with no lowering under these bindings — the diagnostic carries the
1781
+ * instantiation context) leaves the entry poisoned and the type
1782
+ * unmapped, the fenced-JS-class story. Null answers (unmappable type
1783
+ * arguments, the instance cap, an uncollected family) make the whole
1784
+ * reference unmappable — per-site diagnostics own the fence. */
1785
+ export function genericClassInstanceType(L, decl, ref) {
1786
+ const gci = L.genericClassByDecl.get(decl);
1787
+ if (!gci) {
1788
+ // The family never collected (a deferred/poisoned declaration): the
1789
+ // pre-generics answer — the class's own name, unregistered, so dead
1790
+ // storage prunes (typeNamesUnregisteredClass) and live references
1791
+ // flush the declaration's deferred diagnostics (moduleArtifacts /
1792
+ // the validator backstop). Exactly the fenced-class story.
1793
+ return { kind: "object", className: L.classNamer(decl) };
1794
+ }
1795
+ // A degenerate reference collapses to the FAMILY's object type instead
1796
+ // of going unmapped: `Box<any>` under a static build, wilder arguments
1797
+ // no instantiation can carry (`X<<T>() => T>`), and the instance cap.
1798
+ // The family is nominal Box-ness with only the INHERITED layout: no
1799
+ // value can be CONSTRUCTED at such a type (construction resolves
1800
+ // instantiations and fences), real instantiations may UPCAST into its
1801
+ // slots (the ancestor rule — `let b: Box<any> = new Box(1)`), interval
1802
+ // instanceof answers for the whole family, inherited concrete fields
1803
+ // read through the shared prefix, and every per-instantiation member
1804
+ // keeps a named per-site fence.
1805
+ const familyT = { kind: "object", className: gci.family.def.name };
1806
+ // The checker appends `this` (and outer type parameters) to
1807
+ // getTypeArguments — only the declaration's own count participates.
1808
+ const args = L.checker.getTypeArguments(ref).slice(0, gci.typeParams.length);
1809
+ const mapped = [];
1810
+ if (args.length === gci.typeParams.length) {
1811
+ for (const a of args) {
1812
+ const m = L.mapTypeOf(a);
1813
+ if (!m || m.kind === "void") {
1814
+ // An UNBOUND type parameter argument (`Box<T>` outside any
1815
+ // instantiation) stays honestly unmapped — nothing concrete is
1816
+ // being named; everything else degrades to the family.
1817
+ return a.flags & ts.TypeFlags.TypeParameter ? null : familyT;
1818
+ }
1819
+ mapped.push(m);
1820
+ }
1821
+ }
1822
+ else {
1823
+ // No argument list — the `this` TYPE inside the generic class's own
1824
+ // body (`this.v = v` types the receiver as `this`, not a reference).
1825
+ // Inside an instantiation the CURRENT bindings are the arguments;
1826
+ // anywhere else the reference is honestly unmappable.
1827
+ for (const tp of gci.typeParams) {
1828
+ const b = L.typeParamBindings?.get(tp);
1829
+ if (!b)
1830
+ return null;
1831
+ mapped.push(b);
1832
+ }
1833
+ }
1834
+ const key = mapped.map(typeKey).join(",");
1835
+ const existing = gci.instances.get(key);
1836
+ if (existing) {
1837
+ return existing.poisoned ? null : { kind: "object", className: existing.name };
1838
+ }
1839
+ // The generic-fn cap, same rationale (polymorphic recursion through
1840
+ // class fields would mint instances forever). mapType has no
1841
+ // diagnostic channel — the family answer keeps the site compilable
1842
+ // where the OBJECT itself is never touched; touched members fence.
1843
+ if (gci.instances.size >= MAX_GENERIC_INSTANCES)
1844
+ return familyT;
1845
+ const ordinal = gci.instances.size;
1846
+ const name = `${gci.family.def.name}%${ordinal}`;
1847
+ const entry = { name, info: null };
1848
+ gci.instances.set(key, entry);
1849
+ const bindings = new Map();
1850
+ gci.typeParams.forEach((tp, i) => bindings.set(tp, mapped[i]));
1851
+ const rendered = mapped.map((m) => L.fmt(m)).join(", ");
1852
+ const typeArgsText = `<${rendered.length > 80 ? rendered.slice(0, 77) + "..." : rendered}>`;
1853
+ const prevBindings = L.typeParamBindings;
1854
+ const prevContext = L.instantiationContext;
1855
+ L.typeParamBindings = bindings;
1856
+ L.instantiationContext = `instantiating class '${gci.baseName}' with ${typeArgsText}`;
1857
+ try {
1858
+ L.collectClassShapeInner(decl, undefined, { family: gci.family, name, bindings, typeArgsText, ordinal });
1859
+ }
1860
+ catch (e) {
1861
+ // Collection fenced under THESE bindings: the diagnostic (with the
1862
+ // instantiation context) is recorded; the type stays unmapped.
1863
+ if (!(e instanceof PoisonError))
1864
+ throw e;
1865
+ entry.poisoned = true;
1866
+ return null;
1867
+ }
1868
+ finally {
1869
+ L.typeParamBindings = prevBindings;
1870
+ L.instantiationContext = prevContext;
1871
+ }
1872
+ const info = L.classes.get(name);
1873
+ if (!info) {
1874
+ entry.poisoned = true;
1875
+ return null;
1876
+ }
1877
+ entry.info = info;
1878
+ L.genericClassInstances.push(info);
1879
+ L.onLateClassCollected?.(info);
1880
+ return { kind: "object", className: name };
1881
+ }
1882
+ /** Runs a member-lowering thunk under an INSTANTIATION's type-parameter
1883
+ * bindings (the generic-fn typeParamResolver mechanism) — the checker
1884
+ * keeps reporting the unsubstituted `T`s inside the shared body AST.
1885
+ * Coverage counts a generic class's statements once: only the FIRST
1886
+ * instantiation contributes (the lowerGenericInstance rule). A no-op
1887
+ * for ordinary classes. */
1888
+ export function withInstanceBindings(L, info, fn) {
1889
+ const gi = info.genericInstance;
1890
+ if (!gi) {
1891
+ // MIXIN instantiations ride the same mechanism: T (the base
1892
+ // parameter's type parameter) resolves to the argument's classval,
1893
+ // fences carry the instantiation context, and only the first
1894
+ // instantiation of a mixin's class counts toward coverage.
1895
+ const mi = info.mixinInstance;
1896
+ if (!mi)
1897
+ return fn();
1898
+ const prevBindings = L.typeParamBindings;
1899
+ const prevContext = L.instantiationContext;
1900
+ const prevSuppress = L.suppressStats;
1901
+ const prevMixinCtx = L.mixinTypeContext;
1902
+ L.typeParamBindings = mi.bindings;
1903
+ L.instantiationContext = mi.context;
1904
+ L.suppressStats = prevSuppress || mi.ordinal > 0;
1905
+ L.mixinTypeContext = { classNode: info.decl, className: info.def.name };
1906
+ try {
1907
+ return fn();
1908
+ }
1909
+ finally {
1910
+ L.typeParamBindings = prevBindings;
1911
+ L.instantiationContext = prevContext;
1912
+ L.suppressStats = prevSuppress;
1913
+ L.mixinTypeContext = prevMixinCtx;
1914
+ }
1915
+ }
1916
+ const prevBindings = L.typeParamBindings;
1917
+ const prevContext = L.instantiationContext;
1918
+ const prevSuppress = L.suppressStats;
1919
+ L.typeParamBindings = gi.bindings;
1920
+ L.instantiationContext = `instantiating class '${gi.family.generic?.baseName ?? info.def.jsName ?? ""}' with ${gi.typeArgsText}`;
1921
+ L.suppressStats = prevSuppress || gi.ordinal > 0;
1922
+ try {
1923
+ return fn();
1924
+ }
1925
+ finally {
1926
+ L.typeParamBindings = prevBindings;
1927
+ L.instantiationContext = prevContext;
1928
+ L.suppressStats = prevSuppress;
1929
+ }
1930
+ }
1931
+ /** The `%init` statements for one class's static readonly fields AND
1932
+ * static blocks, interleaved in member order — emitted at the class
1933
+ * statement's source position (see lowerFileInit's merge), exactly when
1934
+ * JS evaluates static initializers and blocks. Field failures poison per
1935
+ * field, like fieldInitStmts; a block lowers as the block statement it
1936
+ * is, so its statements poison individually inside lowerStmts. */
1937
+ export function lowerStaticFieldInits(L, info) {
1938
+ // Mixin instantiations lower their initializers under the
1939
+ // instantiation's bindings/context (a no-op for everything else —
1940
+ // generic FAMILIES own their statics and carry no genericInstance).
1941
+ return withInstanceBindings(L, info, () => lowerStaticFieldInitsInner(L, info));
1942
+ }
1943
+ function lowerStaticFieldInitsInner(L, info) {
1944
+ // Decoration first: TC39 evaluates decorator expressions, creates the
1945
+ // class, applies the decorators, and only THEN runs static field
1946
+ // initializers and static blocks (verified against Node — the
1947
+ // decorated result is what `this`/the class name mean inside them).
1948
+ const out = [...lowerClassDecoration(L, info)];
1949
+ const items = [
1950
+ ...info.staticFields.map((f) => ({ pos: f.initializer.getStart(), kind: "field", f })),
1951
+ ...(info.staticBlocks ?? []).map((b) => ({ pos: b.getStart(), kind: "block", b })),
1952
+ ].sort((a, b) => a.pos - b.pos);
1953
+ for (const item of items) {
1954
+ if (item.kind === "block") {
1955
+ // The block's body IS a Block statement: lowerStmts scopes its
1956
+ // let/const like any nested block and poisons per inner statement.
1957
+ out.push(...L.lowerStmts([item.b.body]));
1958
+ continue;
1959
+ }
1960
+ const f = item.f;
1961
+ L.stats.statementsTotal++;
1962
+ L.bumpFileStat(locOf(f.initializer).file, "total");
1963
+ try {
1964
+ // `this` in a static field initializer names the CLASS (like a
1965
+ // static block's), with arrows transparent and this-binding
1966
+ // function forms opaque — the static-block rule verbatim, named
1967
+ // here so the generic outside-a-method fence never fires first.
1968
+ const checkThis = (n) => {
1969
+ if (ts.isFunctionExpression(n) || ts.isFunctionDeclaration(n) ||
1970
+ ts.isMethodDeclaration(n) || ts.isConstructorDeclaration(n) ||
1971
+ ts.isGetAccessor(n) || ts.isSetAccessor(n) ||
1972
+ ts.isClassDeclaration(n) || ts.isClassExpression(n)) {
1973
+ return;
1974
+ }
1975
+ if (n.kind === ts.SyntaxKind.ThisKeyword || n.kind === ts.SyntaxKind.SuperKeyword) {
1976
+ L.unsupported("SC1090", n, "'this' in static field initializers (it names the class — reference the class by name instead)");
1977
+ }
1978
+ n.forEachChild(checkThis);
1979
+ };
1980
+ checkThis(f.initializer);
1981
+ const value = L.lowerExprExpecting(f.initializer, f.type);
1982
+ out.push({ kind: "assign", localId: f.globalId, value, loc: locOf(f.initializer) });
1983
+ }
1984
+ catch (e) {
1985
+ if (!(e instanceof PoisonError))
1986
+ throw e;
1987
+ L.stats.statementsFailed++;
1988
+ L.bumpFileStat(locOf(f.initializer).file, "failed");
1989
+ }
1990
+ }
1991
+ return out;
1992
+ }
1993
+ /** Post-collection analysis of a decorated class (all shapes registered —
1994
+ * a decorator's return type may name a subclass declared BELOW the
1995
+ * class). Classifies every class-level decorator by its checker type:
1996
+ * exactly one parameter, itself a classval the decorated class legally
1997
+ * flows into (the class, or a base sharing its completed constructor
1998
+ * ABI — the classval widening rule), returning void/undefined (an
1999
+ * effect-only decorator) or the class/a same-ABI subclass (a REPLACING
2000
+ * decorator, whose result rebinds the name). Everything else is a named
2001
+ * fence: the standard context parameter, structural sibling
2002
+ * replacements tsc admits but the nominal classval world cannot carry,
2003
+ * unions mixing the class with undefined. A replacing decorator
2004
+ * registers the mutable classval global the name rebinds through, and
2005
+ * fences the two shapes the value rebinding cannot keep exact —
2006
+ * subclasses of the decorated class (the compiled hierarchy is fixed at
2007
+ * build time; the runtime base would be the decoration result) and
2008
+ * namespace-nested declarations (qualified references resolve the class
2009
+ * directly, not through the rebound value). */
2010
+ export function analyzeClassDecoration(L, info) {
2011
+ const cd = info.classDecorators;
2012
+ if (!cd || cd.shapes !== undefined || cd.poisoned)
2013
+ return;
2014
+ const display = info.def.jsName ?? info.def.name;
2015
+ try {
2016
+ const shapes = [];
2017
+ for (const d of cd.nodes) {
2018
+ // An ambient declaration NOTHING defines (`declare let dec: any`,
2019
+ // `declare function dec<T>(t: T): T` — the conformance corpus's
2020
+ // dominant decorator shape): Node erases it, so the decorator
2021
+ // EXPRESSION itself throws ReferenceError when the class statement
2022
+ // evaluates. That is runnable semantics, not a fence — the
2023
+ // undefRead story (the ambient `declare const` stance verbatim).
2024
+ // Factory spellings ride along: `@dec("x")` evaluates the CALLEE
2025
+ // before any argument, so the ReferenceError is still the first
2026
+ // observable effect.
2027
+ const ambientCallee = ts.isCallExpression(d.expression) && ts.isIdentifier(d.expression.expression)
2028
+ ? d.expression.expression
2029
+ : ts.isIdentifier(d.expression)
2030
+ ? d.expression
2031
+ : null;
2032
+ if (ambientCallee !== null) {
2033
+ const sym = L.resolveValueSymbol(ambientCallee);
2034
+ const vdecl = sym && !L.isStdlibSymbol(sym) ? L.checker.declarationsOf(sym)[0] : undefined;
2035
+ const ambientVar = vdecl !== undefined &&
2036
+ ts.isVariableDeclaration(vdecl) &&
2037
+ vdecl.initializer === undefined &&
2038
+ (ts.getCombinedModifierFlags(vdecl) & ts.ModifierFlags.Ambient) !== 0 &&
2039
+ !vdecl.getSourceFile().isDeclarationFile;
2040
+ if (ambientVar || ambientUndefinedFnSymbolOf(L, ambientCallee) !== null) {
2041
+ shapes.push({ kind: "ambientThrow", name: ambientCallee.text });
2042
+ continue;
2043
+ }
2044
+ }
2045
+ const t = L.typeOf(d.expression);
2046
+ // Param-count first, off the checker signature: the context-taking
2047
+ // shape deserves its own name before mapType (whose failure on
2048
+ // ClassDecoratorContext would blur the story).
2049
+ const sigs = L.checker.getCallSignatures(t);
2050
+ if (sigs.length === 1 && sigs[0].getParameters().length > 1) {
2051
+ L.unsupported("SC1090", d, "class decorators that take the standard 'context' parameter (its object — addInitializer, metadata — has no static lowering; single-parameter decorators compile)");
2052
+ }
2053
+ const mapped = L.mapTypeOf(t);
2054
+ if (!mapped || mapped.kind !== "func" || mapped.rest === true || mapped.params.length > 1) {
2055
+ L.unsupported("SC1090", d, "class decorators without one concrete (class) => class-or-void signature ('any'-typed and generic decorators have no compilable call ABI — declare the parameter as the class type)");
2056
+ }
2057
+ // The parameter: a classval slot the decorated class's object can
2058
+ // legally inhabit — the class itself, or a BASE with the same
2059
+ // completed constructor ABI (the classval widening rule).
2060
+ if (mapped.params.length === 1) {
2061
+ const p = mapped.params[0];
2062
+ const paramOk = p.kind === "classval" &&
2063
+ (p.className === info.def.name ||
2064
+ (isSubclassOf(L, info.def.name, p.className) &&
2065
+ (() => {
2066
+ const sup = L.classes.get(p.className);
2067
+ return sup !== undefined && !sup.generic && ctorAbiEquals(L, info, sup);
2068
+ })()));
2069
+ if (!paramOk) {
2070
+ L.unsupported("SC1090", d, `class decorators whose parameter is not the decorated class ('${display}' cannot flow into a '${L.fmt(p)}' slot — declare the parameter as 'typeof ${display}' or a base class sharing its constructor signature)`);
2071
+ }
2072
+ }
2073
+ // The return: void/undefined keeps the original binding; the class
2074
+ // itself or a same-ABI SUBCLASS is a legal replacement (a classval
2075
+ // of the decorated class per the flow rule). tsc also admits
2076
+ // structurally-compatible siblings and bases — the nominal classval
2077
+ // world cannot carry those, so they fence by name.
2078
+ const ret = mapped.ret;
2079
+ const replaces = ret.kind === "classval";
2080
+ if (replaces) {
2081
+ const retOk = ret.className === info.def.name ||
2082
+ (isSubclassOf(L, ret.className, info.def.name) &&
2083
+ (() => {
2084
+ const sub = L.classes.get(ret.className);
2085
+ return sub !== undefined && !sub.generic && ctorAbiEquals(L, sub, info);
2086
+ })());
2087
+ if (!retOk) {
2088
+ L.unsupported("SC1090", d, `class decorators returning '${L.fmt(ret)}' (a replacement must be '${display}' itself or a subclass sharing its constructor signature — tsc's structural check admits shapes the compiled nominal hierarchy cannot rebind)`);
2089
+ }
2090
+ }
2091
+ else if (ret.kind !== "void") {
2092
+ L.unsupported("SC1090", d, `class decorators returning '${L.fmt(ret)}' (supported returns: the decorated class type, a subclass with the same constructor signature, or void)`);
2093
+ }
2094
+ shapes.push({ kind: "call", funcType: mapped, replaces });
2095
+ }
2096
+ if (shapes.some((s) => s.kind === "call" && s.replaces)) {
2097
+ // The name can rebind at runtime: every reference must route
2098
+ // through the decoration result. Two shapes cannot: a compiled
2099
+ // subclass (its base pointer, vtable prefix, and interval are
2100
+ // fixed at build time, but JS would extend the decoration result)
2101
+ // and namespace-nested declarations (the qualified-access paths
2102
+ // resolve the class directly, not through the rebound binding).
2103
+ if (info.subclasses.length > 0) {
2104
+ L.unsupported("SC1090", cd.nodes[0], `class decorators that can replace a class with subclasses ('${info.subclasses[0].def.jsName ?? info.subclasses[0].def.name}' extends '${display}', but the runtime base would be the decoration result — return void, or decorate the leaf classes)`);
2105
+ }
2106
+ if (info.decl && !ts.isSourceFile(info.decl.parent)) {
2107
+ L.unsupported("SC1090", cd.nodes[0], "class decorators that can replace a namespace-nested class (qualified references resolve the declaration directly — return void, or declare the class at top level)");
2108
+ }
2109
+ const globalId = `%g.dec.${info.def.name}`;
2110
+ L.globalsList.push({
2111
+ id: globalId,
2112
+ name: `${display}.decorated`,
2113
+ type: { kind: "classval", className: info.def.name },
2114
+ mutable: true,
2115
+ });
2116
+ cd.valueGlobalId = globalId;
2117
+ }
2118
+ cd.shapes = shapes;
2119
+ }
2120
+ catch (e) {
2121
+ if (!(e instanceof PoisonError))
2122
+ throw e;
2123
+ cd.poisoned = true;
2124
+ }
2125
+ }
2126
+ /** The decoration statements of a decorated class — the %init code that
2127
+ * runs at the class statement's position, BEFORE its static field
2128
+ * initializers and blocks (lowerStaticFieldInits composes them; the
2129
+ * lower-modules interleave places the whole bundle). Verified Node
2130
+ * order: decorator expressions evaluate in SOURCE order (factories run
2131
+ * here), then applications run in REVERSE member order over the class
2132
+ * object, each replacing decorator's non-undefined result feeding the
2133
+ * next application; the final value binds the class name (the mutable
2134
+ * classval global) when any decorator can replace. */
2135
+ export function lowerClassDecoration(L, info) {
2136
+ const cd = info.classDecorators;
2137
+ if (!cd || cd.poisoned || cd.shapes === undefined || info.decl === null)
2138
+ return [];
2139
+ const loc = locOf(info.decl);
2140
+ const stmts = [];
2141
+ try {
2142
+ // 1. Decorator expressions evaluate in source order, into hidden
2143
+ // locals — a later factory's side effects must not precede an
2144
+ // earlier one's, and every expression evaluates before any applies.
2145
+ // An ambient (never-defined) decorator name throws Node's
2146
+ // ReferenceError HERE: earlier expressions still evaluate, nothing
2147
+ // after — expression, application, or the class's own static
2148
+ // initializers — ever runs (the %init unwinds).
2149
+ const temps = [];
2150
+ for (let i = 0; i < cd.nodes.length; i++) {
2151
+ const d = cd.nodes[i];
2152
+ const shape = cd.shapes[i];
2153
+ if (shape.kind === "ambientThrow") {
2154
+ stmts.push({ kind: "exprStmt", expr: nsUndefRead(L, shape.name, d, F64), loc: locOf(d) });
2155
+ return stmts;
2156
+ }
2157
+ const value = L.lowerExprExpecting(d.expression, shape.funcType);
2158
+ const local = L.declareHiddenLocal("dec", shape.funcType);
2159
+ stmts.push({ kind: "varDecl", localId: local.id, init: value, loc: locOf(d) });
2160
+ temps.push({ localId: local.id, funcType: shape.funcType, replaces: shape.replaces });
2161
+ }
2162
+ // 2. Applications, reverse order, over the accumulating class value.
2163
+ let current = classValueRef(L, info, info.decl);
2164
+ for (let i = temps.length - 1; i >= 0; i--) {
2165
+ const t = temps[i];
2166
+ const dLoc = locOf(cd.nodes[i]);
2167
+ const callee = { kind: "varRef", localId: t.localId, type: t.funcType, loc: dLoc };
2168
+ const args = [];
2169
+ if (t.funcType.params.length === 1) {
2170
+ const p = t.funcType.params[0];
2171
+ const widened = L.coerceToExpected(current, p);
2172
+ L.requireExactShape(cd.nodes[i], widened.type, p);
2173
+ args.push(widened);
2174
+ }
2175
+ const call = { kind: "callValue", callee, args, type: t.funcType.ret, loc: dLoc };
2176
+ if (t.replaces) {
2177
+ const target = { kind: "classval", className: info.def.name };
2178
+ const widened = L.coerceToExpected(call, target);
2179
+ L.requireExactShape(cd.nodes[i], widened.type, target);
2180
+ const res = L.declareHiddenLocal("decres", target);
2181
+ stmts.push({ kind: "varDecl", localId: res.id, init: widened, loc: dLoc });
2182
+ current = { kind: "varRef", localId: res.id, type: target, loc: dLoc };
2183
+ }
2184
+ else {
2185
+ stmts.push({ kind: "exprStmt", expr: call, loc: dLoc });
2186
+ }
2187
+ }
2188
+ // 3. The binding: TC39 rebinds the class name to the final result.
2189
+ if (cd.valueGlobalId !== undefined) {
2190
+ stmts.push({ kind: "assign", localId: cd.valueGlobalId, value: current, loc });
2191
+ }
2192
+ return stmts;
2193
+ }
2194
+ catch (e) {
2195
+ if (!(e instanceof PoisonError))
2196
+ throw e;
2197
+ L.stats.statementsFailed++;
2198
+ L.bumpFileStat(loc.file, "failed");
2199
+ return [];
2200
+ }
2201
+ }
2202
+ /** The nearest declaration of static member `name` at or above `info` —
2203
+ * the compile-time prototype-chain walk (`D.x` reads C's global when C
2204
+ * declared x and nothing between redeclares it; a redeclaration shadows
2205
+ * with its OWN storage, exactly JS). */
2206
+ export function findStaticOn(L, info, name) {
2207
+ for (let c = info; c; c = c.base) {
2208
+ const field = c.staticFields.find((s) => s.name === name);
2209
+ if (field)
2210
+ return { declarer: c, field };
2211
+ const method = c.staticMethods?.get(name);
2212
+ if (method)
2213
+ return { declarer: c, method };
2214
+ }
2215
+ return null;
2216
+ }
2217
+ /** True when some STRICT descendant of `info` redeclares static `name` —
2218
+ * the through-a-VALUE devirtualization test: a classval(info) slot can
2219
+ * hold any descendant, and a shadowing redeclaration means the runtime
2220
+ * class decides which storage answers. */
2221
+ export function staticShadowBelow(L, info, name) {
2222
+ return info.subclasses.some((s) => s.staticFields.some((f) => f.name === name) ||
2223
+ s.staticMethods?.has(name) === true ||
2224
+ s.genericStatics?.has(name) === true ||
2225
+ staticShadowBelow(L, s, name));
2226
+ }
2227
+ /** The nearest GENERIC static declaration of `name` at/above `info` —
2228
+ * findStaticOn's twin over the genericStatics tables. */
2229
+ export function findGenericStaticOn(L, info, name) {
2230
+ for (let c = info; c; c = c.base) {
2231
+ const gs = c.genericStatics?.get(name);
2232
+ if (gs)
2233
+ return { declarer: c, info: gs };
2234
+ }
2235
+ return null;
2236
+ }
2237
+ /** A static METHOD taken as a value: the zero-capture closure over its
2238
+ * module function — the declared-function-as-value rule verbatim
2239
+ * (interned by the backend, so `C.m === C.m` holds). */
2240
+ function staticMethodValue(L, declarer, name, sig, blame, loc) {
2241
+ const fnName = `%${declarer.def.name}.static:${name}`;
2242
+ L.noteEdge(fnName);
2243
+ const funcType = {
2244
+ kind: "func",
2245
+ params: sig.params.filter((p) => p.mode !== "dynRest").map((p) => p.type),
2246
+ ret: sig.ret,
2247
+ ...(sig.params.some((p) => p.mode === "dynRest") ? { rest: true } : {}),
2248
+ };
2249
+ L.requireExactArityValue(blame, blame, sig.params, funcType);
2250
+ return { kind: "closure", fnName, captures: [], type: funcType, loc };
2251
+ }
2252
+ /** The class itself taken as a VALUE (`const X = C`, an argument, an
2253
+ * array element, a class expression's result): the classRef over the
2254
+ * per-class immortal class object. The construct thunk needs a thunk-
2255
+ * shaped constructor, so classes whose construction is libCall-shaped —
2256
+ * the runtime-provided builtins and anything inheriting a builtin
2257
+ * constructor (Error/EventEmitter/stream chains complete their `new`
2258
+ * by special rules) — are named fences here. The constructor edge is
2259
+ * noted at every classRef: a value can always be constructed through. */
2260
+ export function classValueRef(L, info, blame) {
2261
+ const display = info.def.name.replace(/^%|^%m\d+\./, "");
2262
+ fenceDecorationThrows(L, info, blame);
2263
+ if (info.generic) {
2264
+ // `typeof Box` — the uninstantiated FAMILY as a value: no thunk, no
2265
+ // single constructor ABI. INSTANTIATIONS have class objects
2266
+ // (`const B = Box<number>`, `new (v: number) => Box<number>` slots).
2267
+ L.unsupported("SC1090", blame, `generic classes as values ('typeof ${display}' keeps the type parameter — instantiation expressions ('${display}<number>') and concrete constructor-typed slots compile)`);
2268
+ }
2269
+ if (info.builtinError || info.builtinEmitter || info.builtinStream !== undefined) {
2270
+ L.unsupported("SC1090", blame, `builtin classes as values ('${display}' is runtime-provided — reference program-declared classes instead)`);
2271
+ }
2272
+ if (L.inheritsBuiltinErrorCtor(info) || L.inheritsBuiltinEmitterCtor(info) ||
2273
+ inheritsBuiltinStreamCtor(L, info) ||
2274
+ (() => { for (let c = info.base; c; c = c.base)
2275
+ if (c.builtinError || c.builtinEmitter || c.builtinStream !== undefined)
2276
+ return true; return false; })()) {
2277
+ L.unsupported("SC1090", blame, `classes extending builtin bases as values ('${display}' inherits a runtime-provided constructor)`);
2278
+ }
2279
+ L.noteEdge(`%${info.def.name}.constructor`);
2280
+ return {
2281
+ kind: "classRef",
2282
+ className: info.def.name,
2283
+ type: { kind: "classval", className: info.def.name },
2284
+ loc: locOf(blame),
2285
+ };
2286
+ }
2287
+ /** Constructor-ABI equality — the classval widening rule: a classval(D)
2288
+ * value may flow into a classval(C) slot only when D's completed
2289
+ * constructor signature equals C's (same count, modes, and ABI types),
2290
+ * which is what keeps newValue completion against C's one signature
2291
+ * sound for every value legally in the slot. */
2292
+ export function ctorAbiEquals(L, sub, sup) {
2293
+ const a = sub.ctorParams;
2294
+ const b = sup.ctorParams;
2295
+ return a.length === b.length && a.every((p, i) => p.mode === b[i].mode && typeEquals(p.type, b[i].type));
2296
+ }
2297
+ /** `C.x` where C is a class declared in the program and x a static
2298
+ * member of its chain: field reads are the module global, static
2299
+ * methods become interned closures, and `.name` folds to the class's
2300
+ * compile-time name. Null for everything else — unresolved members
2301
+ * fall through to the ordinary chain so the static fence or the
2302
+ * generic member rejection names the site. */
2303
+ export function lowerStaticFieldRead(L, expr) {
2304
+ if (expr.questionDotToken)
2305
+ return null;
2306
+ if (!ts.isIdentifier(expr.expression))
2307
+ return null;
2308
+ const symbol = L.resolveValueSymbol(expr.expression);
2309
+ const info = (symbol ? L.classBySymbol.get(symbol) : undefined) ??
2310
+ // A require binding over `module.exports = class {…}` (the alias
2311
+ // lands on the expression's own symbol): resolve/collect on demand,
2312
+ // or `C.name` below would fall through to paths that answer for
2313
+ // stdlib globals instead of this class.
2314
+ propertyAssignedClassInfoOf(L, symbol) ??
2315
+ undefined;
2316
+ if (!info)
2317
+ return null;
2318
+ // A decorated name that can REBIND (a replacing decorator): the
2319
+ // receiver is the decoration result, not the declaration — fall
2320
+ // through to the through-a-VALUE paths (lowerClassValueProperty),
2321
+ // whose devirtualization and .name rules answer for every legal
2322
+ // runtime value.
2323
+ if (info.classDecorators?.valueGlobalId !== undefined)
2324
+ return null;
2325
+ const loc = locOf(expr);
2326
+ const found = findStaticOn(L, info, expr.name.text);
2327
+ if (found?.field !== undefined) {
2328
+ return L.maybeNarrow({ kind: "varRef", localId: found.field.globalId, type: found.field.type, loc }, expr);
2329
+ }
2330
+ if (found) {
2331
+ return staticMethodValue(L, found.declarer, expr.name.text, found.method, expr, loc);
2332
+ }
2333
+ // A GENERIC static method as a VALUE: the pinned-value rule verbatim
2334
+ // (lowerGenericFnValue) — a slot spelling one concrete signature names
2335
+ // an instance, an unpinned reference fences by name.
2336
+ {
2337
+ const gfound = findGenericStaticOn(L, info, expr.name.text);
2338
+ if (gfound)
2339
+ return L.lowerGenericFnValue(expr, gfound.info);
2340
+ }
2341
+ // `C.name` — the JS-observable class name, a compile-time constant on
2342
+ // the direct spelling (tsc rejects user statics named `name`, so the
2343
+ // chain above can never shadow it in TypeScript sources).
2344
+ if (expr.name.text === "name" && info.def.jsName !== undefined) {
2345
+ return { kind: "strLit", value: info.def.jsName, type: STRING, loc };
2346
+ }
2347
+ return null;
2348
+ }
2349
+ /** NamedEvaluation's answer for a class expression's `.name`: its own
2350
+ * declared name, else the binding name when the expression is the
2351
+ * direct initializer of a variable declaration / the RHS of a simple
2352
+ * assignment / an object-literal property value / a default parameter —
2353
+ * "" everywhere else (array elements, call arguments). Verified against
2354
+ * Node for each shape. */
2355
+ function namedEvaluationName(expr) {
2356
+ if (expr.name)
2357
+ return expr.name.text;
2358
+ let p = expr.parent;
2359
+ while (ts.isParenthesizedExpression(p))
2360
+ p = p.parent;
2361
+ if (ts.isVariableDeclaration(p) && ts.isIdentifier(p.name) && p.initializer !== undefined)
2362
+ return p.name.text;
2363
+ if (ts.isBinaryExpression(p) && p.operatorToken.kind === ts.SyntaxKind.EqualsToken && ts.isIdentifier(p.left))
2364
+ return p.left.text;
2365
+ if (ts.isPropertyAssignment(p) && ts.isIdentifier(p.name))
2366
+ return p.name.text;
2367
+ if (ts.isParameter(p) && ts.isIdentifier(p.name))
2368
+ return p.name.text;
2369
+ return "";
2370
+ }
2371
+ /** A class EXPRESSION's ClassInfo: collection on first encounter (the
2372
+ * declaration path over the shared ClassLikeDeclaration machinery, with
2373
+ * NamedEvaluation supplying the runtime .name), idempotent per node —
2374
+ * probeLower's speculative visits and the heritage recursion reuse the
2375
+ * first collection. The honest v1 boundary is TOP-LEVEL evaluation
2376
+ * positions only: each evaluation of a class expression in JS mints a
2377
+ * DISTINCT class (fresh identity, fresh statics), and one immortal
2378
+ * class object is exact only for expressions that evaluate exactly
2379
+ * once. Statics-bearing expressions additionally restrict to positions
2380
+ * where "immediately before the enclosing statement" IS the evaluation
2381
+ * point (lowerFileInit drains pendingClassExprInits there). */
2382
+ export function lowerClassExpressionInfo(L, expr) {
2383
+ const cached = L.exprClassInfoByNode.get(expr);
2384
+ if (cached)
2385
+ return cached;
2386
+ // Reentrancy guard: heritage resolution can DEMAND another class
2387
+ // expression's collection (extends through property assignments —
2388
+ // propertyAssignedClassInfoOf), so a cyclic base chain would re-enter
2389
+ // its own collection here. The tsc gate rejects every such cycle it
2390
+ // can see (TS2506/TS2303 — direct, indirect, and cross-file require
2391
+ // cycles all probed); this fence is the backstop that turns anything
2392
+ // it misses into a diagnostic instead of a stack overflow.
2393
+ if (L.collectingExprClasses.has(expr)) {
2394
+ L.unsupported("SC1090", expr, "class expressions whose extends chain re-enters their own collection (a cyclic base through property assignments)");
2395
+ }
2396
+ if (L.instantiationContext) {
2397
+ L.unsupported("SC1090", expr, "class expressions inside generic functions (each instantiation would need its own class)");
2398
+ }
2399
+ for (let p = expr.parent; !ts.isSourceFile(p); p = p.parent) {
2400
+ if (ts.isFunctionLike(p) || ts.isClassStaticBlockDeclaration(p)) {
2401
+ L.unsupported("SC1090", expr, "class expressions inside functions (each evaluation creates a DISTINCT class in JS — fresh identity, fresh statics; declare the class at top level)");
2402
+ }
2403
+ }
2404
+ L.collectingExprClasses.add(expr);
2405
+ try {
2406
+ L.collectClassShapeInner(expr, namedEvaluationName(expr));
2407
+ }
2408
+ finally {
2409
+ L.collectingExprClasses.delete(expr);
2410
+ }
2411
+ const info = L.classes.get(L.classNamer(expr));
2412
+ if (!info)
2413
+ throw new PoisonError(); // collection poisoned and reported
2414
+ L.exprClassInfoByNode.set(expr, info);
2415
+ L.exprClasses.push(info);
2416
+ L.onExprClassCollected?.(info);
2417
+ // Static field initializers and static blocks run when the class
2418
+ // expression EVALUATES. The supported positions evaluate exactly once,
2419
+ // at the top-level statement containing the expression — the pending
2420
+ // buffer lands them immediately before that statement (lowerFileInit
2421
+ // drains it), which is JS's order for whole-initializer positions.
2422
+ // Anything subtler (multi-declarator statements, arguments evaluated
2423
+ // after other side effects) is a named fence, never a reordering.
2424
+ if (info.staticFields.length > 0 || (info.staticBlocks?.length ?? 0) > 0) {
2425
+ let holder = expr.parent;
2426
+ while (ts.isParenthesizedExpression(holder) || ts.isClassExpression(holder) ||
2427
+ ts.isHeritageClause(holder) || ts.isExpressionWithTypeArguments(holder)) {
2428
+ holder = holder.parent;
2429
+ }
2430
+ const wholeInit = (ts.isVariableDeclaration(holder) &&
2431
+ holder.initializer !== undefined &&
2432
+ ts.isVariableDeclarationList(holder.parent) &&
2433
+ holder.parent.declarations.length === 1 &&
2434
+ ts.isVariableStatement(holder.parent.parent) &&
2435
+ ts.isSourceFile(holder.parent.parent.parent)) ||
2436
+ (ts.isBinaryExpression(holder) &&
2437
+ holder.operatorToken.kind === ts.SyntaxKind.EqualsToken &&
2438
+ ts.isExpressionStatement(holder.parent) &&
2439
+ ts.isSourceFile(holder.parent.parent)) ||
2440
+ (ts.isExpressionStatement(holder) && ts.isSourceFile(holder.parent));
2441
+ if (!wholeInit) {
2442
+ L.unsupported("SC1090", expr, "class expressions with static initializers or static blocks outside a whole-initializer position (their declaration-time code must run exactly where the expression evaluates — bind the class in its own top-level `const C = class …` statement)");
2443
+ }
2444
+ L.pendingClassExprInits.push(...L.lowerStaticFieldInits(info));
2445
+ }
2446
+ return info;
2447
+ }
2448
+ /** `class {…}` in expression position: a class definition bound to no
2449
+ * statement — once the static side is a value, the expression IS the
2450
+ * definition plus a classRef over it. A DECORATED class expression
2451
+ * whose decoration provably throws (the ambient-decorator shape) never
2452
+ * mints a class at all: evaluating the expression IS the
2453
+ * ReferenceError, so it lowers to exactly that read — every evaluation
2454
+ * throws identically, which is why the once-evaluated restriction and
2455
+ * the member fences don't apply. */
2456
+ export function lowerClassExpression(L, expr) {
2457
+ if (decoratorNodesOf(expr).length > 0 ||
2458
+ expr.members.some((m) => decoratorNodesOf(m).length > 0)) {
2459
+ if (!isJsSourceFile(expr.getSourceFile()) && expr.typeParameters === undefined) {
2460
+ const thrown = guaranteedDecorationThrow(L, expr);
2461
+ if (thrown) {
2462
+ // The expression's static type never materializes — the read
2463
+ // throws — so the nominal IR type only has to satisfy the
2464
+ // consumer. F64 is the ambient-undefRead convention.
2465
+ return nsUndefRead(L, thrown.name, expr, F64);
2466
+ }
2467
+ }
2468
+ }
2469
+ return classValueRef(L, lowerClassExpressionInfo(L, expr), expr);
2470
+ }
2471
+ /** The EXACT class a receiver expression is statically known to BE (not
2472
+ * merely be typed by): the class name itself, or a `const` binding
2473
+ * whose initializer is a class expression / class name. Such receivers
2474
+ * can never hold a subclass at runtime, so static WRITES through them
2475
+ * hit the declaring class's storage exactly (the shadowing hazards of
2476
+ * general class values don't arise). Null for everything else. */
2477
+ export function exactClassOfReceiver(L, expr) {
2478
+ if (!ts.isIdentifier(expr))
2479
+ return null;
2480
+ const symbol = L.resolveValueSymbol(expr);
2481
+ if (!symbol)
2482
+ return null;
2483
+ const direct = L.classBySymbol.get(symbol);
2484
+ // A rebindable decorated name is NOT exactly its class — the binding
2485
+ // may hold a replacing decorator's result (a subclass value), where a
2486
+ // static write would create an own property in JS. The general
2487
+ // class-value write fence answers instead.
2488
+ if (direct)
2489
+ return direct.classDecorators?.valueGlobalId !== undefined ? null : direct;
2490
+ const decl = L.checker.valueDeclarationOf(symbol);
2491
+ if (!decl || !ts.isVariableDeclaration(decl) || decl.initializer === undefined ||
2492
+ !ts.isVariableDeclarationList(decl.parent) ||
2493
+ (decl.parent.flags & ts.NodeFlags.Const) === 0) {
2494
+ return null;
2495
+ }
2496
+ let init = decl.initializer;
2497
+ while (ts.isParenthesizedExpression(init))
2498
+ init = init.expression;
2499
+ if (ts.isClassExpression(init))
2500
+ return L.exprClassInfoByNode.get(init) ?? null;
2501
+ if (ts.isIdentifier(init)) {
2502
+ const initSym = L.resolveValueSymbol(init);
2503
+ const aliased = initSym ? (L.classBySymbol.get(initSym) ?? null) : null;
2504
+ // `const X = C` over a rebindable decorated name: X holds the
2505
+ // decoration result — not exactly C (see the direct case above).
2506
+ return aliased?.classDecorators?.valueGlobalId !== undefined ? null : aliased;
2507
+ }
2508
+ return null;
2509
+ }
2510
+ /** The class a PROPERTY-ASSIGNMENT binding pins — the salsa/CJS
2511
+ * declaration forms of a class expression: `Common.I = class {…}`
2512
+ * (expando members of a plain object), `exports.I = class {…}` /
2513
+ * `module.exports.I = class {…}` (CJS member exports), and
2514
+ * `module.exports = class {…}` (the whole-export replacement, whose
2515
+ * export symbol requirer bindings alias to). The symbol arrives in two
2516
+ * shapes — an ALIAS resolving to the class expression's own symbol
2517
+ * (valueDeclaration IS the ts.ClassExpression), or the expando property
2518
+ * symbol whose declarations are the assignment BinaryExpressions — and
2519
+ * both pin the class exactly when ONE top-level assignment declares it:
2520
+ * a reassigned property is a dynamic binding (the runtime class is
2521
+ * whichever assignment ran last), so it answers null and the caller's
2522
+ * fence names it. Collection is on demand and idempotent
2523
+ * (lowerClassExpressionInfo), so resolution order between files and
2524
+ * passes never matters. */
2525
+ export function propertyAssignedClassInfoOf(L, symbol) {
2526
+ if (!symbol)
2527
+ return null;
2528
+ const resolved = symbol.flags & ts.SymbolFlags.Alias ? L.checker.getAliasedSymbol(symbol) : symbol;
2529
+ const registered = L.classBySymbol.get(resolved);
2530
+ if (registered)
2531
+ return registered;
2532
+ const decls = L.checker.declarationsOf(resolved);
2533
+ // The class expression's OWN symbol (tsgo's answer through CJS export
2534
+ // aliases): the declaration is the expression itself. Its top-level
2535
+ // assignment statement must be the binding's ONLY producer. The
2536
+ // resolved symbol registers in classBySymbol so every downstream
2537
+ // path — static reads, the `.name` fold, instanceof — answers like a
2538
+ // declaration from then on.
2539
+ if (decls.length === 1 && decls[0] !== undefined && ts.isClassExpression(decls[0])) {
2540
+ const assign = enclosingTopLevelClassAssignment(decls[0]);
2541
+ if (!assign || countAssignmentsTo(assign) !== 1)
2542
+ return null;
2543
+ const info = L.lowerClassExpressionInfo(decls[0]);
2544
+ L.classBySymbol.set(resolved, info);
2545
+ return info;
2546
+ }
2547
+ // The expando property symbol: every top-level `X.N = …` assignment is
2548
+ // one of its declarations — exactly one, binding a class expression,
2549
+ // pins the class.
2550
+ const assigns = decls.filter((d) => ts.isBinaryExpression(d) && d.operatorToken.kind === ts.SyntaxKind.EqualsToken);
2551
+ if (assigns.length !== 1 || assigns.length !== decls.length)
2552
+ return null;
2553
+ const a = assigns[0];
2554
+ if (!ts.isExpressionStatement(a.parent) || !ts.isSourceFile(a.parent.parent))
2555
+ return null;
2556
+ let rhs = a.right;
2557
+ while (ts.isParenthesizedExpression(rhs))
2558
+ rhs = rhs.expression;
2559
+ if (!ts.isClassExpression(rhs))
2560
+ return null;
2561
+ const info = L.lowerClassExpressionInfo(rhs);
2562
+ L.classBySymbol.set(resolved, info);
2563
+ return info;
2564
+ }
2565
+ /** The top-level `… = <this class expression>` assignment a class
2566
+ * expression is the (paren-unwrapped) RHS of, or null. */
2567
+ function enclosingTopLevelClassAssignment(expr) {
2568
+ let value = expr;
2569
+ while (ts.isParenthesizedExpression(value.parent))
2570
+ value = value.parent;
2571
+ const p = value.parent;
2572
+ if (!ts.isBinaryExpression(p) || p.operatorToken.kind !== ts.SyntaxKind.EqualsToken ||
2573
+ p.right !== value || !ts.isExpressionStatement(p.parent) || !ts.isSourceFile(p.parent.parent)) {
2574
+ return null;
2575
+ }
2576
+ return p;
2577
+ }
2578
+ /** How many top-level statements of the file assign the same target as
2579
+ * `assign` (textual LHS match — `module.exports`, `exports.I`,
2580
+ * `Common.I`): a second assignment makes the binding dynamic, so
2581
+ * callers refuse to pin the first one's class. */
2582
+ function countAssignmentsTo(assign) {
2583
+ const sf = assign.getSourceFile();
2584
+ // `exports.I` and `module.exports.I` are the SAME binding in Node
2585
+ // (exports aliases module.exports until a table replaces it) — fold
2586
+ // the member spellings together before comparing.
2587
+ const canon = (lhs) => {
2588
+ const text = lhs.getText().replace(/\s+/g, "");
2589
+ return text.startsWith("module.exports.") ? text.slice("module.".length) : text;
2590
+ };
2591
+ const target = canon(assign.left);
2592
+ let n = 0;
2593
+ for (const stmt of sf.statements) {
2594
+ if (!ts.isExpressionStatement(stmt))
2595
+ continue;
2596
+ const e = stmt.expression;
2597
+ if (ts.isBinaryExpression(e) && e.operatorToken.kind === ts.SyntaxKind.EqualsToken &&
2598
+ canon(e.left) === target) {
2599
+ n++;
2600
+ }
2601
+ }
2602
+ return n;
2603
+ }
2604
+ /** `C.m(args)` / `X.m(args)` — static method calls, on the class name
2605
+ * directly or through a class VALUE. Resolution walks the chain
2606
+ * (nearest declarer, the compile-time prototype chain); through a
2607
+ * VALUE the call devirtualizes exactly when no strict descendant
2608
+ * redeclares the member (values never leave the static class's
2609
+ * subtree). A func-typed static FIELD in call position reads the
2610
+ * global and calls through the value. Null when the receiver isn't a
2611
+ * class name/value or the member doesn't resolve (the fences name the
2612
+ * site downstream). */
2613
+ export function lowerStaticMethodCall(L, call, access) {
2614
+ if (access.questionDotToken)
2615
+ return null;
2616
+ // `module.exports.describe()` in a module whose whole export IS a
2617
+ // class expression: the receiver is exactly that class (the kept
2618
+ // export assignment pins it) — the direct-name rules apply.
2619
+ if (!ts.isIdentifier(access.expression)) {
2620
+ if (!isModuleExportsAccess(access.expression) || !isCjsJsFile(access.getSourceFile())) {
2621
+ return null;
2622
+ }
2623
+ const whole = cjsClassExprWholeExportOf(access.getSourceFile());
2624
+ if (!whole)
2625
+ return null;
2626
+ return staticCallOn(L, call, access, L.lowerClassExpressionInfo(whole.classExpr), false);
2627
+ }
2628
+ const symbol = L.resolveValueSymbol(access.expression);
2629
+ const direct = (symbol ? L.classBySymbol.get(symbol) : undefined) ??
2630
+ // A require binding over `module.exports = class {…}`: the alias
2631
+ // lands on the expression's own symbol — exact, like the name.
2632
+ propertyAssignedClassInfoOf(L, symbol) ??
2633
+ undefined;
2634
+ let info = direct ?? null;
2635
+ // A rebindable decorated name is a class VALUE receiver: the call
2636
+ // devirtualizes under the value rules (shadow fences below).
2637
+ let throughValue = direct?.classDecorators?.valueGlobalId !== undefined;
2638
+ if (!info) {
2639
+ const recvT = L.mapTypeOf(L.typeOf(access.expression));
2640
+ if (recvT?.kind !== "classval")
2641
+ return null;
2642
+ info = L.classes.get(recvT.className) ?? null;
2643
+ throughValue = true;
2644
+ }
2645
+ if (!info)
2646
+ return null;
2647
+ return staticCallOn(L, call, access, info, throughValue);
2648
+ }
2649
+ function staticCallOn(L, call, access, info, throughValue) {
2650
+ const loc = locOf(call);
2651
+ const found = findStaticOn(L, info, access.name.text);
2652
+ if (!found) {
2653
+ // GENERIC static methods: monomorphized like top-level generic
2654
+ // functions, called directly as `%C.static:m%n` — with the same
2655
+ // through-a-VALUE shadowing fence as plain statics.
2656
+ const gfound = findGenericStaticOn(L, info, access.name.text);
2657
+ if (!gfound)
2658
+ return null;
2659
+ if (throughValue && staticShadowBelow(L, info, access.name.text)) {
2660
+ L.unsupported("SC1090", call, `calling the static member '${access.name.text}' through a class value (a subclass of '${info.def.name.replace(/^%|^%m\d+\./, "")}' redeclares it, so the runtime class decides which declaration answers)`);
2661
+ }
2662
+ const instance = genericCallInstance(L, call, gfound.info);
2663
+ const args = L.completeArgs(call.arguments, instance.params, loc, call);
2664
+ return { kind: "call", callee: instance.name, args, type: instance.returnType, loc };
2665
+ }
2666
+ if (throughValue && staticShadowBelow(L, info, access.name.text)) {
2667
+ L.unsupported("SC1090", call, `calling the static member '${access.name.text}' through a class value (a subclass of '${info.def.name.replace(/^%|^%m\d+\./, "")}' redeclares it, so the runtime class decides which declaration answers)`);
2668
+ }
2669
+ if (found.field !== undefined) {
2670
+ // A func-typed static field in call position: read the global,
2671
+ // call through the value (the ctor-assigned-callback pattern).
2672
+ if (found.field.type.kind !== "func")
2673
+ return null;
2674
+ const callee = { kind: "varRef", localId: found.field.globalId, type: found.field.type, loc };
2675
+ const params = found.field.type.params;
2676
+ const args = call.arguments.map((a, i) => L.lowerExprExpecting(a, params[i]));
2677
+ for (let i = args.length; i < params.length; i++) {
2678
+ const absent = omittedArgFor(L, params[i], loc);
2679
+ if (!absent) {
2680
+ L.unsupported("SC1090", call, "calls omitting a non-optional parameter of the callee's type");
2681
+ }
2682
+ args.push(absent);
2683
+ }
2684
+ return { kind: "callValue", callee, args, type: found.field.type.ret, loc };
2685
+ }
2686
+ const fnName = `%${found.declarer.def.name}.static:${access.name.text}`;
2687
+ L.noteEdge(fnName);
2688
+ const args = L.completeArgs(call.arguments, found.method.params, loc, call);
2689
+ return { kind: "call", callee: fnName, args, type: found.method.ret, loc };
2690
+ }
2691
+ /** Static member access through a class VALUE (`X.m` where X is
2692
+ * classval-typed): devirtualized — the member resolves against the
2693
+ * static class's chain, exact when no strict descendant redeclares it
2694
+ * (values in the slot never leave the subtree). `X.name` is the one
2695
+ * genuinely dynamic member: the class.name libCall reads the runtime
2696
+ * class object's stored name. Null when the receiver isn't a class
2697
+ * value or the member doesn't resolve. */
2698
+ export function lowerClassValueProperty(L, expr) {
2699
+ if (expr.questionDotToken)
2700
+ return null;
2701
+ const recvT = L.mapTypeOf(L.typeOf(expr.expression));
2702
+ if (recvT?.kind !== "classval")
2703
+ return null;
2704
+ const loc = locOf(expr);
2705
+ const member = expr.name.text;
2706
+ // `X.name` reads the RUNTIME class object's stored name — the one
2707
+ // genuinely dynamic member. It CONSUMES the receiver (no evaluation
2708
+ // is discarded), so any receiver expression is fine here.
2709
+ if (member === "name") {
2710
+ const recv = L.lowerExpr(expr.expression);
2711
+ if (recv.type.kind !== "classval")
2712
+ return null;
2713
+ return { kind: "libCall", fn: "class.name", args: [recv], type: STRING, loc };
2714
+ }
2715
+ if (!ts.isIdentifier(expr.expression) &&
2716
+ // `module.exports.label` in a class-replaced CJS module: the
2717
+ // receiver is the exact exported class, and the read is
2718
+ // side-effect-free — as bindable as an identifier.
2719
+ !(isModuleExportsAccess(expr.expression) && isCjsJsFile(expr.getSourceFile()))) {
2720
+ // Devirtualized reads DISCARD the receiver value, so only
2721
+ // side-effect-free receivers are claimed (the instanceOf fold
2722
+ // rule); computed ones meet the pointed fence with a bindable fix.
2723
+ L.unsupported("SC1090", expr, "static member access through a computed class-value expression (bind the class value to a variable first)");
2724
+ }
2725
+ // The direct class-name spelling resolved in lowerStaticFieldRead;
2726
+ // reaching here means the receiver is a classval-typed BINDING.
2727
+ const info = L.classes.get(recvT.className);
2728
+ if (!info)
2729
+ return null;
2730
+ const found = findStaticOn(L, info, member);
2731
+ if (!found) {
2732
+ L.unsupported("SC1090", expr, `the static member '${member}' of class '${info.def.name.replace(/^%|^%m\d+\./, "")}' (static accessors and initializer-less static fields have no lowering, and Function members like .call/.bind/.prototype have no value form)`);
2733
+ }
2734
+ if (staticShadowBelow(L, info, member)) {
2735
+ L.unsupported("SC1090", expr, `reading the static member '${member}' through a class value (a subclass of '${info.def.name.replace(/^%|^%m\d+\./, "")}' redeclares it, so the runtime class decides which declaration answers)`);
2736
+ }
2737
+ if (found.field !== undefined) {
2738
+ return L.maybeNarrow({ kind: "varRef", localId: found.field.globalId, type: found.field.type, loc }, expr);
2739
+ }
2740
+ return staticMethodValue(L, found.declarer, member, found.method, expr, loc);
2741
+ }
2742
+ /** An abstract method's signature — lambdaSignature minus the body check
2743
+ * (an abstract declaration IS exactly a signature; tsc rejects the
2744
+ * async/generator/generic-with-body combinations before this runs, and
2745
+ * the generic case fences at the caller). */
2746
+ function abstractMemberSignature(L, member) {
2747
+ for (const param of member.parameters) {
2748
+ if (!ts.isIdentifier(param.name))
2749
+ L.unsupported("SC1031", param);
2750
+ }
2751
+ return { shapes: L.paramShapes(member.parameters), ret: L.declaredReturnType(member, member.name) };
2752
+ }
2753
+ /** The nearest declaration of `name` at or above `info` — the method a
2754
+ * receiver of that static class runs when nothing below overrides it. */
2755
+ export function findMethodOn(L, info, name) {
2756
+ for (let c = info; c; c = c.base) {
2757
+ const sig = c.methods.get(name);
2758
+ if (sig)
2759
+ return { declarer: c, sig };
2760
+ }
2761
+ return null;
2762
+ }
2763
+ /** True when `sub` is a STRICT descendant of `sup` in the class graph. */
2764
+ export function isSubclassOf(L, sub, sup) {
2765
+ for (let c = L.classes.get(sub)?.base ?? null; c; c = c.base) {
2766
+ if (c.def.name === sup)
2767
+ return true;
2768
+ }
2769
+ return false;
2770
+ }
2771
+ /** In an extends-hierarchy (as base or derived): the class carries a
2772
+ * vtable and participates in dynamic instanceof; standalone classes keep
2773
+ * their exact pre-inheritance layout and behavior. */
2774
+ export function inHierarchy(L, info) {
2775
+ // The runtime emitter class is ALWAYS a hierarchy member: ScrEmitter
2776
+ // carries its vtable word whether or not the program subclasses it
2777
+ // (the runtime allocates bare instances with scr_emitter_vt).
2778
+ return info.base !== null || info.subclasses.length > 0 || info.builtinEmitter === true;
2779
+ }
2780
+ /** True when some STRICT descendant of `info` declares `name` with a BODY
2781
+ * — the whole-program devirtualization test: a call through this static
2782
+ * class can reach a distinct implementation, so it must dispatch
2783
+ * dynamically. Abstract re-declarations don't count (they carry no
2784
+ * implementation; the concrete ones below them do, via the recursion). */
2785
+ export function overrideBelow(L, info, name) {
2786
+ return info.subclasses.some((s) => {
2787
+ const m = s.methods.get(name);
2788
+ return (m !== undefined && m.abstract !== true) || L.overrideBelow(s, name);
2789
+ });
2790
+ }
2791
+ /** The nearest GENERIC-method declaration of `name` at/above `info` —
2792
+ * findMethodOn's twin over the genericMethods tables. */
2793
+ export function findGenericMethodOn(L, info, name) {
2794
+ for (let c = info; c; c = c.base) {
2795
+ const gm = c.genericMethods?.get(name);
2796
+ if (gm)
2797
+ return { declarer: c, info: gm };
2798
+ }
2799
+ return null;
2800
+ }
2801
+ /** True when some STRICT descendant of `info` re-declares the generic
2802
+ * method `name` — overrideBelow's twin: generic methods have no vtable
2803
+ * slot, so a call that could reach an override compiles only when the
2804
+ * receiver's runtime class is statically exact. */
2805
+ export function genericOverrideBelow(L, info, name) {
2806
+ return info.subclasses.some((s) => s.genericMethods?.has(name) === true || genericOverrideBelow(L, s, name));
2807
+ }
2808
+ /** The receiver's EXACT runtime class, when the expression proves it: a
2809
+ * `new C(...)` expression directly, or a const binding initialized with
2810
+ * one (the binding can never be reassigned to a subclass instance).
2811
+ * The class is read off the mapped INITIALIZER type — a `const b: Base =
2812
+ * new D()` receiver is exactly D, not its annotation. Distinct from
2813
+ * exactClassOfReceiver, which answers for CLASS-VALUE receivers. */
2814
+ export function exactInstanceClassOf(L, expr) {
2815
+ let e = expr;
2816
+ while (ts.isParenthesizedExpression(e))
2817
+ e = e.expression;
2818
+ const classOfNew = (n) => {
2819
+ if (!ts.isNewExpression(n))
2820
+ return null;
2821
+ const t = L.mapTypeOf(L.typeOf(n));
2822
+ return t?.kind === "object" ? (L.classes.get(t.className) ?? null) : null;
2823
+ };
2824
+ const direct = classOfNew(e);
2825
+ if (direct)
2826
+ return direct;
2827
+ if (!ts.isIdentifier(e))
2828
+ return null;
2829
+ const symbol = L.resolveValueSymbol(e);
2830
+ const decl = symbol ? L.checker.valueDeclarationOf(symbol) : undefined;
2831
+ if (!decl || !ts.isVariableDeclaration(decl) || decl.initializer === undefined ||
2832
+ !ts.isVariableDeclarationList(decl.parent) ||
2833
+ (decl.parent.flags & ts.NodeFlags.Const) === 0) {
2834
+ return null;
2835
+ }
2836
+ let init = decl.initializer;
2837
+ while (ts.isParenthesizedExpression(init))
2838
+ init = init.expression;
2839
+ return classOfNew(init);
2840
+ }
2841
+ /** `recv.m<T>(args)` — a GENERIC method call, dispatched STATICALLY: the
2842
+ * checker's resolved signature (type arguments substituted, inferred or
2843
+ * explicit) keys one instantiation of the nearest declarer's body, and
2844
+ * the call is a direct `call` of `%C.m%n` over the (up/down)cast
2845
+ * receiver. No per-instantiation vtable slots exist, so a receiver whose
2846
+ * runtime class could OVERRIDE the method (genericOverrideBelow) must be
2847
+ * statically exact (exactInstanceClassOf) — the override set then
2848
+ * resolves at compile time — or fences by name. */
2849
+ export function lowerClassGenericMethodCall(L, call, access, recvInfo, found) {
2850
+ const name = access.name.text;
2851
+ let { declarer, info } = found;
2852
+ if (genericOverrideBelow(L, recvInfo, name)) {
2853
+ const exact = exactInstanceClassOf(L, access.expression);
2854
+ const refound = exact ? findGenericMethodOn(L, exact, name) : null;
2855
+ if (!refound) {
2856
+ L.unsupported("SC1090", call, `calling the generic method '${name}' through a receiver whose runtime class may override it (a subclass of '${recvInfo.def.name.replace(/^%|^%m\d+\./, "")}' redeclares it and generic methods dispatch statically — bind the receiver to a const initialized with its 'new' expression)`);
2857
+ }
2858
+ ({ declarer, info } = refound);
2859
+ }
2860
+ // Implicit-any methods instantiate over the call's ARGUMENT types
2861
+ // (there is no resolved generic signature — the untyped params are the
2862
+ // type parameters); everything else about the dispatch — static
2863
+ // resolution, the exactness rule above — is the generic story.
2864
+ const instance = info.implicitParams
2865
+ ? implicitCallInstance(L, call, info)
2866
+ : genericCallInstance(L, call, info);
2867
+ const receiver = L.lowerExpr(access.expression);
2868
+ const loc = locOf(call);
2869
+ // The declarer sits at/above the receiver's static class on the plain
2870
+ // path; the EXACT path can land below it (a base-typed const provably
2871
+ // holding the subclass) — that direction is the checker-grade downcast
2872
+ // (the exactness proof is static, stronger than an instanceof guard).
2873
+ const thisArg = receiver.type.kind === "object" && isSubclassOf(L, declarer.def.name, receiver.type.className)
2874
+ ? { kind: "downcast", value: receiver, type: { kind: "object", className: declarer.def.name }, loc }
2875
+ : upcastTo(L, receiver, declarer.def.name);
2876
+ const args = L.completeArgs(call.arguments, instance.params, loc, call);
2877
+ return { kind: "call", callee: instance.name, args: [thisArg, ...args], type: instance.returnType, loc };
2878
+ }
2879
+ /** Wraps a derived-class expression in an upcast when the target base
2880
+ * class differs (a no-op reinterpret at runtime; keeps IR types exact). */
2881
+ export function upcastTo(L, expr, className) {
2882
+ if (expr.type.kind === "object" && expr.type.className !== className) {
2883
+ return { kind: "upcast", value: expr, type: { kind: "object", className }, loc: expr.loc };
2884
+ }
2885
+ return expr;
2886
+ }
2887
+ /** Constructor and methods become module functions `%C.name` whose first
2888
+ * param is `this`. Field initializers run in declaration order at the top
2889
+ * of a base class's constructor; a derived class's run right after its
2890
+ * super() call returns (tsc/JS initialization order). Reachability gates
2891
+ * each member independently: an unreached method body never lowers and
2892
+ * never emits (pinned by the corpus), while every override a reachable
2893
+ * virtualCall can dispatch to was marked by the discovery pass. */
2894
+ /** True when a MIXIN class's constructor is the pure forwarding shape:
2895
+ * exactly one rest parameter, `super(...args)` as the first statement,
2896
+ * and no other reference to the parameter — the one rest-constructor
2897
+ * form with an exact static story under monomorphization (the
2898
+ * instantiation adopts the base's ABI; see collectClassShapeInner). */
2899
+ function mixinForwardingCtor(L, ctor) {
2900
+ if (ctor.parameters.length !== 1 || !ctor.body)
2901
+ return false;
2902
+ const p = ctor.parameters[0];
2903
+ if (!p.dotDotDotToken || !ts.isIdentifier(p.name))
2904
+ return false;
2905
+ const paramName = p.name;
2906
+ const paramSym = L.checker.getSymbolAtLocation(paramName);
2907
+ if (!paramSym)
2908
+ return false;
2909
+ const first = ctor.body.statements[0];
2910
+ if (!first || !ts.isExpressionStatement(first) || !ts.isCallExpression(first.expression))
2911
+ return false;
2912
+ const call = first.expression;
2913
+ if (call.expression.kind !== ts.SyntaxKind.SuperKeyword)
2914
+ return false;
2915
+ if (call.arguments.length !== 1)
2916
+ return false;
2917
+ const a = call.arguments[0];
2918
+ if (!ts.isSpreadElement(a) || !ts.isIdentifier(a.expression))
2919
+ return false;
2920
+ const spreadIdent = a.expression;
2921
+ if (L.checker.getSymbolAtLocation(spreadIdent) !== paramSym)
2922
+ return false;
2923
+ let extraRef = false;
2924
+ ts.walkPreorder(ctor.body, (n) => {
2925
+ if (n === spreadIdent)
2926
+ return undefined;
2927
+ if (ts.isIdentifier(n) && n.text === paramName.text && L.checker.getSymbolAtLocation(n) === paramSym) {
2928
+ extraRef = true;
2929
+ return "stop";
2930
+ }
2931
+ return undefined;
2932
+ });
2933
+ return !extraRef;
2934
+ }
2935
+ export function lowerClassMembers(L, info) {
2936
+ const out = [];
2937
+ const className = info.def.name;
2938
+ // Generic-class INSTANTIATIONS (and mixin instantiations) are
2939
+ // demand-driven like generic-fn instances, not reachability units:
2940
+ // they are never registered as units, so wantBody's name-keyed gate
2941
+ // cannot apply — every member of a demanded instantiation lowers.
2942
+ const always = info.genericInstance !== undefined || info.mixinInstance !== undefined;
2943
+ // A FAMILY has no constructor function at all (nothing constructs it;
2944
+ // construction resolves to instantiations) and declares no instance
2945
+ // members — only its statics lower below.
2946
+ // A poison OUTSIDE the per-statement catches (a fenced parameter
2947
+ // default — declareParams lowers it before any statement-level catch
2948
+ // exists): the diagnostic is recorded — the member skips like a
2949
+ // signature-blocked function (lowerStaticMethod's rule) instead of
2950
+ // crashing the whole lowering.
2951
+ if (!info.generic && (always || L.wantBody(`%${className}.constructor`))) {
2952
+ try {
2953
+ out.push(L.lowerClassCtor(info));
2954
+ }
2955
+ catch (e) {
2956
+ if (!(e instanceof PoisonError))
2957
+ throw e;
2958
+ }
2959
+ }
2960
+ for (const { mName, member } of L.classMethodMembers(info)) {
2961
+ if (!always && !L.wantBody(`%${className}.${mName}`))
2962
+ continue;
2963
+ try {
2964
+ const fn = L.lowerClassMethodMember(info, member);
2965
+ if (fn)
2966
+ out.push(fn);
2967
+ }
2968
+ catch (e) {
2969
+ if (!(e instanceof PoisonError))
2970
+ throw e;
2971
+ }
2972
+ }
2973
+ for (const name of info.staticMethods?.keys() ?? []) {
2974
+ if (!L.wantBody(`%${className}.static:${name}`))
2975
+ continue;
2976
+ const fn = lowerStaticMethod(L, info, name);
2977
+ if (fn)
2978
+ out.push(fn);
2979
+ }
2980
+ for (const prop of info.throwingSetters) {
2981
+ if (always || L.wantBody(`%${className}.set:${prop}`))
2982
+ out.push(L.throwingSetterFn(info, prop));
2983
+ }
2984
+ return out;
2985
+ }
2986
+ /** The constructor function `%C.constructor`. Synthesized when absent: a
2987
+ * base class runs just its field initializers; a derived class inherits
2988
+ * the base's signature — forward every param to super(), then run own
2989
+ * field initializers. */
2990
+ export function lowerClassCtor(L, info) {
2991
+ return withInstanceBindings(L, info, () => lowerClassCtorInner(L, info));
2992
+ }
2993
+ function lowerClassCtorInner(L, info) {
2994
+ const className = info.def.name;
2995
+ const thisType = { kind: "object", className };
2996
+ const prevClass = L.currentClass;
2997
+ L.currentClass = info;
2998
+ L.fnStack.push(newFnCtx(false, null, null, VOID));
2999
+ try {
3000
+ const thisLocal = L.declareThis(thisType);
3001
+ const params = [{ localId: thisLocal.id, name: "this", type: thisType }];
3002
+ const body = [];
3003
+ // The construction-relevant base: generic families are transparent
3004
+ // (an instantiation of a base-less generic class IS a base class —
3005
+ // its source has no super()).
3006
+ const ctorBase = superBaseOf(info);
3007
+ if (info.ctor && info.mixinInstance?.forwardingCtor) {
3008
+ // The mixin FORWARDING constructor: the declared rest parameter
3009
+ // never materializes — the ABI is the base's (synthetic params,
3010
+ // the synthesized-ctor rule), `super(...args)` forwards them
3011
+ // unchanged, and the remaining statements lower normally.
3012
+ const loc = locOf(info.ctor);
3013
+ const forward = info.ctorParams.map((shape, i) => {
3014
+ const local = { id: `arg${i}.0`, name: `arg${i}`, type: shape.type, mutable: false };
3015
+ L.ctx.locals.push(local);
3016
+ params.push({ localId: local.id, name: local.name, type: shape.type });
3017
+ return { kind: "varRef", localId: local.id, type: shape.type, loc };
3018
+ });
3019
+ body.push(...L.lowerDerivedCtorBody(info, thisLocal, forward));
3020
+ }
3021
+ else if (info.ctor) {
3022
+ // The default-param prologue runs FIRST — before field initializers
3023
+ // and (in a derived class) before super(): JS evaluates parameter
3024
+ // defaults on entry, ahead of everything the body does.
3025
+ const declared = L.declareParams(info.ctor.parameters, info.ctorParams);
3026
+ params.push(...declared.params);
3027
+ body.push(...declared.prologue);
3028
+ if (!ctorBase) {
3029
+ // Node's base-class order: field initializers run at the start
3030
+ // of construction, the parameter-property assignments open the
3031
+ // constructor body (probed — a field initializer reading a
3032
+ // parameter property sees undefined).
3033
+ body.push(...L.fieldInitStmts(info, thisLocal));
3034
+ body.push(...paramPropInitStmts(L, info, thisLocal));
3035
+ if (info.ctor.body)
3036
+ body.push(...L.lowerStmts(info.ctor.body.statements));
3037
+ }
3038
+ else if (info.ctor.body) {
3039
+ body.push(...L.lowerDerivedCtorBody(info, thisLocal));
3040
+ }
3041
+ }
3042
+ else {
3043
+ if (ctorBase) {
3044
+ // Synthetic forwarding params (the inherited ABI signature).
3045
+ // Nothing references them by symbol — only the super call below,
3046
+ // which forwards the already-completed values UNCHANGED (defaults
3047
+ // apply in the base constructor's own prologue, never twice).
3048
+ const loc = locOf(info.decl);
3049
+ const superArgs = info.ctorParams.map((shape, i) => {
3050
+ const local = { id: `arg${i}.0`, name: `arg${i}`, type: shape.type, mutable: false };
3051
+ L.ctx.locals.push(local);
3052
+ params.push({ localId: local.id, name: local.name, type: shape.type });
3053
+ return { kind: "varRef", localId: local.id, type: shape.type, loc };
3054
+ });
3055
+ try {
3056
+ body.push(L.superCallStmt(info, thisLocal, superArgs, loc));
3057
+ }
3058
+ catch (e) {
3059
+ // A synthesized super() can fence (a stream base whose
3060
+ // underscore methods have no lowering): the diagnostic was
3061
+ // pushed; the half-initialized ctor stays out of the body.
3062
+ if (!(e instanceof PoisonError))
3063
+ throw e;
3064
+ }
3065
+ }
3066
+ body.push(...L.fieldInitStmts(info, thisLocal));
3067
+ }
3068
+ return {
3069
+ name: `%${className}.constructor`,
3070
+ params,
3071
+ returnType: VOID,
3072
+ locals: L.ctx.locals,
3073
+ body,
3074
+ loc: locOf(info.ctor ?? info.decl),
3075
+ };
3076
+ }
3077
+ finally {
3078
+ L.fnStack.pop();
3079
+ L.currentClass = prevClass;
3080
+ }
3081
+ }
3082
+ /** The lowered method-map name of a class member: identifier text, a
3083
+ * COMPUTED name that folds to one compile-time string
3084
+ * (foldedStringKeyOf — the object-literal computed-key machinery
3085
+ * applied to method positions; tsc late-bound the member under exactly
3086
+ * that name), or the reserved slot "sym:iterator" for
3087
+ * `[Symbol.iterator]` (a name no user identifier can spell — the
3088
+ * accessor "get:x" convention; for-of, spreads, and array destructuring
3089
+ * dispatch to it through the iterator protocol). Null for genuinely
3090
+ * runtime-keyed names — the computed-member fences stay. */
3091
+ export function classMemberNameOf(L, name) {
3092
+ if (ts.isIdentifier(name))
3093
+ return name.text;
3094
+ if (!ts.isComputedPropertyName(name))
3095
+ return null;
3096
+ let e = name.expression;
3097
+ while (ts.isParenthesizedExpression(e))
3098
+ e = e.expression;
3099
+ if (ts.isPropertyAccessExpression(e) && L.stdlibGlobalMember(e, "Symbol") === "iterator") {
3100
+ return "sym:iterator";
3101
+ }
3102
+ return L.foldedStringKeyOf(name.expression);
3103
+ }
3104
+ export function classIteratorOf(L, t) {
3105
+ if (t.kind !== "object")
3106
+ return null;
3107
+ const info = L.classes.get(t.className);
3108
+ if (!info)
3109
+ return null;
3110
+ const iter = findMethodOn(L, info, "sym:iterator");
3111
+ if (!iter || iter.sig.params.length !== 0 || iter.sig.abstract === true)
3112
+ return null;
3113
+ const iterT = iter.sig.ret;
3114
+ if (iterT.kind !== "object")
3115
+ return null;
3116
+ const itInfo = L.classes.get(iterT.className);
3117
+ if (!itInfo)
3118
+ return null;
3119
+ // IteratorClose honesty: a declared return()/throw() would be called
3120
+ // by JS on abrupt completion; these desugars never close.
3121
+ if (findMethodOn(L, itInfo, "return") || findMethodOn(L, itInfo, "throw"))
3122
+ return null;
3123
+ const next = findMethodOn(L, itInfo, "next");
3124
+ if (!next || next.sig.params.length !== 0 || next.sig.abstract === true)
3125
+ return null;
3126
+ const resultT = next.sig.ret;
3127
+ if (resultT.kind !== "record")
3128
+ return null;
3129
+ const shape = L.shapes.get(resultT.shapeId);
3130
+ const value = shape?.fields.find((f) => f.name === "value");
3131
+ if (!shape || !value)
3132
+ return null;
3133
+ const done = shape.fields.find((f) => f.name === "done");
3134
+ if (done && done.type.kind !== "bool")
3135
+ return null;
3136
+ return { className: t.className, iterT, resultT, valueT: value.type, hasDone: done !== undefined };
3137
+ }
3138
+ /** The `it.next()` step of a class iterator as an ordinary (possibly
3139
+ * virtual) method call. */
3140
+ export function classIteratorNextCall(L, cit, itRef, loc) {
3141
+ return accessorCall(L, cit.iterT.className, "next", itRef, [], cit.resultT, loc);
3142
+ }
3143
+ /** `recv[Symbol.iterator]()` as an ordinary method call. */
3144
+ export function classIteratorOpenCall(L, cit, recv, loc) {
3145
+ return accessorCall(L, cit.className, "sym:iterator", recv, [], cit.iterT, loc);
3146
+ }
3147
+ /** `[...new C]` / `f(...new C)` over a CLASS ITERABLE: the eager drain —
3148
+ * an interned `%iter.drain.<n>(recv)` lifted function running the
3149
+ * whole protocol into a fresh element array (a doneless iterator loops
3150
+ * forever, exactly Node's spread of an infinite iterator). `elemT`
3151
+ * (default: the iterator's own value type) is the DESTINATION element —
3152
+ * a spread into a union-element literal (`[...numbers, ...symbols]` as
3153
+ * `(number | symbol)[]`) pushes each value wrapped into its arm. Null
3154
+ * when the value isn't a recognized class iterable or the element
3155
+ * doesn't coerce — spread fences stay. */
3156
+ export function classIteratorDrainCall(L, src, loc, elemT) {
3157
+ const cit = classIteratorOf(L, src.type);
3158
+ if (!cit)
3159
+ return null;
3160
+ const outElem = elemT ?? cit.valueT;
3161
+ // Probe the element coercion purely: identical types, or an arm of a
3162
+ // union destination (the wrap coerceToExpected applies below).
3163
+ if (!typeEquals(outElem, cit.valueT)) {
3164
+ if (outElem.kind !== "union" || L.armTag(outElem.unionId, cit.valueT) < 0)
3165
+ return null;
3166
+ }
3167
+ const outT = arrayOf(outElem);
3168
+ const key = `${cit.className}:${typeKey(outElem)}`;
3169
+ let name = L.iterDrainHelpers.get(key);
3170
+ if (!name) {
3171
+ name = `%iter.drain.${L.iterDrainHelpers.size}`;
3172
+ L.iterDrainHelpers.set(key, name);
3173
+ const recvT = { kind: "object", className: cit.className };
3174
+ const recvRef = { kind: "varRef", localId: "r.0", type: recvT, loc };
3175
+ const itRef = { kind: "varRef", localId: "it.0", type: cit.iterT, loc };
3176
+ const outRef = { kind: "varRef", localId: "out.0", type: outT, loc };
3177
+ const resRef = { kind: "varRef", localId: "res.0", type: cit.resultT, loc };
3178
+ const valueRead = { kind: "recordGet", obj: resRef, shapeId: cit.resultT.shapeId, field: "value", type: cit.valueT, loc };
3179
+ const loop = [
3180
+ { kind: "varDecl", localId: "res.0", init: classIteratorNextCall(L, cit, itRef, loc), loc },
3181
+ ...(cit.hasDone
3182
+ ? [
3183
+ {
3184
+ kind: "if",
3185
+ cond: { kind: "recordGet", obj: resRef, shapeId: cit.resultT.shapeId, field: "done", type: BOOL, loc },
3186
+ then: [{ kind: "return", value: outRef, loc }],
3187
+ else_: null,
3188
+ loc,
3189
+ },
3190
+ ]
3191
+ : []),
3192
+ {
3193
+ kind: "exprStmt",
3194
+ expr: {
3195
+ kind: "arrIntrinsic",
3196
+ method: "push",
3197
+ receiver: outRef,
3198
+ args: [L.coerceToExpected(valueRead, outElem)],
3199
+ type: F64,
3200
+ loc,
3201
+ },
3202
+ loc,
3203
+ },
3204
+ ];
3205
+ L.liftedFns.push({
3206
+ name,
3207
+ params: [{ localId: "r.0", name: "r", type: recvT }],
3208
+ returnType: outT,
3209
+ locals: [
3210
+ { id: "r.0", name: "r", type: recvT, mutable: false },
3211
+ { id: "it.0", name: "it", type: cit.iterT, mutable: false },
3212
+ { id: "out.0", name: "out", type: outT, mutable: false },
3213
+ { id: "res.0", name: "res", type: cit.resultT, mutable: true },
3214
+ ],
3215
+ body: [
3216
+ { kind: "varDecl", localId: "out.0", init: { kind: "arrayLit", elems: [], type: outT, loc }, loc },
3217
+ { kind: "varDecl", localId: "it.0", init: classIteratorOpenCall(L, cit, recvRef, loc), loc },
3218
+ {
3219
+ kind: "while",
3220
+ cond: { kind: "boolLit", value: true, type: BOOL, loc },
3221
+ body: loop,
3222
+ loc,
3223
+ },
3224
+ // Doneless iterators never leave the loop; satisfies the
3225
+ // all-paths-return rule (the retag-helper convention).
3226
+ {
3227
+ kind: "throw",
3228
+ value: { kind: "strLit", value: "scriptc: internal error: iterator drain fell through", type: STRING, loc },
3229
+ loc,
3230
+ },
3231
+ ],
3232
+ loc,
3233
+ });
3234
+ }
3235
+ return { kind: "call", callee: name, args: [src], type: outT, loc };
3236
+ }
3237
+ /** The tail of a class iterable's protocol from an already-open ITERATOR
3238
+ * object (`var [a, ...rest] = new C` — the rest element drains whatever
3239
+ * next() still yields): the drain loop keyed by the iterator class. */
3240
+ export function classIteratorRestDrainCall(L, cit, itVal, loc) {
3241
+ const outT = arrayOf(cit.valueT);
3242
+ const key = `it:${cit.iterT.className}`;
3243
+ let name = L.iterDrainHelpers.get(key);
3244
+ if (!name) {
3245
+ name = `%iter.drain.${L.iterDrainHelpers.size}`;
3246
+ L.iterDrainHelpers.set(key, name);
3247
+ const itRef = { kind: "varRef", localId: "it.0", type: cit.iterT, loc };
3248
+ const outRef = { kind: "varRef", localId: "out.0", type: outT, loc };
3249
+ const resRef = { kind: "varRef", localId: "res.0", type: cit.resultT, loc };
3250
+ const loop = [
3251
+ { kind: "varDecl", localId: "res.0", init: classIteratorNextCall(L, cit, itRef, loc), loc },
3252
+ ...(cit.hasDone
3253
+ ? [
3254
+ {
3255
+ kind: "if",
3256
+ cond: { kind: "recordGet", obj: resRef, shapeId: cit.resultT.shapeId, field: "done", type: BOOL, loc },
3257
+ then: [{ kind: "return", value: outRef, loc }],
3258
+ else_: null,
3259
+ loc,
3260
+ },
3261
+ ]
3262
+ : []),
3263
+ {
3264
+ kind: "exprStmt",
3265
+ expr: {
3266
+ kind: "arrIntrinsic",
3267
+ method: "push",
3268
+ receiver: outRef,
3269
+ args: [{ kind: "recordGet", obj: resRef, shapeId: cit.resultT.shapeId, field: "value", type: cit.valueT, loc }],
3270
+ type: F64,
3271
+ loc,
3272
+ },
3273
+ loc,
3274
+ },
3275
+ ];
3276
+ L.liftedFns.push({
3277
+ name,
3278
+ params: [{ localId: "it.0", name: "it", type: cit.iterT }],
3279
+ returnType: outT,
3280
+ locals: [
3281
+ { id: "it.0", name: "it", type: cit.iterT, mutable: false },
3282
+ { id: "out.0", name: "out", type: outT, mutable: false },
3283
+ { id: "res.0", name: "res", type: cit.resultT, mutable: true },
3284
+ ],
3285
+ body: [
3286
+ { kind: "varDecl", localId: "out.0", init: { kind: "arrayLit", elems: [], type: outT, loc }, loc },
3287
+ { kind: "while", cond: { kind: "boolLit", value: true, type: BOOL, loc }, body: loop, loc },
3288
+ {
3289
+ kind: "throw",
3290
+ value: { kind: "strLit", value: "scriptc: internal error: iterator drain fell through", type: STRING, loc },
3291
+ loc,
3292
+ },
3293
+ ],
3294
+ loc,
3295
+ });
3296
+ }
3297
+ return { kind: "call", callee: name, args: [itVal], type: outT, loc };
3298
+ }
3299
+ /** One method or accessor body as its module function `%C.name`
3300
+ * (accessors are methods with property syntax: "get:x"/"set:x" entries —
3301
+ * see collectClassShape). */
3302
+ export function lowerClassMethodMember(L, info, fnLike) {
3303
+ return withInstanceBindings(L, info, () => lowerClassMethodMemberInner(L, info, fnLike));
3304
+ }
3305
+ function lowerClassMethodMemberInner(L, info, fnLike) {
3306
+ const className = info.def.name;
3307
+ const thisType = { kind: "object", className };
3308
+ const memberName = ts.isMethodDeclaration(fnLike) ? classMemberNameOf(L, fnLike.name) : ts.isIdentifier(fnLike.name) ? fnLike.name.text : null;
3309
+ if (memberName === null)
3310
+ return null;
3311
+ const mName = ts.isMethodDeclaration(fnLike)
3312
+ ? memberName
3313
+ : `${ts.isGetAccessor(fnLike) ? "get" : "set"}:${memberName}`;
3314
+ const sig = info.methods.get(mName);
3315
+ if (!sig || !fnLike.body)
3316
+ return null;
3317
+ const prevClass = L.currentClass;
3318
+ L.currentClass = info;
3319
+ // ASYNC methods: the module function is an async IrFunction — its
3320
+ // body returns the promise's INNER type (a `return v` fulfills with
3321
+ // v) and every call enters through the emitted fiber spawn wrapper
3322
+ // (callTargetC routes by fn.async; `this` rides as param 0 in the
3323
+ // spawn's argument pack). Dispatch is static by construction — the
3324
+ // override fence at collection keeps async methods out of vtables.
3325
+ const isAsync = sig.async === true && sig.ret.kind === "promise";
3326
+ const bodyReturn = isAsync && sig.ret.kind === "promise" ? sig.ret.inner : sig.ret;
3327
+ const fnCtx = newFnCtx(false, null, null, bodyReturn);
3328
+ fnCtx.isAsync = isAsync;
3329
+ L.fnStack.push(fnCtx);
3330
+ try {
3331
+ const thisLocal = L.declareThis(thisType);
3332
+ const params = [{ localId: thisLocal.id, name: "this", type: thisType }];
3333
+ // `this` is declared first, so method parameter DEFAULTS may use it
3334
+ // (JS allows this in method defaults; it is param 0 here).
3335
+ const declared = L.declareParams(fnLike.parameters, sig.params);
3336
+ params.push(...declared.params);
3337
+ const body = [...declared.prologue, ...L.lowerStmts(fnLike.body.statements)];
3338
+ const fn = {
3339
+ name: `%${className}.${mName}`,
3340
+ params,
3341
+ returnType: bodyReturn,
3342
+ locals: L.ctx.locals,
3343
+ body,
3344
+ loc: locOf(fnLike),
3345
+ };
3346
+ if (isAsync)
3347
+ fn.async = true;
3348
+ return fn;
3349
+ }
3350
+ finally {
3351
+ L.fnStack.pop();
3352
+ L.currentClass = prevClass;
3353
+ }
3354
+ }
3355
+ /** One static method body as its module function `%C.static:m` — an
3356
+ * ordinary function with NO `this` param. `this` and `super` inside
3357
+ * name the RECEIVER class in JS (dynamic — `F.who()` sees F even when
3358
+ * who() is declared on E), which has no static story here: both are
3359
+ * named fences, with arrow functions transparent (they inherit the
3360
+ * method's `this`) and this-binding function forms opaque — the static-
3361
+ * block rule verbatim. */
3362
+ export function lowerStaticMethod(L, info, name) {
3363
+ const entry = info.staticMethods?.get(name);
3364
+ if (!entry?.member.body)
3365
+ return null;
3366
+ const checkThis = (n) => {
3367
+ if (ts.isFunctionExpression(n) || ts.isFunctionDeclaration(n) ||
3368
+ ts.isMethodDeclaration(n) || ts.isConstructorDeclaration(n) ||
3369
+ ts.isGetAccessor(n) || ts.isSetAccessor(n) ||
3370
+ ts.isClassDeclaration(n) || ts.isClassExpression(n)) {
3371
+ return;
3372
+ }
3373
+ if (n.kind === ts.SyntaxKind.ThisKeyword || n.kind === ts.SyntaxKind.SuperKeyword) {
3374
+ L.unsupported("SC1090", n, `'${n.kind === ts.SyntaxKind.ThisKeyword ? "this" : "super"}' in static methods (it names the RECEIVER class — a dynamic value; reference the class by name instead)`);
3375
+ }
3376
+ n.forEachChild(checkThis);
3377
+ };
3378
+ // Async statics: an async IrFunction like any module function — the
3379
+ // body returns the promise's INNER type, calls enter through the
3380
+ // fiber spawn wrapper (callTargetC routes by fn.async).
3381
+ const isAsync = entry.member.modifiers?.some((m) => m.kind === ts.SyntaxKind.AsyncKeyword) === true &&
3382
+ entry.ret.kind === "promise";
3383
+ const bodyReturn = isAsync && entry.ret.kind === "promise" ? entry.ret.inner : entry.ret;
3384
+ const fnCtx = newFnCtx(false, null, null, bodyReturn);
3385
+ fnCtx.isAsync = isAsync;
3386
+ L.fnStack.push(fnCtx);
3387
+ try {
3388
+ entry.member.body.forEachChild(checkThis);
3389
+ const declared = L.declareParams(entry.member.parameters, entry.params);
3390
+ const body = [...declared.prologue, ...L.lowerStmts(entry.member.body.statements)];
3391
+ const fn = {
3392
+ name: `%${info.def.name}.static:${name}`,
3393
+ params: declared.params,
3394
+ returnType: bodyReturn,
3395
+ locals: L.ctx.locals,
3396
+ body,
3397
+ loc: locOf(entry.member),
3398
+ };
3399
+ if (isAsync)
3400
+ fn.async = true;
3401
+ return fn;
3402
+ }
3403
+ catch (e) {
3404
+ // A poison OUTSIDE the per-statement catches (the this/super fence,
3405
+ // a fenced parameter default): the diagnostic is recorded — the
3406
+ // method skips like a signature-blocked function (lowerFunction's
3407
+ // rule) instead of killing the whole analysis.
3408
+ if (!(e instanceof PoisonError))
3409
+ throw e;
3410
+ return null;
3411
+ }
3412
+ finally {
3413
+ L.fnStack.pop();
3414
+ }
3415
+ }
3416
+ /** A synthesized throwing setter: a getter-only override shadows the
3417
+ * inherited pair (JS), so a base-typed write dispatches HERE and must
3418
+ * throw exactly like Node's TypeError — a real instance (a typed catch's
3419
+ * `e instanceof TypeError` matches), catchable, exit 1 uncaught (message
3420
+ * text is compiler-worded; stdout and exit code are the contract). */
3421
+ export function throwingSetterFn(L, info, prop) {
3422
+ const className = info.def.name;
3423
+ const thisType = { kind: "object", className };
3424
+ const sig = info.methods.get(`set:${prop}`);
3425
+ const loc = locOf(info.decl);
3426
+ const locals = [
3427
+ { id: "this.0", name: "this", type: thisType, mutable: false },
3428
+ { id: "v.0", name: "v", type: sig.params[0].type, mutable: false },
3429
+ ];
3430
+ return {
3431
+ name: `%${className}.set:${prop}`,
3432
+ params: locals.map((l) => ({ localId: l.id, name: l.name, type: l.type })),
3433
+ returnType: VOID,
3434
+ locals,
3435
+ body: [
3436
+ {
3437
+ kind: "throw",
3438
+ value: {
3439
+ kind: "libCall",
3440
+ fn: "error.new",
3441
+ args: [
3442
+ {
3443
+ kind: "strLit",
3444
+ value: `Cannot set property ${prop} which has only a getter`,
3445
+ type: STRING,
3446
+ loc,
3447
+ },
3448
+ ],
3449
+ type: { kind: "object", className: "%TypeError" },
3450
+ loc,
3451
+ },
3452
+ loc,
3453
+ },
3454
+ ],
3455
+ loc,
3456
+ };
3457
+ }
3458
+ /** The base a constructor chain actually CALLS into: generic FAMILIES are
3459
+ * never constructed (no `%<family>.constructor` exists), so an
3460
+ * instantiation's construction-relevant base is the family's own base —
3461
+ * null when the generic class extends nothing, exactly the source's
3462
+ * story (tsc forbids super() there). Ordinary classes answer their base
3463
+ * unchanged. */
3464
+ export function superBaseOf(info) {
3465
+ const b = info.base;
3466
+ return b?.generic ? b.base : b;
3467
+ }
3468
+ /** The class's OWN field initializers as fieldSet statements (declaration
3469
+ * order) — a base constructor's prologue, a derived constructor's
3470
+ * super()-return continuation. */
3471
+ export function fieldInitStmts(L, info, thisLocal) {
3472
+ const out = [];
3473
+ const thisType = { kind: "object", className: info.def.name };
3474
+ for (const f of info.fieldOrder) {
3475
+ if (!f.initializer)
3476
+ continue;
3477
+ L.stats.statementsTotal++;
3478
+ L.bumpFileStat(locOf(f.initializer).file, "total");
3479
+ try {
3480
+ const value = L.lowerExprExpecting(f.initializer, f.type);
3481
+ out.push({
3482
+ kind: "fieldSet",
3483
+ obj: { kind: "varRef", localId: thisLocal.id, type: thisType, loc: locOf(f.initializer) },
3484
+ className: info.def.name,
3485
+ field: f.name,
3486
+ value,
3487
+ loc: locOf(f.initializer),
3488
+ });
3489
+ }
3490
+ catch (e) {
3491
+ if (!(e instanceof PoisonError))
3492
+ throw e;
3493
+ L.stats.statementsFailed++;
3494
+ L.bumpFileStat(locOf(f.initializer).file, "failed");
3495
+ }
3496
+ }
3497
+ return out;
3498
+ }
3499
+ /** PARAMETER-PROPERTY assignments (`this.x = x`, synthesized): run AFTER
3500
+ * the field initializers — Node's transform defines the fields at the
3501
+ * top of the class body (undefined until assigned) and injects the
3502
+ * assignments at the start of the constructor body, i.e. after super()
3503
+ * and after the (native) field initializers ran (probed: a field
3504
+ * initializer reading `this.x` sees undefined; the body sees the value).
3505
+ * Each assignment reads the parameter's BODY local (defaults already
3506
+ * applied by the declareParams prologue), whose type the collection made
3507
+ * the field's type — slot-exact by construction. */
3508
+ export function paramPropInitStmts(L, info, thisLocal) {
3509
+ const out = [];
3510
+ const thisType = { kind: "object", className: info.def.name };
3511
+ for (const pp of info.paramProps ?? []) {
3512
+ const loc = locOf(pp.param);
3513
+ const local = ts.isIdentifier(pp.param.name) ? L.resolveLocal(pp.param.name) : null;
3514
+ if (!local || !typeEquals(local.type, pp.type)) {
3515
+ // Defensive: collection derived the field type from the same
3516
+ // paramShape the ctor's declareParams bound — they cannot diverge.
3517
+ L.unsupported("SC1090", pp.param, "this parameter property form");
3518
+ }
3519
+ out.push({
3520
+ kind: "fieldSet",
3521
+ obj: { kind: "varRef", localId: thisLocal.id, type: thisType, loc },
3522
+ className: info.def.name,
3523
+ field: pp.name,
3524
+ value: { kind: "varRef", localId: local.id, type: local.type, loc },
3525
+ loc,
3526
+ });
3527
+ }
3528
+ return out;
3529
+ }
3530
+ /** A derived constructor's body: statements lower as usual EXCEPT the
3531
+ * top-level `super(...)` statement, which becomes a direct call to the
3532
+ * base constructor over the same `this`, immediately followed by this
3533
+ * class's field initializers (JS runs them when super returns). tsc
3534
+ * guarantees a super call exists and runs before any this-use; the
3535
+ * supported form is a top-level expression statement — anywhere else
3536
+ * (conditionals, expression positions) is rejected, not misordered. */
3537
+ export function lowerDerivedCtorBody(L, info, thisLocal,
3538
+ /** Mixin forwarding-constructor mode: `super(...args)` forwards these
3539
+ * pre-declared synthetic params directly (the spread never lowers —
3540
+ * the base's ABI is this constructor's ABI). */
3541
+ forward) {
3542
+ const out = [];
3543
+ let superSeen = false;
3544
+ for (const stmt of info.ctor.body.statements) {
3545
+ const superCall = ts.isExpressionStatement(stmt) &&
3546
+ ts.isCallExpression(stmt.expression) &&
3547
+ stmt.expression.expression.kind === ts.SyntaxKind.SuperKeyword
3548
+ ? stmt.expression
3549
+ : null;
3550
+ if (!superCall) {
3551
+ out.push(...L.lowerStmts([stmt]));
3552
+ continue;
3553
+ }
3554
+ if (!L.suppressStats) {
3555
+ L.stats.statementsTotal++;
3556
+ L.bumpFileStat(locOf(stmt).file, "total");
3557
+ }
3558
+ try {
3559
+ if (superSeen)
3560
+ L.unsupported("SC1090", stmt, "multiple super() calls");
3561
+ superSeen = true;
3562
+ const base = superBaseOf(info);
3563
+ if (base.builtinEmitter && superCall.arguments.length > 0) {
3564
+ // @types/node admits super({ captureRejections }) — no lowering.
3565
+ L.unsupported("SC1090", superCall, "EventEmitter constructor options ('captureRejections')");
3566
+ }
3567
+ if (base.builtinStream) {
3568
+ // super(options?) into a runtime stream base: the stream spoke
3569
+ // parses the options and binds overridden underscore methods.
3570
+ out.push(...lowerStreamSuperCall(L, info, base, superCall.arguments, thisLocal, locOf(stmt), stmt));
3571
+ out.push(...L.fieldInitStmts(info, thisLocal));
3572
+ out.push(...paramPropInitStmts(L, info, thisLocal));
3573
+ continue;
3574
+ }
3575
+ const args = forward !== undefined
3576
+ ? forward
3577
+ : base.builtinError
3578
+ ? [L.errorMessageArg(superCall.arguments, locOf(stmt), stmt)]
3579
+ : base.builtinEmitter
3580
+ ? []
3581
+ : L.completeArgs(superCall.arguments, base.ctorParams, locOf(stmt), stmt);
3582
+ out.push(L.superCallStmt(info, thisLocal, args, locOf(stmt)));
3583
+ // super() returns → field initializers → parameter-property
3584
+ // assignments (Node's order, probed) → the rest of the body.
3585
+ out.push(...L.fieldInitStmts(info, thisLocal));
3586
+ out.push(...paramPropInitStmts(L, info, thisLocal));
3587
+ }
3588
+ catch (e) {
3589
+ if (!(e instanceof PoisonError))
3590
+ throw e;
3591
+ if (!L.suppressStats) {
3592
+ L.stats.statementsFailed++;
3593
+ L.bumpFileStat(locOf(stmt).file, "failed");
3594
+ }
3595
+ }
3596
+ }
3597
+ if (!superSeen) {
3598
+ // tsc guarantees the call exists somewhere; if it wasn't a top-level
3599
+ // statement the per-site rejection above already fired — this is the
3600
+ // constructor-level backstop so a half-initialized ctor never emits.
3601
+ L.pushDiag(unsupportedDiag("SC1090", locOf(info.ctor), "super() calls anywhere but as a top-level constructor statement"));
3602
+ }
3603
+ return out;
3604
+ }
3605
+ /** `super(args)` → direct call of the base constructor with the SAME
3606
+ * `this` (upcast; retained by the varRef read — the callee owns and
3607
+ * releases its param per the universal convention). */
3608
+ export function superCallStmt(L, info, thisLocal, args, loc) {
3609
+ const base = superBaseOf(info);
3610
+ const thisRef = {
3611
+ kind: "varRef",
3612
+ localId: thisLocal.id,
3613
+ type: { kind: "object", className: info.def.name },
3614
+ loc,
3615
+ };
3616
+ if (base.builtinError) {
3617
+ // super(message) into the runtime-provided Error constructor: stamps
3618
+ // name/message on the (already-allocated) object. Receiver + message
3619
+ // are BORROWED by the libCall — no ownership transfer, unlike the
3620
+ // call form below.
3621
+ return {
3622
+ kind: "exprStmt",
3623
+ expr: {
3624
+ kind: "libCall",
3625
+ fn: "error.ctor",
3626
+ args: [L.upcastTo(thisRef, base.def.name), ...args],
3627
+ type: VOID,
3628
+ loc,
3629
+ },
3630
+ loc,
3631
+ };
3632
+ }
3633
+ if (base.builtinEmitter) {
3634
+ // super() into the runtime-provided EventEmitter: the emitted
3635
+ // allocation already initialized the prefix (registry NULL, display
3636
+ // name stamped), so the call is a placeholder site. Receiver
3637
+ // borrowed, like error.ctor.
3638
+ return {
3639
+ kind: "exprStmt",
3640
+ expr: {
3641
+ kind: "libCall",
3642
+ fn: "emitter.ctor",
3643
+ args: [L.upcastTo(thisRef, base.def.name)],
3644
+ type: VOID,
3645
+ loc,
3646
+ },
3647
+ loc,
3648
+ };
3649
+ }
3650
+ if (base.builtinStream) {
3651
+ // The SYNTHESIZED constructor of a ctor-less stream subclass:
3652
+ // super() with default options (underscore methods still bind; a
3653
+ // construction passing options requires a declared constructor —
3654
+ // lowerNew fences that). Zero options ⇒ exactly one init stmt.
3655
+ return lowerStreamSuperCall(L, info, base, [], thisLocal, loc, info.decl ?? info.ctor)[0];
3656
+ }
3657
+ L.noteEdge(`%${base.def.name}.constructor`);
3658
+ return {
3659
+ kind: "exprStmt",
3660
+ expr: {
3661
+ kind: "call",
3662
+ callee: `%${base.def.name}.constructor`,
3663
+ args: [L.upcastTo(thisRef, base.def.name), ...args],
3664
+ type: VOID,
3665
+ loc,
3666
+ },
3667
+ loc,
3668
+ };
3669
+ }
3670
+ /** `super.method(args)`: the base chain's implementation, called
3671
+ * DIRECTLY over this method's own `this` (upcast to the declarer) —
3672
+ * super dispatch is static in JS too, never through the dynamic class. */
3673
+ export function lowerSuperMethodCall(L, call, access) {
3674
+ const cls = L.currentClass;
3675
+ if (!cls?.base) {
3676
+ // tsc rejects super outside derived-class bodies first; defensive.
3677
+ L.unsupported("SC1090", access, "'super' outside a derived class");
3678
+ }
3679
+ const found = L.findMethodOn(cls.base, access.name.text);
3680
+ if (!found) {
3681
+ // `super.m(...)` of a GENERIC method: super dispatch is static in JS
3682
+ // too, so the base chain's declaration answers unconditionally — the
3683
+ // ordinary instantiation route over this method's own `this`.
3684
+ const gfound = findGenericMethodOn(L, cls.base, access.name.text);
3685
+ if (gfound) {
3686
+ const thisL = L.resolveThis();
3687
+ if (!thisL)
3688
+ L.unsupported("SC1080", access);
3689
+ const instance = genericCallInstance(L, call, gfound.info);
3690
+ const loc = locOf(call);
3691
+ const thisRef = { kind: "varRef", localId: thisL.id, type: thisL.type, loc };
3692
+ const args = L.completeArgs(call.arguments, instance.params, loc, call);
3693
+ return {
3694
+ kind: "call",
3695
+ callee: instance.name,
3696
+ args: [L.upcastTo(thisRef, gfound.declarer.def.name), ...args],
3697
+ type: instance.returnType,
3698
+ loc,
3699
+ };
3700
+ }
3701
+ L.unsupported("SC1090", access, `'super.${access.name.text}' (no base class declares it)`);
3702
+ }
3703
+ // tsc rejects super-access of abstract members (TS2513); defensive —
3704
+ // no function exists behind an abstract declaration.
3705
+ if (found.sig.abstract === true) {
3706
+ L.unsupported("SC1090", access, `'super.${access.name.text}' of an abstract method`);
3707
+ }
3708
+ const thisLocal = L.resolveThis();
3709
+ if (!thisLocal)
3710
+ L.unsupported("SC1080", access);
3711
+ L.noteEdge(`%${found.declarer.def.name}.${access.name.text}`);
3712
+ const loc = locOf(call);
3713
+ const thisRef = { kind: "varRef", localId: thisLocal.id, type: thisLocal.type, loc };
3714
+ const args = L.completeArgs(call.arguments, found.sig.params, loc, call);
3715
+ return {
3716
+ kind: "call",
3717
+ callee: `%${found.declarer.def.name}.${access.name.text}`,
3718
+ args: [L.upcastTo(thisRef, found.declarer.def.name), ...args],
3719
+ type: found.sig.ret,
3720
+ loc,
3721
+ };
3722
+ }
3723
+ /** The `this` reference for super accessor reads/writes, with the shared
3724
+ * validity checks (derived-class body, resolvable this). */
3725
+ export function superThisRef(L, access) {
3726
+ const cls = L.currentClass;
3727
+ if (!cls?.base) {
3728
+ L.unsupported("SC1090", access, "'super' outside a derived class");
3729
+ }
3730
+ const thisLocal = L.resolveThis();
3731
+ if (!thisLocal)
3732
+ L.unsupported("SC1080", access);
3733
+ const loc = locOf(access);
3734
+ return {
3735
+ thisRef: { kind: "varRef", localId: thisLocal.id, type: thisLocal.type, loc },
3736
+ base: cls.base,
3737
+ };
3738
+ }
3739
+ /** `super.x` read: a DIRECT call of the base chain's getter over this
3740
+ * method's own `this` (upcast to the declarer) — like super.method(),
3741
+ * never through the vtable. */
3742
+ export function lowerSuperAccessorRead(L, access) {
3743
+ const { thisRef, base } = L.superThisRef(access);
3744
+ const name = access.name.text;
3745
+ const found = L.findMethodOn(base, `get:${name}`);
3746
+ if (!found) {
3747
+ L.unsupported("SC1090", access, L.findMethodOn(base, name)
3748
+ ? `bound method references through 'super' (call 'super.${name}(...)' directly)`
3749
+ : `'super.${name}' (only base-class methods and getter properties are readable through 'super')`);
3750
+ }
3751
+ // tsc rejects super-access of abstract members (TS2513); defensive.
3752
+ if (found.sig.abstract === true) {
3753
+ L.unsupported("SC1090", access, `'super.${name}' of an abstract accessor`);
3754
+ }
3755
+ L.noteEdge(`%${found.declarer.def.name}.get:${name}`);
3756
+ return {
3757
+ kind: "call",
3758
+ callee: `%${found.declarer.def.name}.get:${name}`,
3759
+ args: [L.upcastTo(thisRef, found.declarer.def.name)],
3760
+ type: found.sig.ret,
3761
+ loc: locOf(access),
3762
+ };
3763
+ }
3764
+ /** `super.x = v`: a DIRECT call of the base chain's setter (same
3765
+ * static-dispatch rule as every super member access). */
3766
+ export function lowerSuperAccessorWrite(L, access, rhs, loc) {
3767
+ const { thisRef, base } = L.superThisRef(access);
3768
+ const name = access.name.text;
3769
+ const found = L.findMethodOn(base, `set:${name}`);
3770
+ if (!found) {
3771
+ L.unsupported("SC1090", access, `assignment to 'super.${name}' (no base class declares a setter for it)`);
3772
+ }
3773
+ // tsc rejects super-access of abstract members (TS2513); defensive.
3774
+ if (found.sig.abstract === true) {
3775
+ L.unsupported("SC1090", access, `assignment to 'super.${name}' of an abstract accessor`);
3776
+ }
3777
+ L.noteEdge(`%${found.declarer.def.name}.set:${name}`);
3778
+ const value = L.lowerExprExpecting(rhs, found.sig.params[0].type);
3779
+ return {
3780
+ kind: "exprStmt",
3781
+ expr: {
3782
+ kind: "call",
3783
+ callee: `%${found.declarer.def.name}.set:${name}`,
3784
+ args: [L.upcastTo(thisRef, found.declarer.def.name), value],
3785
+ type: VOID,
3786
+ loc,
3787
+ },
3788
+ loc,
3789
+ };
3790
+ }
3791
+ /** True when `info`'s EFFECTIVE constructor — its own, or the one
3792
+ * inherited through ctor-less bases — is a builtin error class's. Such
3793
+ * classes construct with the error message rule, and their synthesized
3794
+ * constructors forward one plain string to error.ctor. */
3795
+ export function inheritsBuiltinErrorCtor(L, info) {
3796
+ for (let c = info; c; c = c.base) {
3797
+ if (c.builtinError)
3798
+ return true;
3799
+ if (c.ctor)
3800
+ return false;
3801
+ }
3802
+ return false;
3803
+ }
3804
+ /** The EventEmitter twin: a ctor-less chain into the emitter base
3805
+ * inherits `new C()` — zero arguments (the options bag fences). */
3806
+ export function inheritsBuiltinEmitterCtor(L, info) {
3807
+ for (let c = info; c; c = c.base) {
3808
+ if (c.builtinStream)
3809
+ return false; // the stream story owns the chain
3810
+ if (c.builtinEmitter)
3811
+ return true;
3812
+ if (c.ctor)
3813
+ return false;
3814
+ }
3815
+ return false;
3816
+ }
3817
+ /** The stream twin: a ctor-less chain into a runtime stream base
3818
+ * inherits `new C()` — zero arguments (the synthesized constructor runs
3819
+ * super() with default options; passing options through an inherited
3820
+ * constructor would need the literal at the new-site to plumb, so it
3821
+ * asks for a declared constructor instead). */
3822
+ export function inheritsBuiltinStreamCtor(L, info) {
3823
+ for (let c = info; c; c = c.base) {
3824
+ if (c.builtinStream)
3825
+ return true;
3826
+ if (c.ctor)
3827
+ return false;
3828
+ }
3829
+ return false;
3830
+ }
3831
+ /** `new C(args)` for a class declared in the program (imports resolve
3832
+ * through aliases, so cross-module classes construct too). */
3833
+ /** The single message argument of a builtin Error construction or
3834
+ * super() call: "" when omitted or explicitly undefined (Node's message
3835
+ * property default), the string otherwise. The lib signature's second
3836
+ * parameter (options/cause) has no lowering. */
3837
+ export function errorMessageArg(L, args, loc, blame) {
3838
+ if (args.length > 1) {
3839
+ L.unsupported("SC1090", args[1] ?? blame, "Error constructor options ('cause')");
3840
+ }
3841
+ if (args.length === 0)
3842
+ return { kind: "strLit", value: "", type: STRING, loc };
3843
+ const value = L.lowerExpr(args[0]);
3844
+ if (value.type.kind === "string")
3845
+ return value;
3846
+ if (value.kind === "unitLit" && value.unit === "undefined") {
3847
+ return { kind: "strLit", value: "", type: STRING, loc };
3848
+ }
3849
+ L.unsupported("SC1090", args[0], `Error messages of type '${L.fmt(value.type)}' (the message must be a string)`);
3850
+ }
3851
+ /** `new C(...)` of a registered PROGRAM class — the shared tail of the
3852
+ * identifier and namespace-qualified construction forms. */
3853
+ /** `new Box(1)` / `new Box<string>("s")` — construction of a GENERIC
3854
+ * class resolves to the INSTANTIATION the expression's checker type names
3855
+ * (inference and explicit type arguments both land there; defaults apply).
3856
+ * The identity function for ordinary classes. */
3857
+ function genericNewTarget(L, expr, info) {
3858
+ if (!info.generic)
3859
+ return info;
3860
+ const t = L.typeOf(expr);
3861
+ const mapped = L.mapTypeOf(t);
3862
+ const instInfo = mapped?.kind === "object" ? L.classes.get(mapped.className) : undefined;
3863
+ // Unmappable type arguments (or a poisoned instantiation): the site
3864
+ // reports the type it cannot compile — the instantiation's own
3865
+ // diagnostic (context-tagged) already told the deeper story.
3866
+ if (!instInfo || instInfo.generic)
3867
+ L.badType(expr, t);
3868
+ return instInfo;
3869
+ }
3870
+ /** A class whose decoration provably throws has no reachable VALUE form:
3871
+ * the binding never initializes (the %init ReferenceError unwinds first),
3872
+ * so `new`, the class as a value, and `extends` all fence — reaching one
3873
+ * in compiled code would require executing past the throw. */
3874
+ export function fenceDecorationThrows(L, info, blame) {
3875
+ if (info.decorationThrows === undefined)
3876
+ return;
3877
+ L.unsupported("SC1090", blame, `using the class '${info.def.jsName || info.def.name}' whose decoration provably throws ('${info.decorationThrows.name}' is an ambient name nothing defines — the class statement crashes before the binding exists)`);
3878
+ }
3879
+ function lowerProgramClassNew(L, expr, info0, loc) {
3880
+ const info = genericNewTarget(L, expr, info0);
3881
+ fenceDecorationThrows(L, info, expr);
3882
+ L.noteEdge(`%${info.def.name}.constructor`);
3883
+ // A ctor-less chain into an EventEmitter base inherits `new C()` —
3884
+ // zero arguments (the options bag fences, like the super() form).
3885
+ // Stream subclasses come first: their chain roots at the emitter
3886
+ // too, but the message should name the stream story.
3887
+ if (inheritsBuiltinStreamCtor(L, info) && (expr.arguments ?? []).length > 0) {
3888
+ L.noLowering(`new ${info.def.name.replace(/^%/, "")} with arguments through an inherited stream constructor`, expr.arguments[0], "declare a constructor that passes an inline options object to super(...)");
3889
+ }
3890
+ if (L.inheritsBuiltinEmitterCtor(info) && (expr.arguments ?? []).length > 0) {
3891
+ L.unsupported("SC1090", expr.arguments[0], "EventEmitter constructor options ('captureRejections')");
3892
+ }
3893
+ // A ctor-less chain into a builtin error base inherits `new
3894
+ // C(message?)` — completed by the error rule (one plain string),
3895
+ // not the general ABI completion.
3896
+ const args = L.inheritsBuiltinErrorCtor(info)
3897
+ ? [L.errorMessageArg(expr.arguments ?? [], loc, expr)]
3898
+ : L.completeArgs(expr.arguments ?? [], info.ctorParams, loc, expr);
3899
+ return {
3900
+ kind: "new",
3901
+ className: info.def.name,
3902
+ args,
3903
+ type: { kind: "object", className: info.def.name },
3904
+ loc,
3905
+ };
3906
+ }
3907
+ export function lowerNew(L, expr) {
3908
+ const loc = locOf(expr);
3909
+ // `new X(...)` where X is a package-declared class, in a static build:
3910
+ // the per-package requires-dynamic diagnostic (the constructor runs in
3911
+ // the embedded engine). Under --dynamic, X is jsval-typed and lowers
3912
+ // to the construct op below.
3913
+ if (!L.dynamic) {
3914
+ const pkg = ts.isIdentifier(expr.expression)
3915
+ ? L.npmPackageOfSymbol(L.resolveValueSymbol(expr.expression) ?? undefined)
3916
+ : null;
3917
+ if (pkg) {
3918
+ L.pushDiag(requiresDynamicPackageDiag(pkg, loc));
3919
+ throw new PoisonError();
3920
+ }
3921
+ }
3922
+ // Island construction: a jsval-typed callee (a package-declared class,
3923
+ // or any 'any'-typed constructor value) runs JS_CallConstructor —
3924
+ // `new Command()` is the npm entry point. Arguments marshal in; the
3925
+ // instance stays an island handle.
3926
+ if (L.isIslandExpr(expr.expression)) {
3927
+ const callee = L.lowerExpr(expr.expression);
3928
+ const args = (expr.arguments ?? []).map((a) => L.jsvalIn(L.lowerExpr(a), a));
3929
+ return { kind: "jsOp", op: "construct", args: [callee, ...args], type: JSVAL, loc };
3930
+ }
3931
+ // `new events.EventEmitter()` — the namespace-member (and CJS
3932
+ // `require('events').EventEmitter`) construction form: the property's
3933
+ // symbol resolves to the same ambient class as the named import.
3934
+ if (ts.isPropertyAccessExpression(expr.expression) && ts.isIdentifier(expr.expression.name)) {
3935
+ const memberSym = L.checker.getSymbolAtLocation(expr.expression.name);
3936
+ const resolved = memberSym && memberSym.flags & ts.SymbolFlags.Alias
3937
+ ? L.checker.getAliasedSymbol(memberSym)
3938
+ : memberSym;
3939
+ const emitterInfo = L.builtinEmitterInfoOf(resolved);
3940
+ if (emitterInfo) {
3941
+ if ((expr.arguments ?? []).length > 0) {
3942
+ L.unsupported("SC1090", expr.arguments[0], "EventEmitter constructor options ('captureRejections')");
3943
+ }
3944
+ return {
3945
+ kind: "libCall",
3946
+ fn: "emitter.new",
3947
+ args: [],
3948
+ type: { kind: "object", className: RUNTIME_EMITTER_CLASS },
3949
+ loc,
3950
+ };
3951
+ }
3952
+ // `new stream.Readable({...})` — the namespace-member (and CJS
3953
+ // `require('stream').Readable`) construction form.
3954
+ const streamInfoNs = builtinStreamInfoOf(L, resolved);
3955
+ if (streamInfoNs)
3956
+ return lowerStreamNew(L, expr, streamInfoNs);
3957
+ // `new N.C(...)` / `new a.Point(...)` — construction through a
3958
+ // USER namespace qualifier (import= alias chains included): the
3959
+ // member resolves to the registered program class, guarded by the
3960
+ // namespace source-order fences (lower-namespaces.ts).
3961
+ if (!expr.expression.questionDotToken && nsMemberIdentOf(L, expr.expression)) {
3962
+ if (memberSym)
3963
+ fenceEarlyNsMemberRef(L, expr.expression, memberSym);
3964
+ // resolveValueSymbol (not the bare alias chase): the reference
3965
+ // must flush deferred collection diagnostics like any other.
3966
+ const classSym = L.resolveValueSymbol(expr.expression.name);
3967
+ const info = classSym ? L.classBySymbol.get(classSym) : undefined;
3968
+ // Qualified spellings of a rebindable decorated class (an import=
3969
+ // alias chain landing on it) cannot construct the declaration
3970
+ // directly — the decoration result decides. The bare-name path
3971
+ // routes through the class VALUE; the qualified one fences.
3972
+ if (info?.classDecorators?.valueGlobalId !== undefined) {
3973
+ L.unsupported("SC1090", expr, "constructing a decorated class through a qualified name (a replacing decorator rebinds the class name — construct through the bare name)");
3974
+ }
3975
+ if (info)
3976
+ return lowerProgramClassNew(L, expr, info, loc);
3977
+ L.unsupported("SC1090", expr, `constructing '${expr.expression.name.text}' (a namespace member with no class lowering)`);
3978
+ }
3979
+ // `new B.C()` where B is an AMBIENT namespace (fundule merges
3980
+ // included): Node evaluates the callee first and throws
3981
+ // ReferenceError before any argument runs — undefRead reproduces it
3982
+ // exactly.
3983
+ if (!expr.expression.questionDotToken) {
3984
+ const ambientRoot = ambientNsRootOf(L, expr.expression.expression);
3985
+ if (ambientRoot !== null) {
3986
+ const t = ambientUndefReadType(L, expr);
3987
+ if (t)
3988
+ return nsUndefRead(L, ambientRoot.text, expr, t);
3989
+ }
3990
+ }
3991
+ // Construction through a CJS export member tsgo types `any`
3992
+ // (expando members — `new module.exports.Sub()` / `new
3993
+ // exports.Sub()` in-file, `new C.Sub()` through the require
3994
+ // binding): the member IS its pre-registered export global —
3995
+ // construction dispatches through the class VALUE, the classval
3996
+ // path's newValue with the global as the callee. Resolution is the
3997
+ // member-export symbol's; the class collects on demand (a body
3998
+ // lowering ahead of the assignment statement).
3999
+ if (!expr.expression.questionDotToken &&
4000
+ ((isCjsJsFile(expr.getSourceFile()) &&
4001
+ (isModuleExportsAccess(expr.expression.expression) ||
4002
+ (ts.isIdentifier(expr.expression.expression) &&
4003
+ expr.expression.expression.text === "exports" &&
4004
+ !L.resolveLocal(expr.expression.expression) &&
4005
+ !L.globalOf(expr.expression.expression)))) ||
4006
+ L.cjsLocalModuleBindingOf(expr.expression.expression))) {
4007
+ // Candidate symbols for the export global: the member symbol as
4008
+ // spelled, its alias-chased resolution (resolveValueSymbol carries
4009
+ // the dep-module fallback tsgo needs at member-use sites), and the
4010
+ // in-file module-export symbol.
4011
+ const candidates = [
4012
+ memberSym,
4013
+ resolved,
4014
+ L.resolveValueSymbol(expr.expression.name) ?? undefined,
4015
+ L.cjsModuleExportSymbol(expr.getSourceFile(), expr.expression.name.text),
4016
+ ];
4017
+ const exportSym = candidates.find((s) => s !== undefined);
4018
+ const g = candidates
4019
+ .map((s) => (s ? L.globalsBySymbol.get(s) : undefined))
4020
+ .find((x) => x !== undefined);
4021
+ if (g && g.type.kind === "classval") {
4022
+ const info = L.classes.get(g.type.className) ??
4023
+ propertyAssignedClassInfoOf(L, exportSym) ??
4024
+ L.classes.get(g.type.className);
4025
+ if (info && !info.generic) {
4026
+ L.noteEdge(`%${info.def.name}.constructor`);
4027
+ const below = (c) => {
4028
+ for (const s of c.subclasses) {
4029
+ L.noteEdge(`%${s.def.name}.constructor`);
4030
+ below(s);
4031
+ }
4032
+ };
4033
+ below(info);
4034
+ const callee = { kind: "varRef", localId: g.id, type: g.type, loc };
4035
+ const args = L.completeArgs(expr.arguments ?? [], info.ctorParams, loc, expr);
4036
+ return {
4037
+ kind: "newValue",
4038
+ callee,
4039
+ args,
4040
+ type: { kind: "object", className: info.def.name },
4041
+ loc,
4042
+ };
4043
+ }
4044
+ }
4045
+ }
4046
+ }
4047
+ // `new http.Server([options][, handler])` — the constructor spelling
4048
+ // of http.createServer (Node's Server class IS the factory's
4049
+ // product); routed to lower-server ahead of the stdlib-ctor fences.
4050
+ {
4051
+ const httpServer = lowerHttpServerNew(L, expr);
4052
+ if (httpServer)
4053
+ return httpServer;
4054
+ }
4055
+ if (ts.isIdentifier(expr.expression)) {
4056
+ // `import C = N.C; new C()` — the alias's own source-order guards
4057
+ // (a no-op for every non-import= binding).
4058
+ fenceEarlyAliasUse(L, expr.expression, expr);
4059
+ const symbol = L.resolveValueSymbol(expr.expression);
4060
+ // `new Error(msg?)` (and TypeError/RangeError/SyntaxError): the
4061
+ // runtime-provided classes construct through one libCall — the result
4062
+ // TYPE names which builtin, and the message completes to "" exactly
4063
+ // like Node's message property default.
4064
+ const errInfo = L.builtinErrorInfoOf(symbol);
4065
+ // `new DOMException(message?, nameOrOptions?)`: both arguments cross
4066
+ // as DOM values (absent → the DOM undefined), and the runtime owns
4067
+ // WebIDL's resolution — ToString of the message ("" for undefined),
4068
+ // name from a string / an options object's `name` member (with the
4069
+ // `cause` own-property record) / "Error" for absent, and the legacy
4070
+ // numeric code from the name table.
4071
+ if (errInfo && errInfo.def.name === "%DOMException") {
4072
+ const args = expr.arguments ?? [];
4073
+ if (args.length > 2) {
4074
+ L.noLowering(`new DOMException with ${args.length} arguments`, expr);
4075
+ }
4076
+ const toDynArg = (a) => {
4077
+ if (!a)
4078
+ return dynUndefinedExpr(loc);
4079
+ const v = L.lowerExpr(a);
4080
+ if (v.type.kind === "dyn")
4081
+ return v;
4082
+ if (v.kind === "unitLit" || (v.type.kind !== "jsval" && L.dynConvertible(v.type))) {
4083
+ return { kind: "dynFrom", value: v, type: DYN, loc };
4084
+ }
4085
+ L.noLowering(`new DOMException with a '${L.fmt(v.type)}' argument`, a, "message strings and string/options-object names lower (Node ToStrings other values — convert explicitly)");
4086
+ };
4087
+ const msgArg = toDynArg(args[0]);
4088
+ const nameArg = toDynArg(args[1]);
4089
+ return {
4090
+ kind: "libCall",
4091
+ fn: "error.newDom",
4092
+ args: [msgArg, nameArg],
4093
+ type: { kind: "object", className: "%DOMException" },
4094
+ loc,
4095
+ };
4096
+ }
4097
+ if (errInfo) {
4098
+ const msg = L.errorMessageArg(expr.arguments ?? [], loc, expr);
4099
+ return {
4100
+ kind: "libCall",
4101
+ fn: "error.new",
4102
+ args: [msg],
4103
+ type: { kind: "object", className: errInfo.def.name },
4104
+ loc,
4105
+ };
4106
+ }
4107
+ // `new EventEmitter()`: the runtime-provided emitter constructs
4108
+ // through one libCall. Zero arguments — the options bag
4109
+ // (@types/node's captureRejections) has no lowering.
4110
+ const emitterInfo = L.builtinEmitterInfoOf(symbol);
4111
+ if (emitterInfo) {
4112
+ if ((expr.arguments ?? []).length > 0) {
4113
+ L.unsupported("SC1090", expr.arguments[0], "EventEmitter constructor options ('captureRejections')");
4114
+ }
4115
+ return {
4116
+ kind: "libCall",
4117
+ fn: "emitter.new",
4118
+ args: [],
4119
+ type: { kind: "object", className: RUNTIME_EMITTER_CLASS },
4120
+ loc,
4121
+ };
4122
+ }
4123
+ // `new Readable({...})` and the other stream classes: the options
4124
+ // object parses structurally in the stream spoke.
4125
+ const streamInfo = builtinStreamInfoOf(L, symbol);
4126
+ if (streamInfo)
4127
+ return lowerStreamNew(L, expr, streamInfo);
4128
+ // `new URL(input)`: the WHATWG URL class (stdlib/@types provenance —
4129
+ // a user's own `class URL` resolves through classBySymbol below).
4130
+ // One string argument; invalid input throws a catchable TypeError
4131
+ // ("Invalid URL"), like Node. The lib's base-argument form
4132
+ // typechecks and is fenced here.
4133
+ // `new RegExp(pattern, flags?)`: runtime construction over the same
4134
+ // libregexp engine the literals ride. The pattern compiles EAGERLY,
4135
+ // so bad input throws Node's catchable SyntaxError at construction.
4136
+ // String arguments only (Node also accepts a RegExp to copy — that
4137
+ // form keeps the fence).
4138
+ if (symbol && symbol.name === "RegExp" && L.isStdlibSymbol(symbol)) {
4139
+ const args = expr.arguments ?? [];
4140
+ if (args.length > 2) {
4141
+ L.noLowering(`new RegExp with ${args.length} arguments`, expr);
4142
+ }
4143
+ const strArg = (a, what) => {
4144
+ if (!a)
4145
+ return { kind: "strLit", value: "", type: STRING, loc };
4146
+ const v = L.lowerExpr(a);
4147
+ if (v.type.kind !== "string") {
4148
+ L.noLowering(`new RegExp with a '${L.fmt(v.type)}' ${what}`, a, "string arguments are the lowered form (a RegExp copy or ToString coercion has no lowering)");
4149
+ }
4150
+ return v;
4151
+ };
4152
+ const pattern = strArg(args[0], "pattern");
4153
+ const flags = strArg(args[1], "flags argument");
4154
+ return { kind: "libCall", fn: "regex.new", args: [pattern, flags], type: { kind: "regex" }, loc };
4155
+ }
4156
+ if (symbol && symbol.name === "URL" && L.isStdlibSymbol(symbol)) {
4157
+ const args = expr.arguments ?? [];
4158
+ if (args.length !== 1) {
4159
+ L.noLowering(`new URL with ${args.length} argument${args.length === 1 ? "" : "s"}`, expr, "one absolute-URL string is the supported form (resolve relative inputs against a base yourself)", symbol);
4160
+ }
4161
+ const input = L.lowerExprExpecting(args[0], STRING);
4162
+ return { kind: "libCall", fn: "url.new", args: [input], type: URL_T, loc };
4163
+ }
4164
+ // `new URLSearchParams(init?)`: the WHATWG list (stdlib provenance —
4165
+ // see lowerSearchParamsNew for the lowered init shapes).
4166
+ if (symbol && symbol.name === "URLSearchParams" && L.isStdlibSymbol(symbol)) {
4167
+ return lowerSearchParamsNew(L, expr, loc);
4168
+ }
4169
+ // `new Date(...)` NOT consumed by the composed toISOString lowering
4170
+ // (lowerDateCall claims that form before the receiver lowers): Date
4171
+ // values have no representation — point at what does compile.
4172
+ if (symbol && symbol.name === "Date" && L.isStdlibSymbol(symbol)) {
4173
+ L.noLowering("new Date", expr, "Date values have no representation — Date.now() and the composed new Date(ms?).toISOString() form compile", symbol);
4174
+ }
4175
+ // `new StringDecoder(encoding?)` (node:string_decoder): the decoder
4176
+ // is a two-field record — the CANONICAL encoding name (aliases fold
4177
+ // at compile time, exactly what `.encoding` answers in Node) and
4178
+ // the packed-f64 pending state starting at 0 (nothing buffered).
4179
+ // The encoding must be a literal (Node's alias set); omitted means
4180
+ // utf8, Node's default.
4181
+ if (symbol && symbol.name === "StringDecoder" && L.isStdlibSymbol(symbol)) {
4182
+ const args = expr.arguments ?? [];
4183
+ if (args.length > 1) {
4184
+ L.noLowering("new StringDecoder with 2 arguments", expr, undefined, symbol);
4185
+ }
4186
+ const encName = args.length === 1 ? bufEncoding(L, "new StringDecoder", args[0]) : "utf8";
4187
+ const decT = L.mapTypeOf(L.typeOf(expr));
4188
+ if (decT?.kind !== "record")
4189
+ L.badType(expr, L.typeOf(expr));
4190
+ return {
4191
+ kind: "recordLit",
4192
+ fields: [
4193
+ { name: "%enc", value: { kind: "strLit", value: encName, type: STRING, loc } },
4194
+ { name: "%pending", value: { kind: "numLit", value: 0, type: F64, loc } },
4195
+ ],
4196
+ type: decT,
4197
+ loc,
4198
+ };
4199
+ }
4200
+ // Encoder objects likewise exist only inside the composed forms
4201
+ // (lowerTextCodecCall claims those before the receiver lowers).
4202
+ if (symbol && (symbol.name === "TextDecoder" || symbol.name === "TextEncoder") && L.isStdlibSymbol(symbol)) {
4203
+ L.noLowering(`new ${symbol.name}`, expr, `${symbol.name} values have no representation — the composed form compiles: ` +
4204
+ (symbol.name === "TextDecoder"
4205
+ ? "new TextDecoder().decode(bytes)"
4206
+ : "new TextEncoder().encode(s)"), symbol);
4207
+ }
4208
+ // `new Uint8Array(...)` / `new Uint32Array(...)` / `new
4209
+ // Float32Array(...)`: the typed-array constructors with a runtime
4210
+ // representation (stdlib provenance — see lowerBytesNew for the
4211
+ // lowered argument shapes; a user's own class with one of the names
4212
+ // resolves through classBySymbol below).
4213
+ const bytesNew = L.lowerBytesNew(expr, symbol);
4214
+ if (bytesNew)
4215
+ return bytesNew;
4216
+ const info = (symbol ? L.classBySymbol.get(symbol) : undefined) ??
4217
+ // `const C = require('./x'); new C()` over `module.exports =
4218
+ // class {…}`: the binding aliases the expression's own symbol —
4219
+ // the declaration story, collected on demand.
4220
+ propertyAssignedClassInfoOf(L, symbol) ??
4221
+ undefined;
4222
+ // A rebindable decorated name constructs through its VALUE (the
4223
+ // classval-typed path below — newValue through the decoration
4224
+ // result's construct thunk), never the declaration directly.
4225
+ if (info && info.classDecorators?.valueGlobalId === undefined) {
4226
+ return lowerProgramClassNew(L, expr, info, loc);
4227
+ }
4228
+ // `new Map<K, V>()`: the lib Map constructor. The SEEDED forms: an
4229
+ // entries ARRAY LITERAL of PAIR LITERALS at the construction site
4230
+ // (`new Map([[k, v], ...])`) — each pair's key/value lower as
4231
+ // ordinary K/V-typed expressions and the backend set()s them in
4232
+ // order, so the tuple array never exists as a value — and a
4233
+ // `[K, V][]`-typed tuple-array VALUE (lowerMapSeedArrayNew: a
4234
+ // construct-and-set loop, pairs in array order, duplicates
4235
+ // overwrite). Other seeds — another Map, general iterables — keep
4236
+ // the fence: never silently an empty map. Unsupported key/value
4237
+ // types get their half named specifically instead of the generic
4238
+ // SC2001 (which fires for Map-typed slots elsewhere).
4239
+ // `new Array<T>()` and the ELEMENTS forms (`new Array('hi', 'bye')`,
4240
+ // any argument list that is not one lone number) ARE array literals
4241
+ // — the spec's ArrayCreate + element writes. The one-NUMBER form
4242
+ // allocates a HOLE array (reads answer undefined where the element
4243
+ // type says T) — no honest lowering exists unless the element type
4244
+ // admits undefined, so it fences by name.
4245
+ // `new Object()` — the spec's OrdinaryObjectCreate, exactly what the
4246
+ // `{}` literal builds (fresh reference identity, no own properties) —
4247
+ // lowers as the empty record. The ARGUMENT form is Object(x): it
4248
+ // returns its argument for objects and BOXES primitives — the wrapper
4249
+ // story with no lowering — so it keeps the constructor fence.
4250
+ if (symbol?.name === "Object" &&
4251
+ L.isStdlibSymbol(symbol) &&
4252
+ (expr.arguments ?? []).length === 0) {
4253
+ return {
4254
+ kind: "recordLit",
4255
+ fields: [],
4256
+ type: { kind: "record", shapeId: L.shapes.intern([]) },
4257
+ loc,
4258
+ };
4259
+ }
4260
+ if (symbol?.name === "Array" && L.isStdlibSymbol(symbol)) {
4261
+ const args = expr.arguments ?? [];
4262
+ if (args.some(ts.isSpreadElement)) {
4263
+ L.noLowering("new Array with spread arguments", expr, "write the array literal: [...xs]");
4264
+ }
4265
+ if (args.length === 1 && L.mapTypeOf(L.typeOf(args[0]))?.kind === "f64") {
4266
+ L.noLowering("new Array(count)", expr, "the one-number form allocates HOLES (reads answer undefined, which the element type cannot carry) — build and push, or use the elements form: new Array(a, b)");
4267
+ }
4268
+ let t = L.mapTypeOf(L.typeOf(expr));
4269
+ // JS's `new Array()` types any[]; the contextual type carries the
4270
+ // annotation when one exists (the new Map() stance).
4271
+ if (t?.kind !== "array") {
4272
+ const ctx = L.checker.getContextualType(expr);
4273
+ const ctxMapped = ctx ? L.mapTypeOf(ctx) : null;
4274
+ if (ctxMapped?.kind === "array")
4275
+ t = ctxMapped;
4276
+ }
4277
+ if (t?.kind !== "array")
4278
+ L.badType(expr, L.typeOf(expr));
4279
+ const elems = args.map((a) => L.lowerExprExpecting(a, t.elem));
4280
+ return { kind: "arrayLit", elems, type: t, loc };
4281
+ }
4282
+ if (symbol?.name === "Map" && L.isStdlibSymbol(symbol)) {
4283
+ const seedArg = (expr.arguments?.length ?? 0) === 1 ? expr.arguments[0] : null;
4284
+ const isPairLit = (el) => ts.isArrayLiteralExpression(el) && el.elements.length === 2 &&
4285
+ !el.elements.some(ts.isSpreadElement);
4286
+ const entriesLit = seedArg && ts.isArrayLiteralExpression(seedArg) && seedArg.elements.every(isPairLit)
4287
+ ? seedArg.elements.filter(isPairLit)
4288
+ : null;
4289
+ let tsType = L.typeOf(expr);
4290
+ let mapped = L.mapTypeOf(tsType);
4291
+ // JavaScript's `new Map()` has no type-argument syntax: the no-arg
4292
+ // constructor overload pins Map<any, any> whatever the JSDoc says
4293
+ // (`@type` on the declaration types the VARIABLE, not this
4294
+ // expression). The CONTEXTUAL type carries the annotation — adopt
4295
+ // it when it is a supported map. TS type arguments keep winning:
4296
+ // their expression type already maps.
4297
+ if (mapped?.kind !== "map") {
4298
+ const ctx = L.checker.getContextualType(expr);
4299
+ const ctxMapped = ctx ? L.mapTypeOf(ctx) : null;
4300
+ if (ctx && ctxMapped?.kind === "map") {
4301
+ tsType = ctx;
4302
+ mapped = ctxMapped;
4303
+ }
4304
+ }
4305
+ if (seedArg && !entriesLit && mapped?.kind === "map") {
4306
+ const seeded = lowerMapSeedArrayNew(L, seedArg, mapped);
4307
+ if (seeded)
4308
+ return seeded;
4309
+ }
4310
+ if ((expr.arguments?.length ?? 0) > 0 && !entriesLit) {
4311
+ L.noLowering("new Map(entries)", expr, "supported seeds: an array literal of [key, value] pair literals, or a " +
4312
+ "[K, V][]-typed tuple-array value — construct the Map empty and set() " +
4313
+ "each entry otherwise");
4314
+ }
4315
+ if (mapped?.kind === "map") {
4316
+ if (!entriesLit)
4317
+ return { kind: "mapNew", type: mapped, loc };
4318
+ const seed = entriesLit.map((pair) => ({
4319
+ key: L.lowerExprExpecting(pair.elements[0], mapped.key),
4320
+ value: L.lowerExprExpecting(pair.elements[1], mapped.value),
4321
+ }));
4322
+ return { kind: "mapNew", seed, type: mapped, loc };
4323
+ }
4324
+ const targs = L.checker.getTypeArguments(tsType);
4325
+ const keyIr = targs[0] ? L.mapTypeOf(targs[0]) : null;
4326
+ if (targs[0] && (!keyIr || !isSupportedMapKey(keyIr))) {
4327
+ L.unsupported("SC1090", expr, `Map keys of type '${L.checker.typeToString(targs[0])}' ` +
4328
+ `(Map keys must be string or number)`);
4329
+ }
4330
+ if (targs[1]) {
4331
+ L.unsupported("SC1090", expr, `Map values of type '${L.checker.typeToString(targs[1])}' ` +
4332
+ `(Map values must be number, string, boolean, records, class instances, ` +
4333
+ `arrays, promises, or unions of those — not functions, Maps, 'unknown', or 'any')`);
4334
+ }
4335
+ L.badType(expr, tsType);
4336
+ }
4337
+ // `new Set<T>()`: Map's sibling. The SEEDED form lowers for any
4338
+ // T[]-typed argument — literal or variable, T already a legal
4339
+ // element type — as construct + bulk add (duplicates collapse,
4340
+ // insertion order preserved, exactly JS). Non-array seeds (another
4341
+ // Set, general iterables) keep the fence. Unsupported element types
4342
+ // are named specifically.
4343
+ // `new WeakMap()` / `new WeakSet()` in JAVASCRIPT sources: no weak
4344
+ // container exists in the value model, but harness code constructs
4345
+ // one unconditionally and touches it only on paths tests don't
4346
+ // reach — the value lowers as an opaque DOM object (identity only;
4347
+ // every reached METHOD use meets its own per-site fence → runtime
4348
+ // fence). TypeScript keeps the compile fence.
4349
+ if ((symbol?.name === "WeakMap" || symbol?.name === "WeakSet") &&
4350
+ L.isStdlibSymbol(symbol) &&
4351
+ isJsSourceFile(expr.getSourceFile()) &&
4352
+ (expr.arguments?.length ?? 0) === 0) {
4353
+ return { kind: "dynObjLit", type: DYN, loc };
4354
+ }
4355
+ if (symbol?.name === "Set" && L.isStdlibSymbol(symbol)) {
4356
+ const tsType = L.typeOf(expr);
4357
+ const mapped = L.mapTypeOf(tsType);
4358
+ if (mapped?.kind === "set" && (expr.arguments?.length ?? 0) === 1) {
4359
+ const argNode = expr.arguments[0];
4360
+ // An array LITERAL seed builds element-wise (its contextual type
4361
+ // is the lib constructor's `readonly T[] | Iterable<T> | null`
4362
+ // union — unmappable, so the generic literal path can't type it);
4363
+ // an array-typed VALUE seed lowers as itself.
4364
+ if (ts.isArrayLiteralExpression(argNode) && !argNode.elements.some(ts.isSpreadElement)) {
4365
+ const elems = argNode.elements.map((el) => L.lowerExprExpecting(el, mapped.elem));
4366
+ const seed = { kind: "arrayLit", elems, type: arrayOf(mapped.elem), loc };
4367
+ return { kind: "setNew", seed, type: mapped, loc };
4368
+ }
4369
+ if (!ts.isSpreadElement(argNode)) {
4370
+ const argIr = L.mapTypeOf(L.typeOf(argNode));
4371
+ if (argIr?.kind === "array" && typeEquals(argIr.elem, mapped.elem)) {
4372
+ return { kind: "setNew", seed: L.lowerExpr(argNode), type: mapped, loc };
4373
+ }
4374
+ }
4375
+ }
4376
+ // JavaScript's identity-Set idiom: `new Set([setTimeout, atob,
4377
+ // ...])` — the element TYPE (a union of stdlib signatures) has no
4378
+ // mapping, but the element VALUES all lower to identity tokens
4379
+ // (interned strings — see the JS token stance in lower-exprs), so
4380
+ // the honest construction is a Set of those scalars.
4381
+ if (!mapped &&
4382
+ isJsSourceFile(expr.getSourceFile()) &&
4383
+ (expr.arguments?.length ?? 0) === 1 &&
4384
+ ts.isArrayLiteralExpression(expr.arguments[0]) &&
4385
+ !expr.arguments[0].elements.some(ts.isSpreadElement)) {
4386
+ const lit = expr.arguments[0];
4387
+ const elems = lit.elements.map((el) => L.lowerExpr(el));
4388
+ const first = elems[0];
4389
+ if (first !== undefined &&
4390
+ (first.type.kind === "string" || first.type.kind === "f64") &&
4391
+ elems.every((e) => e.type.kind === first.type.kind)) {
4392
+ const setT = { kind: "set", elem: first.type };
4393
+ const seed = { kind: "arrayLit", elems, type: arrayOf(first.type), loc };
4394
+ return { kind: "setNew", seed, type: setT, loc };
4395
+ }
4396
+ }
4397
+ if ((expr.arguments?.length ?? 0) > 0) {
4398
+ L.noLowering("new Set(values)", expr, "construct the Set empty and add() each value — only an array of " +
4399
+ "already-legal elements (string or number) seeds a Set");
4400
+ }
4401
+ if (mapped?.kind === "set")
4402
+ return { kind: "setNew", type: mapped, loc };
4403
+ const targs = L.checker.getTypeArguments(tsType);
4404
+ if (targs[0]) {
4405
+ L.unsupported("SC1090", expr, `Set elements of type '${L.checker.typeToString(targs[0])}' ` +
4406
+ `(Set elements must be string or number — Map's key kinds — or a server handle, which stores under reference identity)`);
4407
+ }
4408
+ L.badType(expr, tsType);
4409
+ }
4410
+ // `new AsyncLocalStorage()` (node:async_hooks): a fresh store id —
4411
+ // an f64 handle (types.ts), the Channel story. Construction options
4412
+ // ({ defaultValue, name }) have no lowering yet.
4413
+ if (symbol?.name === "AsyncLocalStorage" && L.isStdlibSymbol(symbol)) {
4414
+ if ((expr.arguments?.length ?? 0) > 0) {
4415
+ L.noLowering("new AsyncLocalStorage(options)", expr, "the zero-argument constructor is the supported form (defaultValue/name options have no lowering yet)");
4416
+ }
4417
+ return { kind: "libCall", fn: "als.new", args: [], type: F64, loc };
4418
+ }
4419
+ // `new Promise<T>((resolve) => ...)`: the ambient Promise constructor.
4420
+ if (symbol?.name === "Promise" && L.isStdlibSymbol(symbol)) {
4421
+ const type = L.irTypeOf(expr);
4422
+ if (type.kind !== "promise")
4423
+ L.badType(expr, L.typeOf(expr));
4424
+ const args = expr.arguments ?? [];
4425
+ if (args.length !== 1) {
4426
+ L.unsupported("SC1090", expr, "Promise construction without an executor");
4427
+ }
4428
+ // `new Promise(setImmediate)` (the Node-suite early-exit shape):
4429
+ // the executor IS the stdlib setImmediate, so resolve rides the
4430
+ // immediate queue — a dedicated runtime constructor arms an
4431
+ // immediate that fulfills with the undefined DOM value.
4432
+ {
4433
+ const a0 = args[0];
4434
+ if (ts.isIdentifier(a0) && a0.text === "setImmediate") {
4435
+ const sym = L.checker.getSymbolAtLocation(a0);
4436
+ const decls = sym ? L.checker.declarationsOf(sym) : [];
4437
+ if (decls.length > 0 && decls.every((d) => L.isStdlibFile(d.getSourceFile()))) {
4438
+ // The settled value is the undefined DOM value — the result
4439
+ // is promise<dyn> whatever T the checker inferred for the
4440
+ // unusual executor (Promise<unknown> in the suite's shape).
4441
+ return {
4442
+ kind: "libCall",
4443
+ fn: "timers.immediatePromise",
4444
+ args: [],
4445
+ type: { kind: "promise", inner: DYN },
4446
+ loc,
4447
+ };
4448
+ }
4449
+ }
4450
+ }
4451
+ // Executors bind resolve alone or (resolve, reject): reject is a
4452
+ // real closure rejecting the promise with an Error reason (the
4453
+ // ambient override pins `reason: Error` — rejection payloads share
4454
+ // the thrown-value representation, and the OBJ kind keeps
4455
+ // catch-side instanceof and the uncaught printer working). First
4456
+ // settle wins, exactly JS: reject-after-resolve and double-reject
4457
+ // are no-ops, and an executor throw after any settle is swallowed.
4458
+ const executor = L.lowerExpr(args[0]);
4459
+ if (executor.type.kind !== "func")
4460
+ L.badType(args[0], L.typeOf(args[0]));
4461
+ if (executor.type.params.length > 1) {
4462
+ const rj = executor.type.params[1];
4463
+ if (executor.type.params.length > 2 ||
4464
+ rj.kind !== "func" ||
4465
+ rj.ret.kind !== "void" ||
4466
+ rj.params.length !== 1 ||
4467
+ rj.params[0].kind !== "object" ||
4468
+ rj.params[0].className !== "%Error") {
4469
+ // A non-contextually-typed executor VALUE whose second param
4470
+ // isn't the pinned (reason: Error) => void shape.
4471
+ L.unsupported("SC1090", args[0], "Promise executors whose reject parameter is not '(reason: Error) => void'");
4472
+ }
4473
+ }
4474
+ return { kind: "newPromise", executor, type, loc };
4475
+ }
4476
+ // The lib fence's CONSTRUCTOR chokepoint: `new` of any other
4477
+ // stdlib-declared constructor (Date, WeakMap, Proxy,
4478
+ // ArrayBuffer, RegExp, ... — and @types/node's URL, AbortController,
4479
+ // TextEncoder, ...) typechecks and reports SC2020 here. The named
4480
+ // families carry pointed hints: each states WHY no honest static
4481
+ // lowering exists (or what to use instead).
4482
+ if (L.isStdlibSymbol(symbol ?? undefined)) {
4483
+ const ctorHints = {
4484
+ RegExp: "use a regex literal (/pattern/flags) — constructed regexes have no lowering",
4485
+ String: "boxed wrapper objects have no lowering — use the string primitive (the box is only distinguishable via typeof/identity, which nothing here can honor)",
4486
+ Number: "boxed wrapper objects have no lowering — use the number primitive",
4487
+ Boolean: "boxed wrapper objects have no lowering — use the boolean primitive",
4488
+ WeakMap: "weak collections observe garbage collection, which reference counting never exposes — a strong Map behaves identically in-language: use Map",
4489
+ WeakSet: "weak collections observe garbage collection, which reference counting never exposes — a strong Set behaves identically in-language: use Set",
4490
+ WeakRef: "deref()-after-collect exposes GC timing — genuinely dynamic; hold a strong reference instead",
4491
+ FinalizationRegistry: "finalization callbacks expose GC timing — genuinely dynamic; release resources explicitly instead",
4492
+ SharedArrayBuffer: "no shared-memory threads exist in a compiled program — Uint8Array is the byte storage",
4493
+ Proxy: "property-access metaprogramming has no static lowering (every property read must resolve at compile time)",
4494
+ Function: "runtime code generation cannot be compiled ahead of time (the eval stance) — write the function",
4495
+ };
4496
+ L.noLowering(`new ${expr.expression.text}`, expr, ctorHints[expr.expression.text], symbol);
4497
+ }
4498
+ }
4499
+ // `new crypto.X509Certificate(data)` — the Dirent-style data record:
4500
+ // the certificate's lowered members (fingerprint — the SHA-1 of the
4501
+ // DER, uppercase colon-separated — plus the validFrom/validTo
4502
+ // validity window in Node's ASN1_TIME_print shape) compute AT
4503
+ // CONSTRUCTION, when Node parses too, so unparseable input throws
4504
+ // Node's exact PEM error here (ERR_OSSL_PEM_NO_START_LINE) and the
4505
+ // handle never exists. Both import forms (`crypto.X509Certificate`
4506
+ // through the namespace, named `X509Certificate`); Buffer input only
4507
+ // — the readFileSync idiom. mapType interns the matching record, so
4508
+ // locals and the composed member reads all flow. The data argument
4509
+ // feeds THREE field computations, so construction goes through an
4510
+ // interned helper whose parameter evaluates it exactly once.
4511
+ {
4512
+ const callee = expr.expression;
4513
+ const isX509 = (ts.isPropertyAccessExpression(callee) &&
4514
+ callee.name.text === "X509Certificate" &&
4515
+ L.builtinNamespaceModuleOf(callee.expression) === "crypto") ||
4516
+ (ts.isIdentifier(callee) &&
4517
+ (() => {
4518
+ const bi = L.builtinImportOf(callee);
4519
+ return bi?.module === "crypto" && bi.member === "X509Certificate";
4520
+ })());
4521
+ if (isX509) {
4522
+ const args = expr.arguments ?? [];
4523
+ if (args.length !== 1) {
4524
+ L.noLowering("X509Certificate with this argument shape", expr, "the supported form is new X509Certificate(readFileSync(path))");
4525
+ }
4526
+ const data = L.lowerExpr(args[0]);
4527
+ const isBytes = data.type.kind === "bytes" && data.type.elem === "u8";
4528
+ if (!isBytes && data.type.kind !== "string") {
4529
+ L.noLowering(`X509Certificate over '${L.fmt(data.type)}' data`, args[0], "pass the certificate Buffer or PEM string (an fs.readFileSync result)");
4530
+ }
4531
+ const t = L.mapTypeOf(L.typeOf(expr));
4532
+ if (t?.kind !== "record")
4533
+ L.badType(expr, L.typeOf(expr));
4534
+ const key = `x509.record:${isBytes ? "bytes" : "str"}`;
4535
+ let helper = L.widthHelpers.get(key);
4536
+ if (!helper) {
4537
+ helper = `%x509.record.${L.widthHelpers.size}`;
4538
+ L.widthHelpers.set(key, helper);
4539
+ const dataT = data.type;
4540
+ const dRef = { kind: "varRef", localId: "d.0", type: dataT, loc };
4541
+ const field = (name, fn) => ({
4542
+ name,
4543
+ value: { kind: "libCall", fn, args: [dRef], type: STRING, loc },
4544
+ });
4545
+ L.liftedFns.push({
4546
+ name: helper,
4547
+ params: [{ localId: "d.0", name: "d", type: dataT }],
4548
+ returnType: t,
4549
+ locals: [{ id: "d.0", name: "d", type: dataT, mutable: false }],
4550
+ body: [
4551
+ {
4552
+ kind: "return",
4553
+ value: {
4554
+ kind: "recordLit",
4555
+ fields: [
4556
+ field("fingerprint", isBytes ? "crypto.x509Fingerprint" : "crypto.x509FingerprintStr"),
4557
+ field("validFrom", isBytes ? "crypto.x509ValidFrom" : "crypto.x509ValidFromStr"),
4558
+ field("validTo", isBytes ? "crypto.x509ValidTo" : "crypto.x509ValidToStr"),
4559
+ ],
4560
+ type: t,
4561
+ loc,
4562
+ },
4563
+ loc,
4564
+ },
4565
+ ],
4566
+ loc,
4567
+ });
4568
+ }
4569
+ return { kind: "call", callee: helper, args: [data], type: t, loc };
4570
+ }
4571
+ }
4572
+ // `new X(...)` through a class VALUE (a classval-typed binding, array
4573
+ // element, map read, param): the newValue dispatch through the class
4574
+ // object's construct thunk. Arguments complete against the STATIC
4575
+ // class's one constructor signature — exact for every value legally
4576
+ // in the slot (the classval widening rule pins the ABI). tsc typed
4577
+ // the site against the slot's construct signature; a UNION-typed
4578
+ // callee (unannotated heterogeneous registries) keeps a pointed
4579
+ // fence — annotate the slot with the common constructor type.
4580
+ {
4581
+ const calleeT = L.mapTypeOf(L.typeOf(expr.expression));
4582
+ if (calleeT?.kind === "classval") {
4583
+ let info = L.classes.get(calleeT.className);
4584
+ if (!info && ts.isPropertyAccessExpression(expr.expression) && ts.isIdentifier(expr.expression.name)) {
4585
+ // A property-assigned class expression not collected yet (this
4586
+ // body lowers ahead of the assignment statement — hoisted
4587
+ // functions): collect it on demand and retry, keeping the
4588
+ // dynamic newValue dispatch below (the runtime field value
4589
+ // decides, exactly Node under reassignment-through-aliases).
4590
+ propertyAssignedClassInfoOf(L, L.checker.getSymbolAtLocation(expr.expression.name));
4591
+ info = L.classes.get(calleeT.className);
4592
+ }
4593
+ if (!info) {
4594
+ // The TYPE world names a class the lowering never registered —
4595
+ // a fenced class expression, an abstract/deferred declaration:
4596
+ // flush its own diagnostics (they tell the real story) and
4597
+ // poison this construction site, never an ICE.
4598
+ L.flushDeferredClass(calleeT.className);
4599
+ L.unsupported("SC1090", expr, "constructing through a class value whose class has no lowering (the class declaration itself was rejected — see its own diagnostic)");
4600
+ }
4601
+ // A classval of a generic FAMILY (`new () => Box<any>` slots): no
4602
+ // single constructor ABI exists to complete against. No producer
4603
+ // can fill such a slot (family values and widenings both fence),
4604
+ // so the construction site is the honest place to name it.
4605
+ if (info.generic) {
4606
+ L.unsupported("SC1090", expr, "constructing through a class value of an uninstantiated generic type (annotate the slot with a concrete instantiation — e.g. 'new (v: number) => Box<number>')");
4607
+ }
4608
+ const callee = L.lowerExpr(expr.expression);
4609
+ if (callee.type.kind !== "classval")
4610
+ L.badType(expr.expression, L.typeOf(expr.expression));
4611
+ L.noteEdge(`%${info.def.name}.constructor`);
4612
+ // Every constructor a value in this slot can dispatch to is a
4613
+ // descendant's — mark them reachable like a virtual edge.
4614
+ const below = (c) => {
4615
+ for (const s of c.subclasses) {
4616
+ L.noteEdge(`%${s.def.name}.constructor`);
4617
+ below(s);
4618
+ }
4619
+ };
4620
+ below(info);
4621
+ const args = L.completeArgs(expr.arguments ?? [], info.ctorParams, loc, expr);
4622
+ return {
4623
+ kind: "newValue",
4624
+ callee,
4625
+ args,
4626
+ type: { kind: "object", className: info.def.name },
4627
+ loc,
4628
+ };
4629
+ }
4630
+ if (calleeT?.kind === "union") {
4631
+ const def = L.unions.get(calleeT.unionId);
4632
+ if (def?.arms.some((a) => a.kind === "classval")) {
4633
+ L.unsupported("SC1090", expr, "constructing through a union of class values (annotate the slot with the common constructor type — e.g. `new () => Base` — or narrow first)");
4634
+ }
4635
+ }
4636
+ }
4637
+ // Declared-but-unlowered stdlib classes (fallback surface: the
4638
+ // http/https Agent): the fence points at the lowered shapes instead
4639
+ // of the generic construction rejection.
4640
+ {
4641
+ const STDLIB_CTOR_HINTS = {
4642
+ Agent: "constructing an http Agent (compiled clients dial one connection per request and close it with the response — pass agent: false for Node's one-shot semantics, or omit the option)",
4643
+ };
4644
+ const ctorName = ts.isIdentifier(expr.expression)
4645
+ ? expr.expression
4646
+ : ts.isPropertyAccessExpression(expr.expression) && ts.isIdentifier(expr.expression.name)
4647
+ ? expr.expression.name
4648
+ : null;
4649
+ if (ctorName !== null) {
4650
+ const raw = L.checker.getSymbolAtLocation(ctorName);
4651
+ const sym = raw && raw.flags & ts.SymbolFlags.Alias ? L.checker.getAliasedSymbol(raw) : raw;
4652
+ const hint = sym && L.isStdlibSymbol(sym) ? own(STDLIB_CTOR_HINTS, sym.name) : undefined;
4653
+ if (hint !== undefined) {
4654
+ L.unsupported("SC1090", expr, hint);
4655
+ }
4656
+ }
4657
+ }
4658
+ L.unsupported("SC1090", expr, "constructing values other than classes declared in the program");
4659
+ }
4660
+ /** A getter/setter invocation over an accessor target's receiver — the
4661
+ * same whole-program devirtualization as method calls: a virtualCall
4662
+ * when some strict subclass of the receiver's static class overrides
4663
+ * this HALF of the accessor (get and set devirtualize independently),
4664
+ * a direct call of the nearest declaration otherwise. */
4665
+ export function accessorCall(L, className, member, obj, extraArgs, ret, loc) {
4666
+ const info = L.classes.get(className);
4667
+ if (!info)
4668
+ throw new Error(`lowerer bug: accessor call on unknown class ${className}`);
4669
+ const found = L.findMethodOn(info, member);
4670
+ if (!found)
4671
+ throw new Error(`lowerer bug: no ${member} on ${className}`);
4672
+ // The abstract direct-call fence, accessor form (see
4673
+ // lowerObjectMethodCall): an abstract accessor with no concrete
4674
+ // override below has no implementation for a direct call to target.
4675
+ if (found.sig.abstract === true && !L.overrideBelow(info, member)) {
4676
+ L.pushDiag(unsupportedDiag("SC1090", loc, `${member.startsWith("get:") ? "reads" : "writes"} of the abstract accessor '${member.slice(4)}' with no concrete implementation below the receiver's static class`));
4677
+ throw new PoisonError();
4678
+ }
4679
+ if (L.overrideBelow(info, member))
4680
+ L.noteVirtualEdge(info, member);
4681
+ else
4682
+ L.noteEdge(`%${found.declarer.def.name}.${member}`);
4683
+ if (L.overrideBelow(info, member)) {
4684
+ return {
4685
+ kind: "virtualCall",
4686
+ className: info.def.name,
4687
+ method: member,
4688
+ args: [L.upcastTo(obj, info.def.name), ...extraArgs],
4689
+ type: ret,
4690
+ loc,
4691
+ };
4692
+ }
4693
+ return {
4694
+ kind: "call",
4695
+ callee: `%${found.declarer.def.name}.${member}`,
4696
+ args: [L.upcastTo(obj, found.declarer.def.name), ...extraArgs],
4697
+ type: ret,
4698
+ loc,
4699
+ };
4700
+ }
4701
+ //# sourceMappingURL=lower-classes.js.map