@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.
- package/LICENSE +202 -0
- package/ambient/package.json +3 -0
- package/ambient/scriptc-node-fallback.d.ts +3001 -0
- package/ambient/scriptc-overrides.d.ts +95 -0
- package/ambient/scriptc.d.ts +74 -0
- package/dist/backend/cc.d.ts +169 -0
- package/dist/backend/cc.js +897 -0
- package/dist/backend/cc.js.map +1 -0
- package/dist/backend/emission/emit-async.d.ts +145 -0
- package/dist/backend/emission/emit-async.js +996 -0
- package/dist/backend/emission/emit-async.js.map +1 -0
- package/dist/backend/emission/emit-exprs.d.ts +3 -0
- package/dist/backend/emission/emit-exprs.js +5947 -0
- package/dist/backend/emission/emit-exprs.js.map +1 -0
- package/dist/backend/emission/emit-island.d.ts +45 -0
- package/dist/backend/emission/emit-island.js +271 -0
- package/dist/backend/emission/emit-island.js.map +1 -0
- package/dist/backend/emission/emit-shapes.d.ts +142 -0
- package/dist/backend/emission/emit-shapes.js +575 -0
- package/dist/backend/emission/emit-shapes.js.map +1 -0
- package/dist/backend/emission/emit-stmts.d.ts +78 -0
- package/dist/backend/emission/emit-stmts.js +960 -0
- package/dist/backend/emission/emit-stmts.js.map +1 -0
- package/dist/backend/emission/emit-types.d.ts +65 -0
- package/dist/backend/emission/emit-types.js +652 -0
- package/dist/backend/emission/emit-types.js.map +1 -0
- package/dist/backend/emission/emit-walkers.d.ts +154 -0
- package/dist/backend/emission/emit-walkers.js +1587 -0
- package/dist/backend/emission/emit-walkers.js.map +1 -0
- package/dist/backend/emission/emitter.d.ts +452 -0
- package/dist/backend/emission/emitter.js +1249 -0
- package/dist/backend/emission/emitter.js.map +1 -0
- package/dist/backend/emission/may-throw.d.ts +15 -0
- package/dist/backend/emission/may-throw.js +250 -0
- package/dist/backend/emission/may-throw.js.map +1 -0
- package/dist/backend/llvm/blocks.d.ts +21 -0
- package/dist/backend/llvm/blocks.js +68 -0
- package/dist/backend/llvm/blocks.js.map +1 -0
- package/dist/backend/llvm/classes.d.ts +84 -0
- package/dist/backend/llvm/classes.js +438 -0
- package/dist/backend/llvm/classes.js.map +1 -0
- package/dist/backend/llvm/dyn.d.ts +123 -0
- package/dist/backend/llvm/dyn.js +2624 -0
- package/dist/backend/llvm/dyn.js.map +1 -0
- package/dist/backend/llvm/emitter.d.ts +3 -0
- package/dist/backend/llvm/emitter.js +10087 -0
- package/dist/backend/llvm/emitter.js.map +1 -0
- package/dist/backend/llvm/shapes.d.ts +78 -0
- package/dist/backend/llvm/shapes.js +754 -0
- package/dist/backend/llvm/shapes.js.map +1 -0
- package/dist/backend/llvm/unsupported.d.ts +6 -0
- package/dist/backend/llvm/unsupported.js +12 -0
- package/dist/backend/llvm/unsupported.js.map +1 -0
- package/dist/backend/llvm/walkers.d.ts +58 -0
- package/dist/backend/llvm/walkers.js +793 -0
- package/dist/backend/llvm/walkers.js.map +1 -0
- package/dist/backend/mangle.d.ts +124 -0
- package/dist/backend/mangle.js +232 -0
- package/dist/backend/mangle.js.map +1 -0
- package/dist/coverage/report.d.ts +64 -0
- package/dist/coverage/report.js +226 -0
- package/dist/coverage/report.js.map +1 -0
- package/dist/diagnostics/diagnostic.d.ts +140 -0
- package/dist/diagnostics/diagnostic.js +458 -0
- package/dist/diagnostics/diagnostic.js.map +1 -0
- package/dist/diagnostics/render.d.ts +11 -0
- package/dist/diagnostics/render.js +58 -0
- package/dist/diagnostics/render.js.map +1 -0
- package/dist/frontend/cjs-lexer.d.ts +20 -0
- package/dist/frontend/cjs-lexer.js +813 -0
- package/dist/frontend/cjs-lexer.js.map +1 -0
- package/dist/frontend/lowering/http2-constants.d.ts +1 -0
- package/dist/frontend/lowering/http2-constants.js +251 -0
- package/dist/frontend/lowering/http2-constants.js.map +1 -0
- package/dist/frontend/lowering/lib-boundary.d.ts +6 -0
- package/dist/frontend/lowering/lib-boundary.js +143 -0
- package/dist/frontend/lowering/lib-boundary.js.map +1 -0
- package/dist/frontend/lowering/lower-assert.d.ts +18 -0
- package/dist/frontend/lowering/lower-assert.js +1269 -0
- package/dist/frontend/lowering/lower-assert.js.map +1 -0
- package/dist/frontend/lowering/lower-builtins.d.ts +511 -0
- package/dist/frontend/lowering/lower-builtins.js +5249 -0
- package/dist/frontend/lowering/lower-builtins.js.map +1 -0
- package/dist/frontend/lowering/lower-calls.d.ts +581 -0
- package/dist/frontend/lowering/lower-calls.js +6281 -0
- package/dist/frontend/lowering/lower-calls.js.map +1 -0
- package/dist/frontend/lowering/lower-classes.d.ts +727 -0
- package/dist/frontend/lowering/lower-classes.js +4701 -0
- package/dist/frontend/lowering/lower-classes.js.map +1 -0
- package/dist/frontend/lowering/lower-comptime.d.ts +53 -0
- package/dist/frontend/lowering/lower-comptime.js +244 -0
- package/dist/frontend/lowering/lower-comptime.js.map +1 -0
- package/dist/frontend/lowering/lower-containers.d.ts +483 -0
- package/dist/frontend/lowering/lower-containers.js +6281 -0
- package/dist/frontend/lowering/lower-containers.js.map +1 -0
- package/dist/frontend/lowering/lower-dgram.d.ts +20 -0
- package/dist/frontend/lowering/lower-dgram.js +319 -0
- package/dist/frontend/lowering/lower-dgram.js.map +1 -0
- package/dist/frontend/lowering/lower-emitter.d.ts +10 -0
- package/dist/frontend/lowering/lower-emitter.js +688 -0
- package/dist/frontend/lowering/lower-emitter.js.map +1 -0
- package/dist/frontend/lowering/lower-enums.d.ts +22 -0
- package/dist/frontend/lowering/lower-enums.js +235 -0
- package/dist/frontend/lowering/lower-enums.js.map +1 -0
- package/dist/frontend/lowering/lower-expando.d.ts +35 -0
- package/dist/frontend/lowering/lower-expando.js +276 -0
- package/dist/frontend/lowering/lower-expando.js.map +1 -0
- package/dist/frontend/lowering/lower-exprs.d.ts +524 -0
- package/dist/frontend/lowering/lower-exprs.js +8099 -0
- package/dist/frontend/lowering/lower-exprs.js.map +1 -0
- package/dist/frontend/lowering/lower-generators.d.ts +50 -0
- package/dist/frontend/lowering/lower-generators.js +421 -0
- package/dist/frontend/lowering/lower-generators.js.map +1 -0
- package/dist/frontend/lowering/lower-inspect.d.ts +20 -0
- package/dist/frontend/lowering/lower-inspect.js +1069 -0
- package/dist/frontend/lowering/lower-inspect.js.map +1 -0
- package/dist/frontend/lowering/lower-island.d.ts +108 -0
- package/dist/frontend/lowering/lower-island.js +650 -0
- package/dist/frontend/lowering/lower-island.js.map +1 -0
- package/dist/frontend/lowering/lower-mixins.d.ts +96 -0
- package/dist/frontend/lowering/lower-mixins.js +534 -0
- package/dist/frontend/lowering/lower-mixins.js.map +1 -0
- package/dist/frontend/lowering/lower-modules.d.ts +135 -0
- package/dist/frontend/lowering/lower-modules.js +1613 -0
- package/dist/frontend/lowering/lower-modules.js.map +1 -0
- package/dist/frontend/lowering/lower-namespaces.d.ts +168 -0
- package/dist/frontend/lowering/lower-namespaces.js +706 -0
- package/dist/frontend/lowering/lower-namespaces.js.map +1 -0
- package/dist/frontend/lowering/lower-server.d.ts +90 -0
- package/dist/frontend/lowering/lower-server.js +3471 -0
- package/dist/frontend/lowering/lower-server.js.map +1 -0
- package/dist/frontend/lowering/lower-stmts.d.ts +361 -0
- package/dist/frontend/lowering/lower-stmts.js +5341 -0
- package/dist/frontend/lowering/lower-stmts.js.map +1 -0
- package/dist/frontend/lowering/lower-stream.d.ts +88 -0
- package/dist/frontend/lowering/lower-stream.js +1533 -0
- package/dist/frontend/lowering/lower-stream.js.map +1 -0
- package/dist/frontend/lowering/lower-test.d.ts +27 -0
- package/dist/frontend/lowering/lower-test.js +353 -0
- package/dist/frontend/lowering/lower-test.js.map +1 -0
- package/dist/frontend/lowering/lowerer.d.ts +1748 -0
- package/dist/frontend/lowering/lowerer.js +6256 -0
- package/dist/frontend/lowering/lowerer.js.map +1 -0
- package/dist/frontend/lowering/surfaces.d.ts +272 -0
- package/dist/frontend/lowering/surfaces.js +1096 -0
- package/dist/frontend/lowering/surfaces.js.map +1 -0
- package/dist/frontend/npm-static.d.ts +55 -0
- package/dist/frontend/npm-static.js +300 -0
- package/dist/frontend/npm-static.js.map +1 -0
- package/dist/frontend/npm.d.ts +330 -0
- package/dist/frontend/npm.js +1259 -0
- package/dist/frontend/npm.js.map +1 -0
- package/dist/frontend/program.d.ts +186 -0
- package/dist/frontend/program.js +2172 -0
- package/dist/frontend/program.js.map +1 -0
- package/dist/frontend/provenance-registry.d.ts +48 -0
- package/dist/frontend/provenance-registry.js +87 -0
- package/dist/frontend/provenance-registry.js.map +1 -0
- package/dist/frontend/provenance.d.ts +6 -0
- package/dist/frontend/provenance.js +459 -0
- package/dist/frontend/provenance.js.map +1 -0
- package/dist/frontend/resolve.d.ts +56 -0
- package/dist/frontend/resolve.js +682 -0
- package/dist/frontend/resolve.js.map +1 -0
- package/dist/frontend/shared.d.ts +60 -0
- package/dist/frontend/shared.js +232 -0
- package/dist/frontend/shared.js.map +1 -0
- package/dist/frontend/ts7/adapter.d.ts +7 -0
- package/dist/frontend/ts7/adapter.js +54 -0
- package/dist/frontend/ts7/adapter.js.map +1 -0
- package/dist/frontend/ts7/ast.d.ts +50 -0
- package/dist/frontend/ts7/ast.js +211 -0
- package/dist/frontend/ts7/ast.js.map +1 -0
- package/dist/frontend/ts7/census-check.d.ts +1 -0
- package/dist/frontend/ts7/census-check.js +12 -0
- package/dist/frontend/ts7/census-check.js.map +1 -0
- package/dist/frontend/ts7/checker.d.ts +140 -0
- package/dist/frontend/ts7/checker.js +544 -0
- package/dist/frontend/ts7/checker.js.map +1 -0
- package/dist/frontend/ts7/enums.d.ts +18 -0
- package/dist/frontend/ts7/enums.js +43 -0
- package/dist/frontend/ts7/enums.js.map +1 -0
- package/dist/frontend/ts7/program.d.ts +99 -0
- package/dist/frontend/ts7/program.js +278 -0
- package/dist/frontend/ts7/program.js.map +1 -0
- package/dist/frontend/ts7/world-check.d.ts +3 -0
- package/dist/frontend/ts7/world-check.js +34 -0
- package/dist/frontend/ts7/world-check.js.map +1 -0
- package/dist/frontend/types.d.ts +205 -0
- package/dist/frontend/types.js +2485 -0
- package/dist/frontend/types.js.map +1 -0
- package/dist/index.d.ts +85 -0
- package/dist/index.js +424 -0
- package/dist/index.js.map +1 -0
- package/dist/ir/nodes.d.ts +4334 -0
- package/dist/ir/nodes.js +1906 -0
- package/dist/ir/nodes.js.map +1 -0
- package/dist/ir/serialize.d.ts +4 -0
- package/dist/ir/serialize.js +34 -0
- package/dist/ir/serialize.js.map +1 -0
- package/dist/ir/validate.d.ts +36 -0
- package/dist/ir/validate.js +4884 -0
- package/dist/ir/validate.js.map +1 -0
- package/package.json +30 -6
- package/README.md +0 -3
- 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
|