@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,2485 @@
|
|
|
1
|
+
import * as ts from "./ts7/adapter.js";
|
|
2
|
+
import { arrayOf, BOOL, bytesOf, CHILD_T, DYN, F64, funcOf, isSupportedIndexValue, isSupportedMapKey, isSupportedMapValue, isSupportedSetElem, isUnitType, JSVAL, mapOf, NULL_T, PROCSTREAM_T, RUNTIME_EMITTER_CLASS, RUNTIME_ERROR_CLASSES, RUNTIME_STREAM_CLASSES, setOf, STRING, SYMBOL_T, typeEquals, typeKey, UNDEFINED_T, VOID } from "../ir/nodes.js";
|
|
3
|
+
import { isJsSourceFile, isNodeTypesPath } from "./program.js";
|
|
4
|
+
import { accessorSlotProp } from "../ir/nodes.js";
|
|
5
|
+
// typeKey moved to ir/nodes.ts (the backend needs it too, for per-type
|
|
6
|
+
// helper interning); re-exported here so frontend call sites keep their
|
|
7
|
+
// import path.
|
|
8
|
+
export { typeKey };
|
|
9
|
+
/** The island-backed ambient TYPE names of the fetch slice: standard-
|
|
10
|
+
* library interfaces whose VALUES live in the embedded engine (the
|
|
11
|
+
* engine's fetch mints the Response; AbortSignal.timeout mints the
|
|
12
|
+
* signal), so under --dynamic they map to island handles (jsval) exactly
|
|
13
|
+
* like npm-declared types. Checked with declaration provenance in mapType;
|
|
14
|
+
* consumed by the lowerer's badType for the static-build wording. */
|
|
15
|
+
export const ISLAND_AMBIENT_TYPES = ["Response", "RequestInit", "AbortSignal"];
|
|
16
|
+
/** The frontend's record-shape interner. Records are monomorphic structural
|
|
17
|
+
* shapes: fields sorted by name form the canonical identity, and two types
|
|
18
|
+
* with the same canonical field list share one shapeId (and later one C
|
|
19
|
+
* struct). Owned by the Lowerer; `mapType` needs it to intern the object
|
|
20
|
+
* types it encounters, which is why it is threaded through as a parameter. */
|
|
21
|
+
export class ShapeRegistry {
|
|
22
|
+
byKey = new Map();
|
|
23
|
+
byId = new Map();
|
|
24
|
+
/** All interned shapes in first-seen (`r0`, `r1`, ...) order. */
|
|
25
|
+
shapes = [];
|
|
26
|
+
/** ts.Types currently being mapped — breaks recursive shape types
|
|
27
|
+
* (`type T = { next: T }`), which mapType rejects by returning null. */
|
|
28
|
+
inProgress = new Set();
|
|
29
|
+
/** Interns a canonical (name-sorted) field list, returning its shapeId.
|
|
30
|
+
* `tuple` shapes (fields "0".."n-1" from a tuple type) intern SEPARATELY
|
|
31
|
+
* from structurally identical numeric-keyed object records: the flag is
|
|
32
|
+
* part of the identity because the two serialize differently (a tuple is
|
|
33
|
+
* a JSON array, an object a JSON object). A string index signature's
|
|
34
|
+
* value type (`indexValue`) is part of the identity too: the same
|
|
35
|
+
* declared fields with and without an overflow portion — or with
|
|
36
|
+
* differently-typed ones — are different structs. */
|
|
37
|
+
intern(fields, tuple = false, indexValue, declaredOrder) {
|
|
38
|
+
const key = (tuple ? "tuple!" : "") +
|
|
39
|
+
(indexValue ? `idx<${typeKey(indexValue)}>!` : "") +
|
|
40
|
+
JSON.stringify(fields.map((f) => [f.name, typeKey(f.type)]));
|
|
41
|
+
let id = this.byKey.get(key);
|
|
42
|
+
if (id === undefined) {
|
|
43
|
+
id = `r${this.shapes.length}`;
|
|
44
|
+
const shape = {
|
|
45
|
+
id,
|
|
46
|
+
fields,
|
|
47
|
+
...(tuple ? { tuple: true } : {}),
|
|
48
|
+
...(indexValue ? { indexValue } : {}),
|
|
49
|
+
// First-seen declaration order — metadata, NOT identity (see the
|
|
50
|
+
// IrRecordShape doc): a later structurally-equal type keeps the
|
|
51
|
+
// first one's order.
|
|
52
|
+
...(declaredOrder ? { declaredOrder } : {}),
|
|
53
|
+
};
|
|
54
|
+
this.byKey.set(key, id);
|
|
55
|
+
this.byId.set(id, shape);
|
|
56
|
+
this.shapes.push(shape);
|
|
57
|
+
}
|
|
58
|
+
return id;
|
|
59
|
+
}
|
|
60
|
+
get(shapeId) {
|
|
61
|
+
return this.byId.get(shapeId);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/** The frontend's union interner — mirrors ShapeRegistry. A union's
|
|
65
|
+
* canonical identity is its typeKey-sorted arm list; two ts unions whose
|
|
66
|
+
* arms map to the same IR types share one unionId (and later one runtime
|
|
67
|
+
* tag numbering: an arm's index in the canonical list IS its tag). Owned by
|
|
68
|
+
* the Lowerer; threaded through mapType exactly like ShapeRegistry. */
|
|
69
|
+
export class UnionRegistry {
|
|
70
|
+
byKey = new Map();
|
|
71
|
+
byId = new Map();
|
|
72
|
+
/** All interned unions in first-seen (`u0`, `u1`, ...) order. */
|
|
73
|
+
unions = [];
|
|
74
|
+
/** ts.Types currently being mapped — breaks recursive union types
|
|
75
|
+
* (`type T = A | { next: T }`), which mapType rejects by returning null. */
|
|
76
|
+
inProgress = new Set();
|
|
77
|
+
/** Interns a canonical (typeKey-sorted, deduplicated) arm list, returning
|
|
78
|
+
* its unionId. */
|
|
79
|
+
intern(arms) {
|
|
80
|
+
const key = JSON.stringify(arms.map(typeKey));
|
|
81
|
+
let id = this.byKey.get(key);
|
|
82
|
+
if (id === undefined) {
|
|
83
|
+
id = `u${this.unions.length}`;
|
|
84
|
+
const def = { id, arms };
|
|
85
|
+
this.byKey.set(key, id);
|
|
86
|
+
this.byId.set(id, def);
|
|
87
|
+
this.unions.push(def);
|
|
88
|
+
}
|
|
89
|
+
return id;
|
|
90
|
+
}
|
|
91
|
+
get(unionId) {
|
|
92
|
+
return this.byId.get(unionId);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
/** Human-readable rendering of an IrType for diagnostics (records expand to
|
|
96
|
+
* their canonical field list, unions to their arms; `checker.typeToString`
|
|
97
|
+
* can't — it never sees IR types). */
|
|
98
|
+
export function formatIrType(t, shapes, unions) {
|
|
99
|
+
switch (t.kind) {
|
|
100
|
+
case "f64":
|
|
101
|
+
return "number";
|
|
102
|
+
case "string":
|
|
103
|
+
return "string";
|
|
104
|
+
case "bool":
|
|
105
|
+
return "boolean";
|
|
106
|
+
case "dyn":
|
|
107
|
+
return "unknown";
|
|
108
|
+
case "caught":
|
|
109
|
+
// What tsc calls the binding; the fence messages carry the real story.
|
|
110
|
+
return "unknown";
|
|
111
|
+
case "void":
|
|
112
|
+
return "void";
|
|
113
|
+
case "undefinedT":
|
|
114
|
+
return "undefined";
|
|
115
|
+
case "nullT":
|
|
116
|
+
return "null";
|
|
117
|
+
case "array": {
|
|
118
|
+
// Union/func elements need the parens TS syntax would ("(number |
|
|
119
|
+
// string)[]" — without them the [] reads as binding to the last arm).
|
|
120
|
+
const elem = formatIrType(t.elem, shapes, unions);
|
|
121
|
+
return t.elem.kind === "union" || t.elem.kind === "func" ? `(${elem})[]` : `${elem}[]`;
|
|
122
|
+
}
|
|
123
|
+
case "bytes":
|
|
124
|
+
// The u8 kind reads as Uint8Array (Buffer maps here too — one
|
|
125
|
+
// runtime representation; the message stays honest either way).
|
|
126
|
+
return t.elem === "u8" ? "Uint8Array" : t.elem === "u32" ? "Uint32Array" : t.elem === "i32" ? "Int32Array" : "Float32Array";
|
|
127
|
+
case "map":
|
|
128
|
+
return `Map<${formatIrType(t.key, shapes, unions)}, ${formatIrType(t.value, shapes, unions)}>`;
|
|
129
|
+
case "set":
|
|
130
|
+
return `Set<${formatIrType(t.elem, shapes, unions)}>`;
|
|
131
|
+
case "func":
|
|
132
|
+
return `(${t.params.map((p) => formatIrType(p, shapes, unions)).join(", ")}) => ${formatIrType(t.ret, shapes, unions)}`;
|
|
133
|
+
case "object":
|
|
134
|
+
// Runtime-provided error classes carry '%'-prefixed IR names
|
|
135
|
+
// ("%Error") so user classes can never collide; diagnostics show the
|
|
136
|
+
// source-level name.
|
|
137
|
+
return t.className.startsWith("%") ? t.className.slice(1) : t.className;
|
|
138
|
+
case "classval":
|
|
139
|
+
// The static side, in TS's own spelling.
|
|
140
|
+
return `typeof ${t.className.startsWith("%") ? t.className.slice(1) : t.className}`;
|
|
141
|
+
case "record": {
|
|
142
|
+
const shape = shapes.get(t.shapeId);
|
|
143
|
+
if (!shape)
|
|
144
|
+
return `{ /* unknown shape ${t.shapeId} */ }`;
|
|
145
|
+
if (shape.tuple) {
|
|
146
|
+
const byIndex = [...shape.fields].sort((a, b) => Number(a.name) - Number(b.name));
|
|
147
|
+
return `[${byIndex.map((f) => formatIrType(f.type, shapes, unions)).join(", ")}]`;
|
|
148
|
+
}
|
|
149
|
+
const members = shape.fields.map((f) => {
|
|
150
|
+
// Accessor slots print in TS's accessor spelling, not the
|
|
151
|
+
// reserved '%'-field encoding.
|
|
152
|
+
const slot = accessorSlotProp(f.name);
|
|
153
|
+
if (slot && f.type.kind === "func") {
|
|
154
|
+
return slot.kind === "get"
|
|
155
|
+
? `get ${slot.prop}(): ${formatIrType(f.type.ret, shapes, unions)}`
|
|
156
|
+
: `set ${slot.prop}(${formatIrType(f.type.params[0] ?? VOID, shapes, unions)})`;
|
|
157
|
+
}
|
|
158
|
+
return `${f.name}: ${formatIrType(f.type, shapes, unions)}`;
|
|
159
|
+
});
|
|
160
|
+
if (shape.indexValue) {
|
|
161
|
+
members.push(`[key: string]: ${formatIrType(shape.indexValue, shapes, unions)}`);
|
|
162
|
+
}
|
|
163
|
+
if (members.length === 0)
|
|
164
|
+
return "{}";
|
|
165
|
+
return `{ ${members.join("; ")} }`;
|
|
166
|
+
}
|
|
167
|
+
case "union": {
|
|
168
|
+
const def = unions.get(t.unionId);
|
|
169
|
+
if (!def)
|
|
170
|
+
return `/* union ${t.unionId} */`;
|
|
171
|
+
return def.arms.map((a) => formatIrType(a, shapes, unions)).join(" | ");
|
|
172
|
+
}
|
|
173
|
+
case "jsval":
|
|
174
|
+
return "any";
|
|
175
|
+
case "regex":
|
|
176
|
+
return "RegExp";
|
|
177
|
+
case "url":
|
|
178
|
+
return "URL";
|
|
179
|
+
case "searchParams":
|
|
180
|
+
return "URLSearchParams";
|
|
181
|
+
case "symbol":
|
|
182
|
+
return "symbol";
|
|
183
|
+
case "stats":
|
|
184
|
+
return "Stats";
|
|
185
|
+
case "spawnRes":
|
|
186
|
+
return "SpawnSyncReturns";
|
|
187
|
+
case "child":
|
|
188
|
+
return "ChildProcess";
|
|
189
|
+
case "netServer":
|
|
190
|
+
return "Server";
|
|
191
|
+
case "netSocket":
|
|
192
|
+
return "Socket";
|
|
193
|
+
case "http2Session":
|
|
194
|
+
return "Http2Session";
|
|
195
|
+
case "http2Stream":
|
|
196
|
+
return "Http2Stream";
|
|
197
|
+
case "dgramSocket":
|
|
198
|
+
return "dgram.Socket";
|
|
199
|
+
case "testCtx":
|
|
200
|
+
return "TestContext";
|
|
201
|
+
case "httpReq":
|
|
202
|
+
return "IncomingMessage";
|
|
203
|
+
case "httpRes":
|
|
204
|
+
return "ServerResponse";
|
|
205
|
+
case "httpClientReq":
|
|
206
|
+
return "ClientRequest";
|
|
207
|
+
case "secureCtx":
|
|
208
|
+
return "SecureContext";
|
|
209
|
+
case "fsWatcher":
|
|
210
|
+
return "FSWatcher";
|
|
211
|
+
case "childStream":
|
|
212
|
+
return "Readable";
|
|
213
|
+
case "procStream":
|
|
214
|
+
return "WriteStream";
|
|
215
|
+
case "promise":
|
|
216
|
+
return `Promise<${formatIrType(t.inner, shapes, unions)}>`;
|
|
217
|
+
case "generator":
|
|
218
|
+
return `Generator<${formatIrType(t.yieldT, shapes, unions)}, ${formatIrType(t.retT, shapes, unions)}, ${formatIrType(t.nextT, shapes, unions)}>`;
|
|
219
|
+
default: {
|
|
220
|
+
const _exhaustive = t;
|
|
221
|
+
void _exhaustive;
|
|
222
|
+
throw new Error("unreachable");
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
/** True when the declaration sits inside `declare module "<name>"` (or
|
|
227
|
+
* its node:-prefixed twin) — the disambiguator for builtin-module type
|
|
228
|
+
* names that repeat across modules (net.Server vs http.Server). Both
|
|
229
|
+
* @types/node and the shipped fallback declare builtins this way. */
|
|
230
|
+
/** ES OrdinaryOwnPropertyKeys order over string keys: canonical array
|
|
231
|
+
* indices (the exact string spelling of an integer in [0, 2^32-1)) come
|
|
232
|
+
* first in ascending numeric order, everything else follows in the given
|
|
233
|
+
* (insertion/declaration) order. This is JS's enumeration order for the
|
|
234
|
+
* objects records model — Object.keys, JSON.stringify, spread, inspect. */
|
|
235
|
+
export function esOwnKeyOrder(names) {
|
|
236
|
+
const isArrayIndex = (name) => {
|
|
237
|
+
const n = Number(name);
|
|
238
|
+
return Number.isInteger(n) && n >= 0 && n < 4294967295 && String(n) === name;
|
|
239
|
+
};
|
|
240
|
+
const indices = names.filter(isArrayIndex).sort((a, b) => Number(a) - Number(b));
|
|
241
|
+
return indices.length === 0 ? names : [...indices, ...names.filter((n) => !isArrayIndex(n))];
|
|
242
|
+
}
|
|
243
|
+
function isDeclaredInAmbientModule(d, name) {
|
|
244
|
+
let node = d.parent;
|
|
245
|
+
while (node) {
|
|
246
|
+
if (ts.isModuleDeclaration(node) && ts.isStringLiteral(node.name)) {
|
|
247
|
+
const spec = node.name.text;
|
|
248
|
+
return spec === name || spec === `node:${name}`;
|
|
249
|
+
}
|
|
250
|
+
node = node.parent;
|
|
251
|
+
}
|
|
252
|
+
return false;
|
|
253
|
+
}
|
|
254
|
+
/** True when the declaration's nearest enclosing namespace is `name`
|
|
255
|
+
* (`declare global { namespace NodeJS { ... } }` — the NodeJS-global
|
|
256
|
+
* interfaces @types/node declares outside any ambient module). */
|
|
257
|
+
function isDeclaredInAmbientNamespace(d, name) {
|
|
258
|
+
let node = d.parent;
|
|
259
|
+
while (node) {
|
|
260
|
+
if (ts.isModuleDeclaration(node) && ts.isIdentifier(node.name)) {
|
|
261
|
+
return node.name.text === name;
|
|
262
|
+
}
|
|
263
|
+
node = node.parent;
|
|
264
|
+
}
|
|
265
|
+
return false;
|
|
266
|
+
}
|
|
267
|
+
/** True when a type contains a record anywhere (the trigger for the
|
|
268
|
+
* shape-mismatch diagnostic SC2002 — non-record mismatches are tsc's or
|
|
269
|
+
* the validator's business). */
|
|
270
|
+
export function containsRecord(t) {
|
|
271
|
+
switch (t.kind) {
|
|
272
|
+
case "record":
|
|
273
|
+
return true;
|
|
274
|
+
case "array":
|
|
275
|
+
return containsRecord(t.elem);
|
|
276
|
+
case "func":
|
|
277
|
+
return t.params.some(containsRecord) || containsRecord(t.ret);
|
|
278
|
+
default:
|
|
279
|
+
// Unions deliberately return false: a union-involving mismatch has its
|
|
280
|
+
// own diagnostic (SC2003 via containsUnion), not the record one.
|
|
281
|
+
return false;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
/** True when a type contains a union anywhere reachable without a registry
|
|
285
|
+
* (the trigger for the union-mismatch diagnostic SC2003 — e.g. a value
|
|
286
|
+
* narrowed to a SUB-union flowing into the full union's slot, which would
|
|
287
|
+
* need a runtime re-tag). */
|
|
288
|
+
export function containsUnion(t) {
|
|
289
|
+
switch (t.kind) {
|
|
290
|
+
case "union":
|
|
291
|
+
return true;
|
|
292
|
+
case "array":
|
|
293
|
+
return containsUnion(t.elem);
|
|
294
|
+
case "func":
|
|
295
|
+
return t.params.some(containsUnion) || containsUnion(t.ret);
|
|
296
|
+
default:
|
|
297
|
+
return false;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
/** lower-calls.ts's bodyReadsArguments, duplicated here (types.ts must not
|
|
301
|
+
* import from lowering/ — that edge is a module cycle): does the function's
|
|
302
|
+
* OWN body read `arguments`? Nested plain functions/methods own theirs
|
|
303
|
+
* (skipped); arrows see the enclosing one (descended). */
|
|
304
|
+
function bodyReadsArgumentsLocal(fn) {
|
|
305
|
+
let found = false;
|
|
306
|
+
if (fn.body === undefined)
|
|
307
|
+
return false;
|
|
308
|
+
// Iterative walk (walkPreorder): function bodies can hold pathologically
|
|
309
|
+
// deep expression chains that a recursive visit would die on.
|
|
310
|
+
ts.walkPreorder(fn.body, (n) => {
|
|
311
|
+
if (ts.isIdentifier(n) && n.text === "arguments" && !(ts.isPropertyAccessExpression(n.parent) && n.parent.name === n)) {
|
|
312
|
+
found = true;
|
|
313
|
+
return "stop";
|
|
314
|
+
}
|
|
315
|
+
if ((ts.isFunctionExpression(n) || ts.isFunctionDeclaration(n) || ts.isMethodDeclaration(n)) && n !== fn) {
|
|
316
|
+
return "skip"; // own `arguments` scope
|
|
317
|
+
}
|
|
318
|
+
return undefined;
|
|
319
|
+
});
|
|
320
|
+
return found;
|
|
321
|
+
}
|
|
322
|
+
/** A generator type's normalized value channels (Generator<T, TReturn,
|
|
323
|
+
* TNext> and the IteratorResult alias share this):
|
|
324
|
+
* - yield: `never` (a generator that never yields) rides the VOID
|
|
325
|
+
* sentinel; any/unknown ride dyn; undefined-only yields have no C value
|
|
326
|
+
* form (null); else the mapped type.
|
|
327
|
+
* - return: void/undefined/never carry no value (VOID — the done-value is
|
|
328
|
+
* the undefined arm); any/unknown are DEFAULTS (`Generator<number>` is
|
|
329
|
+
* Generator<number, any, any>), and a defaulted return channel means
|
|
330
|
+
* "no modeled return value" — VOID, not dyn (a dyn return channel would
|
|
331
|
+
* force the yield channel dyn too; bodies that `return v` under a
|
|
332
|
+
* defaulted TReturn keep their fence at the return site).
|
|
333
|
+
* - next: void/undefined/never mean valueless resumes (the undefined
|
|
334
|
+
* unit); any/unknown ride dyn; else the mapped type (`.next(v)` then
|
|
335
|
+
* requires its argument — fenced at the call site).
|
|
336
|
+
* Mixed dyn/concrete value channels stay unmapped (the shared result
|
|
337
|
+
* record's value slot is one representation). */
|
|
338
|
+
function genChannels(yieldTs, retTs, nextTs, ctx) {
|
|
339
|
+
if (!yieldTs)
|
|
340
|
+
return null;
|
|
341
|
+
const UNITISH = ts.TypeFlags.Void | ts.TypeFlags.Undefined | ts.TypeFlags.Never;
|
|
342
|
+
const ANYISH = ts.TypeFlags.Any | ts.TypeFlags.Unknown;
|
|
343
|
+
let yieldT;
|
|
344
|
+
if (yieldTs.flags & ts.TypeFlags.Never)
|
|
345
|
+
yieldT = VOID;
|
|
346
|
+
else if (yieldTs.flags & ANYISH)
|
|
347
|
+
yieldT = DYN;
|
|
348
|
+
else {
|
|
349
|
+
yieldT = mapType(yieldTs, ctx);
|
|
350
|
+
if (yieldT?.kind === "void" || (yieldT && isUnitType(yieldT)))
|
|
351
|
+
yieldT = null;
|
|
352
|
+
}
|
|
353
|
+
if (!yieldT)
|
|
354
|
+
return null;
|
|
355
|
+
const retT = retTs === undefined || retTs.flags & (UNITISH | ANYISH)
|
|
356
|
+
? VOID
|
|
357
|
+
: mapType(retTs, ctx);
|
|
358
|
+
if (!retT || (retT.kind !== "void" && isUnitType(retT)))
|
|
359
|
+
return null;
|
|
360
|
+
const nextT = nextTs === undefined || nextTs.flags & UNITISH
|
|
361
|
+
? UNDEFINED_T
|
|
362
|
+
: nextTs.flags & ANYISH ? DYN : mapType(nextTs, ctx);
|
|
363
|
+
if (!nextT || nextT.kind === "void" || nextT.kind === "nullT")
|
|
364
|
+
return null;
|
|
365
|
+
const dynMix = (yieldT.kind === "dyn" && retT.kind !== "dyn" && retT.kind !== "void") ||
|
|
366
|
+
(retT.kind === "dyn" && yieldT.kind !== "dyn" && yieldT.kind !== "void");
|
|
367
|
+
if (dynMix)
|
|
368
|
+
return null;
|
|
369
|
+
return { yieldT, retT, nextT };
|
|
370
|
+
}
|
|
371
|
+
/** SELF-REFERENTIAL types recurse through mapType with no structural floor:
|
|
372
|
+
* `function somefn() { return somefn; }` maps the return type by mapping the
|
|
373
|
+
* function's own type again (the 15-stack-overflow-self-return signature),
|
|
374
|
+
* and `new.target` in a class-field function expression builds the same
|
|
375
|
+
* loop through the containing class. Legitimate types bottom out fast —
|
|
376
|
+
* anything this deep is cyclic (or pathological), and unmappable (null) is
|
|
377
|
+
* the honest answer: every call site already owns a fence or checked-dyn
|
|
378
|
+
* fallback for it. */
|
|
379
|
+
const MAP_TYPE_MAX_DEPTH = 64;
|
|
380
|
+
let mapTypeDepth = 0;
|
|
381
|
+
export function mapType(type, ctx) {
|
|
382
|
+
if (mapTypeDepth >= MAP_TYPE_MAX_DEPTH)
|
|
383
|
+
return null;
|
|
384
|
+
mapTypeDepth++;
|
|
385
|
+
try {
|
|
386
|
+
return mapTypeInner(type, ctx);
|
|
387
|
+
}
|
|
388
|
+
finally {
|
|
389
|
+
mapTypeDepth--;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
/** True when a class expression can NEVER register a lowering: one
|
|
393
|
+
* enclosed by a function-like body or a class static block mints a
|
|
394
|
+
* DISTINCT class per evaluation, which lowerClassExpressionInfo always
|
|
395
|
+
* fences. Its instance/static types must stay UNMAPPED — an object type
|
|
396
|
+
* naming a struct that will never be emitted is the invalid-C escape
|
|
397
|
+
* family (every SITE using the value already carries its own fence). */
|
|
398
|
+
function classExprNeverRegisters(decl) {
|
|
399
|
+
if (!ts.isClassExpression(decl))
|
|
400
|
+
return false;
|
|
401
|
+
for (let p = decl.parent; !ts.isSourceFile(p); p = p.parent) {
|
|
402
|
+
if (ts.isFunctionLike(p) || ts.isClassStaticBlockDeclaration(p))
|
|
403
|
+
return true;
|
|
404
|
+
}
|
|
405
|
+
return false;
|
|
406
|
+
}
|
|
407
|
+
function mapTypeInner(type, ctx) {
|
|
408
|
+
const { checker, unions, classNamer, resolveTypeParam } = ctx;
|
|
409
|
+
if (resolveTypeParam && type.flags & ts.TypeFlags.TypeParameter) {
|
|
410
|
+
const bound = resolveTypeParam(type);
|
|
411
|
+
if (bound)
|
|
412
|
+
return bound;
|
|
413
|
+
}
|
|
414
|
+
// Narrowed type parameters: `!== undefined` / `!== null` / truthiness on
|
|
415
|
+
// a `T | undefined`-flavored value inside a generic body leaves the
|
|
416
|
+
// SYMBOLIC forms `T & {}`, `T & ({} | null)`, `(T & {}) | (T & null)`
|
|
417
|
+
// (concrete types compute the intersection away; only generic bodies see
|
|
418
|
+
// them). Each companion is an ALLOWANCE over the binding's arms — `{}`
|
|
419
|
+
// admits every non-unit arm, a null/undefined companion its unit — and
|
|
420
|
+
// the mapping is the binding filtered to what the narrowing kept.
|
|
421
|
+
if (resolveTypeParam) {
|
|
422
|
+
const viaNarrowedParam = mapNarrowedTypeParam(type, ctx);
|
|
423
|
+
if (viaNarrowedParam !== undefined)
|
|
424
|
+
return viaNarrowedParam;
|
|
425
|
+
}
|
|
426
|
+
const widened = checker.getBaseTypeOfLiteralType(type);
|
|
427
|
+
const flags = widened.flags;
|
|
428
|
+
if (flags & ts.TypeFlags.Number)
|
|
429
|
+
return F64;
|
|
430
|
+
if (flags & ts.TypeFlags.String)
|
|
431
|
+
return STRING;
|
|
432
|
+
if (flags & ts.TypeFlags.Boolean || flags & ts.TypeFlags.BooleanLiteral)
|
|
433
|
+
return BOOL;
|
|
434
|
+
// The lib's BOXED wrapper interfaces used as TYPES (`const n: Number =
|
|
435
|
+
// 5`): every value such a slot can hold IS the primitive — `new
|
|
436
|
+
// Number(...)` construction is fenced, so no box object ever exists —
|
|
437
|
+
// and the interface members are exactly the primitive's lowered surface.
|
|
438
|
+
// Provenance-gated: a user's own `interface Number` maps as a record.
|
|
439
|
+
{
|
|
440
|
+
const boxSym = widened.getSymbol();
|
|
441
|
+
if (boxSym &&
|
|
442
|
+
(boxSym.name === "Number" || boxSym.name === "String" || boxSym.name === "Boolean") &&
|
|
443
|
+
checker.declarationsOf(boxSym).some((d) => ctx.isStdlibFile(d.getSourceFile()))) {
|
|
444
|
+
return boxSym.name === "Number" ? F64 : boxSym.name === "String" ? STRING : BOOL;
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
// `symbol` and `unique symbol` (the type of `const k = Symbol(...)` —
|
|
448
|
+
// getBaseTypeOfLiteralType widens unique symbols, but check both flags)
|
|
449
|
+
// map to the symbol identity kind.
|
|
450
|
+
if (flags & (ts.TypeFlags.ESSymbol | ts.TypeFlags.UniqueESSymbol))
|
|
451
|
+
return SYMBOL_T;
|
|
452
|
+
// STANDALONE undefined (and void) map to VOID: return-type mapping flows
|
|
453
|
+
// through here, and `(): void | undefined` functions must stay void.
|
|
454
|
+
// Inside a union, undefined becomes the undefinedT unit ARM instead (see
|
|
455
|
+
// the union branch below). VALUE positions that today receive VOID
|
|
456
|
+
// (record fields, tuple/array elements, variable slots) substitute the
|
|
457
|
+
// unit-only union themselves (isUnitOnlyTsType + unitOnlyUnion) — the
|
|
458
|
+
// position knows it wants a value; this mapping cannot.
|
|
459
|
+
if (flags & (ts.TypeFlags.Void | ts.TypeFlags.Undefined))
|
|
460
|
+
return VOID;
|
|
461
|
+
// Standalone `null` (a `const x = null` binding, a `{ value: null }`
|
|
462
|
+
// field, a `(): null` return): the unit-only union — the value is always
|
|
463
|
+
// THE interned null instance, comparisons are tag tests, JSON serializes
|
|
464
|
+
// null. Inside a union, null keeps becoming the nullT ARM (the union
|
|
465
|
+
// branch below never recurses here for null parts).
|
|
466
|
+
if (flags & ts.TypeFlags.Null)
|
|
467
|
+
return unitOnlyUnion(unions);
|
|
468
|
+
// `never` VALUES are uninhabited — the checker only types dead reads
|
|
469
|
+
// this way (`for (const v of [])`'s loop var, the empty literal's
|
|
470
|
+
// `never[]` element) — so any representation is unobservable; f64 is the
|
|
471
|
+
// cheapest slot. Return positions never reach here (declaredReturnType
|
|
472
|
+
// and the signature branches map never returns to VOID first), and
|
|
473
|
+
// construction sites cannot exist (nothing has type never to feed them).
|
|
474
|
+
if (flags & ts.TypeFlags.Never)
|
|
475
|
+
return F64;
|
|
476
|
+
// `unknown` is a VALUE with a runtime representation (the dyn JSON DOM —
|
|
477
|
+
// JSON.parse results and unknown-typed locals/params/returns).
|
|
478
|
+
if (flags & ts.TypeFlags.Unknown)
|
|
479
|
+
return DYN;
|
|
480
|
+
// `object` (the NonPrimitive intrinsic) is a TOP type over non-primitive
|
|
481
|
+
// values — tsc admits every record, array, function, or class instance
|
|
482
|
+
// into an `object` slot, so no exact record shape can hold it. It lowers
|
|
483
|
+
// like `unknown` (the dyn DOM): assignments convert at the site (sources
|
|
484
|
+
// outside the DOM fence there, same as unknown), reads narrow back out
|
|
485
|
+
// through the same checked casts. Member accesses tsc allows on `object`
|
|
486
|
+
// (the Object.prototype surface) fence at their use sites.
|
|
487
|
+
if (flags & ts.TypeFlags.NonPrimitive)
|
|
488
|
+
return DYN;
|
|
489
|
+
// `any` is the island handle type under --dynamic (the type-hole becomes
|
|
490
|
+
// dynamically-executed code); without the flag it stays unmapped and the
|
|
491
|
+
// per-site diagnostic tells the user about --dynamic and the static
|
|
492
|
+
// alternative (`unknown` + a checked cast).
|
|
493
|
+
if (flags & ts.TypeFlags.Any)
|
|
494
|
+
return ctx.dynamic ? JSVAL : null;
|
|
495
|
+
// Enum types and enum member literal types: an enum VALUE at runtime IS
|
|
496
|
+
// its underlying primitive (tsc's own emit stores plain numbers/strings
|
|
497
|
+
// on the enum object; const enums inline them), so the enum type maps to
|
|
498
|
+
// number/string — comparisons, switch dispatch, arithmetic, and template
|
|
499
|
+
// formatting all take their ordinary primitive lowerings. A member
|
|
500
|
+
// literal type (`E.A` in type position) widens to the whole enum first
|
|
501
|
+
// (getBaseTypeOfLiteralType), so both spellings land here. Heterogeneous
|
|
502
|
+
// enums map to the number|string union (each member READ produces one
|
|
503
|
+
// exact arm; the slot re-tags like any literal-into-union assignment).
|
|
504
|
+
// A union MIXING enum literals with non-enum arms (`E | undefined`)
|
|
505
|
+
// falls through to the general union branch below, whose per-part
|
|
506
|
+
// recursion re-enters here with the pure enum parts.
|
|
507
|
+
if (flags & ts.TypeFlags.EnumLike) {
|
|
508
|
+
const parts = widened.isUnionType() ? widened.getTypes() : [widened];
|
|
509
|
+
if (parts.every((p) => p.flags & ts.TypeFlags.EnumLike)) {
|
|
510
|
+
let hasNum = false;
|
|
511
|
+
let hasStr = false;
|
|
512
|
+
for (const p of parts) {
|
|
513
|
+
if (p.flags & ts.TypeFlags.StringLiteral)
|
|
514
|
+
hasStr = true;
|
|
515
|
+
// NumberLiteral members, and the non-literal `Enum` flavor (an
|
|
516
|
+
// enum with computed members is a NUMERIC enum type — string
|
|
517
|
+
// members would make it a literal enum by definition).
|
|
518
|
+
else
|
|
519
|
+
hasNum = true;
|
|
520
|
+
}
|
|
521
|
+
if (hasNum && hasStr) {
|
|
522
|
+
const arms = [F64, STRING];
|
|
523
|
+
arms.sort((a, b) => (typeKey(a) < typeKey(b) ? -1 : 1));
|
|
524
|
+
return { kind: "union", unionId: unions.intern(arms) };
|
|
525
|
+
}
|
|
526
|
+
return hasStr ? STRING : F64;
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
// Types DECLARED by a shipped .d.ts — an npm package's, or a LOCAL
|
|
530
|
+
// declaration file describing sibling JS the program loads dynamically
|
|
531
|
+
// (an Emscripten factory's .d.mts): the .d.ts is trusted as the type
|
|
532
|
+
// surface, but the values behind it live in the embedded engine — under
|
|
533
|
+
// --dynamic they are island handles (jsval), and every operation on them
|
|
534
|
+
// rides the engine ops with validated exits at typed boundaries.
|
|
535
|
+
// Primitives/arrays/etc. REACHED THROUGH such types keep their
|
|
536
|
+
// structural mapping (they were handled above or recurse normally); this
|
|
537
|
+
// rule fires only when the type's own identity is declaration-file-
|
|
538
|
+
// declared — interfaces, classes, type literals, and aliases from the
|
|
539
|
+
// .d.ts. The standard library's declaration files are carved out (their
|
|
540
|
+
// surfaces have static lowerings), and the program's own compiled
|
|
541
|
+
// modules are never declaration files. Without --dynamic it stays
|
|
542
|
+
// unmapped; badType reports the per-package requires-dynamic diagnostic
|
|
543
|
+
// for node_modules types and the generic story otherwise.
|
|
544
|
+
const npmSym = widened.getAliasSymbol() ?? widened.getSymbol();
|
|
545
|
+
const npmDecls = npmSym ? checker.declarationsOf(npmSym) : undefined;
|
|
546
|
+
if (npmDecls &&
|
|
547
|
+
npmDecls.length > 0 &&
|
|
548
|
+
npmDecls.every((d) => {
|
|
549
|
+
const sf = d.getSourceFile();
|
|
550
|
+
return sf.isDeclarationFile && !ctx.isStdlibFile(sf);
|
|
551
|
+
})) {
|
|
552
|
+
return ctx.dynamic ? JSVAL : null;
|
|
553
|
+
}
|
|
554
|
+
// NOTE on module NAMESPACE types (`typeof import("./x.mjs")` — what a
|
|
555
|
+
// dynamic import resolves to): non-stdlib ones fall under the rule
|
|
556
|
+
// above (their declarations are the .d.ts SourceFiles themselves).
|
|
557
|
+
// Stdlib `declare module` namespaces ("fs", "path") deliberately stay
|
|
558
|
+
// unmapped: their members have STATIC lowerings keyed off import
|
|
559
|
+
// bindings (builtinImportOf), and a handle mapping would reroute
|
|
560
|
+
// `import * as path` member calls into the engine — dynamic imports of
|
|
561
|
+
// builtins get their handles from the import lowering's IR type
|
|
562
|
+
// instead.
|
|
563
|
+
// T[]: monomorphic arrays, recursively (number[][] works). An element type
|
|
564
|
+
// that doesn't map (never from a context-free `[]`) makes the whole array
|
|
565
|
+
// unsupported — null propagates. Record/object/union elements ride the
|
|
566
|
+
// runtime's REF element kind (per-array RC entry points, the map-value
|
|
567
|
+
// technique), and PROMISE elements ride it too (Promise<T>[] is
|
|
568
|
+
// Promise.all's food: promises are refcounted, cycle-headered values
|
|
569
|
+
// with `_v` adapters like any other ref element — they just never
|
|
570
|
+
// JSON-serialize or cross the island boundary, which the safety
|
|
571
|
+
// predicates already refuse). FUNCTION elements ride REF too (closure
|
|
572
|
+
// `_v` adapters + scr_closure_trace_v — closures are cycle-headered):
|
|
573
|
+
// `f === f` identity through indexOf/includes is sound because a
|
|
574
|
+
// function VALUE is one ScrClosure for its whole life — top-level
|
|
575
|
+
// declarations intern one immortal closure, and inner closures are
|
|
576
|
+
// allocated once at their definition's evaluation and flow by
|
|
577
|
+
// reference, exactly JS's function identity. map/set/regex/url/dyn and
|
|
578
|
+
// the other opaque handles stay unsupported.
|
|
579
|
+
if (checker.isArrayType(widened)) {
|
|
580
|
+
const elemTs = checker.getTypeArguments(widened)[0];
|
|
581
|
+
if (!elemTs)
|
|
582
|
+
return null;
|
|
583
|
+
let elem = mapType(elemTs, ctx);
|
|
584
|
+
// `undefined[]` (sparse literals — `[,]` — and explicit annotations):
|
|
585
|
+
// the element rides the unit-only union like a record field would; the
|
|
586
|
+
// VOID mapping is a return-position artifact, not a value.
|
|
587
|
+
if (elem?.kind === "void" && isUnitOnlyTsType(elemTs))
|
|
588
|
+
elem = unitOnlyUnion(unions);
|
|
589
|
+
// A jsval element ABSORBS the array — with one carve-out. An array
|
|
590
|
+
// type entangled with package-declared ('npm-jsval') elements
|
|
591
|
+
// (`GeneratedFile[]`, `ModelMessage[]`) describes ISLAND values: the
|
|
592
|
+
// handles those APIs produce are engine arrays, never the static
|
|
593
|
+
// ScrArr this mapping would otherwise claim, so the whole array is
|
|
594
|
+
// one handle and every operation on it is an engine op. The carve-out
|
|
595
|
+
// is literal `any[]`: an EXPLICIT static array of dynamic values —
|
|
596
|
+
// the messages-building pattern (`const content: any[] = []`) — which
|
|
597
|
+
// keeps its static array-of-handles representation.
|
|
598
|
+
if (elem?.kind === "jsval" && !(elemTs.flags & ts.TypeFlags.Any))
|
|
599
|
+
return JSVAL;
|
|
600
|
+
if (!elem ||
|
|
601
|
+
elem.kind === "void" ||
|
|
602
|
+
elem.kind === "map" ||
|
|
603
|
+
elem.kind === "set" ||
|
|
604
|
+
elem.kind === "regex" ||
|
|
605
|
+
elem.kind === "url" ||
|
|
606
|
+
elem.kind === "searchParams" ||
|
|
607
|
+
elem.kind === "stats" ||
|
|
608
|
+
elem.kind === "spawnRes" ||
|
|
609
|
+
elem.kind === "netSocket" ||
|
|
610
|
+
elem.kind === "dgramSocket" ||
|
|
611
|
+
elem.kind === "testCtx" ||
|
|
612
|
+
elem.kind === "httpReq" ||
|
|
613
|
+
elem.kind === "httpRes" ||
|
|
614
|
+
elem.kind === "httpClientReq" ||
|
|
615
|
+
elem.kind === "secureCtx" ||
|
|
616
|
+
elem.kind === "fsWatcher" ||
|
|
617
|
+
elem.kind === "childStream" ||
|
|
618
|
+
elem.kind === "procStream" ||
|
|
619
|
+
elem.kind === "generator" ||
|
|
620
|
+
elem.kind === "dyn") {
|
|
621
|
+
return null; // ScrArr has no closure/map/regex/url/dyn element kinds yet
|
|
622
|
+
}
|
|
623
|
+
// ChildProcess[] (the running-apps list) and Server[] (the [...set]
|
|
624
|
+
// drain of the auxiliary-server registries): handles are ordinary
|
|
625
|
+
// refcounted REF elements (their `_v` adapters, no trace — both drop
|
|
626
|
+
// their closures at their terminal event).
|
|
627
|
+
return arrayOf(elem);
|
|
628
|
+
}
|
|
629
|
+
// Tuples: `[string, string]` maps to an interned RECORD shape flagged
|
|
630
|
+
// `tuple`, with one field per position named by its index ("0", "1", ...)
|
|
631
|
+
// — fixed shape, literal-index access, length = the arity constant
|
|
632
|
+
// (SEMANTICS.md). The flag keeps tuple shapes distinct from numeric-keyed
|
|
633
|
+
// object records (JSON must serialize a tuple as an ARRAY) and is why
|
|
634
|
+
// interning happens through the tuple-aware path. Optional/rest elements
|
|
635
|
+
// have no fixed shape and stay unmapped; element types follow record-field
|
|
636
|
+
// rules (no void/dyn).
|
|
637
|
+
if (checker.isTupleType(widened)) {
|
|
638
|
+
const ref = widened;
|
|
639
|
+
// elementFlags live on the tuple SHAPE: a direct tuple type carries
|
|
640
|
+
// them itself; a REFERENCE to one (isTupleType still answers true —
|
|
641
|
+
// the facade's 5.9.3 contract) reads them off its target.
|
|
642
|
+
const tupleShape = ref.elementFlags !== undefined
|
|
643
|
+
? ref
|
|
644
|
+
: ref.getTarget();
|
|
645
|
+
// The empty tuple `[]` — a declared annotation, or the facade edge
|
|
646
|
+
// where isTupleType answers true with NO element flags on the shape or
|
|
647
|
+
// its target (the empty-array arm of `''.match(/x/) || []`; the
|
|
648
|
+
// declared `[]` reads the same way, 0 type arguments and no flags). A
|
|
649
|
+
// zero-field tuple SHAPE would violate the IR's tuple invariant
|
|
650
|
+
// (positional fields "0".."n-1", n >= 1), but the type needs no shape
|
|
651
|
+
// at all — its only inhabitant is the empty array. It rides the ARRAY
|
|
652
|
+
// representation over the unit-only element (the `undefined[]` rule):
|
|
653
|
+
// length reads the runtime 0 and no element is ever written (push
|
|
654
|
+
// takes `never`; indexed reads are out of range for tsc). Element-
|
|
655
|
+
// FACING surfaces (JSON.stringify, spread, for-of) keep their
|
|
656
|
+
// unit-element fences: no element exists, but the type-directed checks
|
|
657
|
+
// see the unit arm.
|
|
658
|
+
const args = checker.getTypeArguments(ref);
|
|
659
|
+
if (args.length === 0)
|
|
660
|
+
return arrayOf(unitOnlyUnion(unions));
|
|
661
|
+
if (tupleShape?.elementFlags === undefined)
|
|
662
|
+
return null;
|
|
663
|
+
// Optional/rest elements (`[string?, number?]`, `[string, ...number[]]`)
|
|
664
|
+
// have no fixed shape — the ARITY is a runtime fact. Under --dynamic
|
|
665
|
+
// the value lives in the engine, a real JS array carrying its true
|
|
666
|
+
// length (reads, destructuring, JSON, .length all exact); static
|
|
667
|
+
// builds stay unmapped and badType tells the dynamic-tier story.
|
|
668
|
+
if (tupleShape.elementFlags.some((f) => !(f & ts.ElementFlags.Required))) {
|
|
669
|
+
return ctx.dynamic ? JSVAL : null;
|
|
670
|
+
}
|
|
671
|
+
const fields = [];
|
|
672
|
+
for (let i = 0; i < args.length; i++) {
|
|
673
|
+
let et = mapType(args[i], ctx);
|
|
674
|
+
// A unit-only element (`[number, undefined]`) rides the unit-only
|
|
675
|
+
// union, the record-field rule.
|
|
676
|
+
if (et?.kind === "void" && isUnitOnlyTsType(args[i]))
|
|
677
|
+
et = unitOnlyUnion(unions);
|
|
678
|
+
// dyn ELEMENTS map now — `[string, unknown]`, the Object.entries
|
|
679
|
+
// tuple over an `unknown`-valued index signature (the pricing-table
|
|
680
|
+
// normalizer shape): the slot has the overflow map's RC/JSON/DOM
|
|
681
|
+
// plumbing, values arrive dyn or convert via dynFrom.
|
|
682
|
+
if (!et || et.kind === "void")
|
|
683
|
+
return null;
|
|
684
|
+
// A jsval MEMBER absorbs the tuple (bare jsval fields have no shape
|
|
685
|
+
// slot — the record-field rule), exactly like record fields below.
|
|
686
|
+
if (et.kind === "jsval")
|
|
687
|
+
return JSVAL;
|
|
688
|
+
fields.push({ name: String(i), type: et });
|
|
689
|
+
}
|
|
690
|
+
fields.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
|
|
691
|
+
return { kind: "record", shapeId: ctx.shapes.intern(fields, true) };
|
|
692
|
+
}
|
|
693
|
+
// Class instances: the type's symbol is a class declared in the user's
|
|
694
|
+
// file. The class NAME as a value has the *constructor* type — same
|
|
695
|
+
// REFINED handle intersections — @types/node's idioms: `ServerResponse<
|
|
696
|
+
// IncomingMessage> & { req: IncomingMessage }` (RequestListener's
|
|
697
|
+
// inferred res param — every unannotated http.createServer handler) and
|
|
698
|
+
// the cast shape `Socket & { encrypted?: boolean }`. The VALUE is the
|
|
699
|
+
// handle; the refinement's extra members are type-level decoration whose
|
|
700
|
+
// uses fence per site (they are not stdlib members). Exactly one
|
|
701
|
+
// constituent must map to an opaque builtin-handle kind; every other
|
|
702
|
+
// part must be a plain object refinement — no call/construct signatures,
|
|
703
|
+
// no class identity of its own.
|
|
704
|
+
if (widened.isIntersectionType()) {
|
|
705
|
+
const HANDLE_KINDS = new Set([
|
|
706
|
+
"netServer", "netSocket", "httpReq", "httpRes", "httpClientReq", "dgramSocket",
|
|
707
|
+
// process.stdout's own type IS the refined intersection
|
|
708
|
+
// `WriteStream & { fd: 1 }` — the scalar stream kind rides the same
|
|
709
|
+
// refinement rule.
|
|
710
|
+
"procStream",
|
|
711
|
+
]);
|
|
712
|
+
let handle = null;
|
|
713
|
+
let refined = true;
|
|
714
|
+
for (const part of widened.getTypes()) {
|
|
715
|
+
const mapped = mapType(part, ctx);
|
|
716
|
+
if (mapped && HANDLE_KINDS.has(mapped.kind)) {
|
|
717
|
+
if (handle !== null && handle.kind !== mapped.kind) {
|
|
718
|
+
refined = false;
|
|
719
|
+
break;
|
|
720
|
+
}
|
|
721
|
+
handle = mapped;
|
|
722
|
+
continue;
|
|
723
|
+
}
|
|
724
|
+
const partSym = part.getSymbol();
|
|
725
|
+
if ((part.flags & ts.TypeFlags.Object) === 0 ||
|
|
726
|
+
checker.getCallSignatures(part).length > 0 ||
|
|
727
|
+
checker.getConstructSignatures(part).length > 0 ||
|
|
728
|
+
(partSym !== undefined && (partSym.flags & ts.SymbolFlags.Class) !== 0)) {
|
|
729
|
+
refined = false;
|
|
730
|
+
break;
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
if (refined && handle !== null)
|
|
734
|
+
return handle;
|
|
735
|
+
// MIXIN instance intersections (`Tagged.C & Derived` — values built
|
|
736
|
+
// through a mixin result): the chain structure names the unique
|
|
737
|
+
// pinned instantiation; ambiguity stays unmapped (the hook's rules).
|
|
738
|
+
{
|
|
739
|
+
const viaMixin = ctx.mixinIntersectionInstance?.(widened);
|
|
740
|
+
if (viaMixin)
|
|
741
|
+
return viaMixin;
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
// symbol, but with construct signatures — that is the STATIC side, and
|
|
745
|
+
// it maps to classval below.
|
|
746
|
+
const widenedSym = widened.getSymbol();
|
|
747
|
+
const classDecl = widenedSym ? checker.valueDeclarationOf(widenedSym) : undefined;
|
|
748
|
+
if (classDecl &&
|
|
749
|
+
(ts.isClassDeclaration(classDecl) || ts.isClassExpression(classDecl)) &&
|
|
750
|
+
// Nameless class DECLARATIONS are legal only as `export default class
|
|
751
|
+
// {}` — classNamer spells them "%anon" (unique per file), the same
|
|
752
|
+
// name their collection registered.
|
|
753
|
+
!classDecl.getSourceFile().isDeclarationFile &&
|
|
754
|
+
checker.getConstructSignatures(widened).length === 0) {
|
|
755
|
+
// Declared OUTSIDE the lowered program (reached through the type world
|
|
756
|
+
// alone — jsdoc `typeof import()` over a never-required module): the
|
|
757
|
+
// class will never register, so the instance type is unmappable, not
|
|
758
|
+
// an object type naming a struct that cannot exist.
|
|
759
|
+
if (!ctx.isProgramFile(classDecl.getSourceFile()))
|
|
760
|
+
return null;
|
|
761
|
+
// The class inside a MIXIN function: per-call-site instantiations
|
|
762
|
+
// share this one node — the CURRENT instantiation (collecting or
|
|
763
|
+
// lowering) is the answer; outside any mixin context the node's type
|
|
764
|
+
// stays unmapped (nothing names a call site).
|
|
765
|
+
{
|
|
766
|
+
const viaMixin = ctx.mixinClassInstance?.(classDecl);
|
|
767
|
+
if (viaMixin)
|
|
768
|
+
return viaMixin;
|
|
769
|
+
}
|
|
770
|
+
// A class expression inside a function/static block never registers
|
|
771
|
+
// (a distinct class per evaluation): unmappable for the same reason.
|
|
772
|
+
if (classExprNeverRegisters(classDecl))
|
|
773
|
+
return null;
|
|
774
|
+
// A GENERIC class's instance type (`Box<number>`) maps to the concrete
|
|
775
|
+
// INSTANTIATION's class (`Box%0`), registered on demand — the Lowerer
|
|
776
|
+
// hook owns the instance table (monomorphization by flow).
|
|
777
|
+
if (classDecl.typeParameters) {
|
|
778
|
+
return ctx.genericClassInstance ? ctx.genericClassInstance(classDecl, widened) : null;
|
|
779
|
+
}
|
|
780
|
+
return { kind: "object", className: classNamer(classDecl) };
|
|
781
|
+
}
|
|
782
|
+
// The class STATIC side (`typeof C` — the class name as a value): the
|
|
783
|
+
// same symbol WITH its construct signatures. Program classes only; lib
|
|
784
|
+
// and package statics keep their fences.
|
|
785
|
+
if (classDecl &&
|
|
786
|
+
(ts.isClassDeclaration(classDecl) || ts.isClassExpression(classDecl)) &&
|
|
787
|
+
!classDecl.getSourceFile().isDeclarationFile &&
|
|
788
|
+
checker.getConstructSignatures(widened).length > 0) {
|
|
789
|
+
if (!ctx.isProgramFile(classDecl.getSourceFile()))
|
|
790
|
+
return null;
|
|
791
|
+
// The MIXIN class node's static side (`typeof C` inside the mixin):
|
|
792
|
+
// the current instantiation's classval, like the instance type above.
|
|
793
|
+
{
|
|
794
|
+
const viaMixin = ctx.mixinClassInstance?.(classDecl);
|
|
795
|
+
if (viaMixin?.kind === "object")
|
|
796
|
+
return { kind: "classval", className: viaMixin.className };
|
|
797
|
+
}
|
|
798
|
+
if (classExprNeverRegisters(classDecl))
|
|
799
|
+
return null;
|
|
800
|
+
// A GENERIC class's static side: only an INSTANTIATED one maps — an
|
|
801
|
+
// instantiation expression's type (`Box<number>` as a value) carries a
|
|
802
|
+
// construct signature returning the concrete instance, which maps to
|
|
803
|
+
// the instantiation's classval. Uninstantiated `typeof Box` keeps the
|
|
804
|
+
// type parameter in the construct signature's return — unmapped (the
|
|
805
|
+
// family has no thunk and no single constructor ABI).
|
|
806
|
+
if (classDecl.typeParameters) {
|
|
807
|
+
const ctorSigs = checker.getConstructSignatures(widened);
|
|
808
|
+
if (ctorSigs.length !== 1)
|
|
809
|
+
return null;
|
|
810
|
+
const inst = mapType(checker.getReturnTypeOfSignature(ctorSigs[0]), ctx);
|
|
811
|
+
return inst?.kind === "object" ? { kind: "classval", className: inst.className } : null;
|
|
812
|
+
}
|
|
813
|
+
return { kind: "classval", className: classNamer(classDecl) };
|
|
814
|
+
}
|
|
815
|
+
// Constructor-signature TYPES (`new (…) => T`, an interface with one
|
|
816
|
+
// construct signature): slots typed as constructables hold class
|
|
817
|
+
// values whose instance type is T — classval of T's class. The spelled
|
|
818
|
+
// signature is not part of the IR type: every value legally in the slot
|
|
819
|
+
// shares the class's own completed constructor ABI (the upcast rule),
|
|
820
|
+
// which is what newValue completes against. Only single-signature
|
|
821
|
+
// constructables over PROGRAM class instances map; overload sets and
|
|
822
|
+
// lib/package constructables stay unmapped (their fences name them).
|
|
823
|
+
{
|
|
824
|
+
const ctorSigs = checker.getConstructSignatures(widened);
|
|
825
|
+
if (ctorSigs.length === 1 && checker.getCallSignatures(widened).length === 0) {
|
|
826
|
+
const inst = mapType(checker.getReturnTypeOfSignature(ctorSigs[0]), ctx);
|
|
827
|
+
if (inst?.kind === "object" &&
|
|
828
|
+
!RUNTIME_ERROR_CLASSES.has(inst.className) &&
|
|
829
|
+
inst.className !== RUNTIME_EMITTER_CLASS &&
|
|
830
|
+
!RUNTIME_STREAM_CLASSES.has(inst.className)) {
|
|
831
|
+
return { kind: "classval", className: inst.className };
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
// Map<K, V>: a reference to the standard library's Map interface —
|
|
836
|
+
// provenance, not the name (a user's own `interface Map` maps as a record
|
|
837
|
+
// like any other). Keys are fenced to f64/string (SameValueZero hashing),
|
|
838
|
+
// values to the supported kinds (see isSupportedMapValue); anything
|
|
839
|
+
// outside stays unmapped — callers report SC2001 with the full type
|
|
840
|
+
// text, and the `new Map` lowering names the offending half specifically.
|
|
841
|
+
const psym = widened.getSymbol();
|
|
842
|
+
const isStdlibInterface = (name) => psym?.name === name &&
|
|
843
|
+
checker.declarationsOf(psym).some((d) => ts.isInterfaceDeclaration(d) && ctx.isStdlibFile(d.getSourceFile()));
|
|
844
|
+
// The builtin Error classes: references to the LIB's Error/TypeError/
|
|
845
|
+
// RangeError/SyntaxError interfaces map to the runtime-provided class
|
|
846
|
+
// hierarchy (provenance, not the name — a user's own `class Error`
|
|
847
|
+
// resolved through the class-instance branch above, and a user
|
|
848
|
+
// `interface Error` maps as a record). The '%'-prefixed IR names are the
|
|
849
|
+
// RUNTIME_ERROR_CLASSES table's; the Lowerer registers the matching
|
|
850
|
+
// ClassInfos eagerly, so every consumer of the class name finds them.
|
|
851
|
+
for (const [irName, rec] of RUNTIME_ERROR_CLASSES) {
|
|
852
|
+
if (isStdlibInterface(rec.lib))
|
|
853
|
+
return { kind: "object", className: irName };
|
|
854
|
+
}
|
|
855
|
+
// The lib's `Object` interface is a TOP type: tsc admits every
|
|
856
|
+
// non-nullish value into an `Object` slot (`const x: Object = "s"`), so
|
|
857
|
+
// it lowers like `unknown` (the dyn DOM) — the same rule as `object` and
|
|
858
|
+
// the empty object type, provenance-checked so a user's own `interface
|
|
859
|
+
// Object` still maps as a record. Its Object.prototype member surface
|
|
860
|
+
// (`x.toString()`) fences at each use site, never silently.
|
|
861
|
+
if (isStdlibInterface("Object"))
|
|
862
|
+
return DYN;
|
|
863
|
+
// events.EventEmitter: the runtime-provided emitter base class (the
|
|
864
|
+
// Error-hierarchy precedent — user subclasses resolved through the
|
|
865
|
+
// class-instance branch above). Both type layers declare it in the
|
|
866
|
+
// "events" ambient module — the fallback as a class, @types/node as a
|
|
867
|
+
// (generic) class plus a same-named interface — and @types/node's
|
|
868
|
+
// NodeJS.EventEmitter interface (the structural surface net.Socket
|
|
869
|
+
// et al. extend) is the same runtime value. Provenance-checked.
|
|
870
|
+
if (psym?.name === "EventEmitter" &&
|
|
871
|
+
checker.declarationsOf(psym).some((d) => (ts.isClassDeclaration(d) || ts.isInterfaceDeclaration(d)) &&
|
|
872
|
+
ctx.isStdlibFile(d.getSourceFile()) &&
|
|
873
|
+
(isDeclaredInAmbientModule(d, "events") || isDeclaredInAmbientNamespace(d, "NodeJS")))) {
|
|
874
|
+
return { kind: "object", className: RUNTIME_EMITTER_CLASS };
|
|
875
|
+
}
|
|
876
|
+
// readline.Interface: the interface value is an f64 handle into the
|
|
877
|
+
// runtime's registry (the Timeout-id precedent). The NAME is generic,
|
|
878
|
+
// so the provenance check adds the enclosing ambient module, like
|
|
879
|
+
// net.Server (@types/node also declares Interface in
|
|
880
|
+
// readline/promises — that one stays unmapped and fences).
|
|
881
|
+
if (psym?.name === "Interface" &&
|
|
882
|
+
checker.declarationsOf(psym).some((d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) &&
|
|
883
|
+
ctx.isStdlibFile(d.getSourceFile()) &&
|
|
884
|
+
isDeclaredInAmbientModule(d, "readline"))) {
|
|
885
|
+
return F64;
|
|
886
|
+
}
|
|
887
|
+
// diagnostics_channel.Channel: the channel value is an f64 handle into
|
|
888
|
+
// the runtime's channel registry (the readline.Interface precedent —
|
|
889
|
+
// Node's channels are process-lived too, its WeakRef machinery aside).
|
|
890
|
+
// Provenance: the name is specific enough that stdlib-file provenance
|
|
891
|
+
// plus the ambient module suffices for both the fallback declarations
|
|
892
|
+
// and @types/node's class Channel<StoreType, ContextType>.
|
|
893
|
+
if (psym?.name === "Channel" &&
|
|
894
|
+
checker.declarationsOf(psym).some((d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) &&
|
|
895
|
+
ctx.isStdlibFile(d.getSourceFile()) &&
|
|
896
|
+
isDeclaredInAmbientModule(d, "diagnostics_channel"))) {
|
|
897
|
+
return F64;
|
|
898
|
+
}
|
|
899
|
+
// async_hooks.AsyncLocalStorage: the store value is an f64 handle into
|
|
900
|
+
// the runtime's store id space (the Channel story — stores are
|
|
901
|
+
// process-lived; contexts ride the fiber machinery).
|
|
902
|
+
if (psym?.name === "AsyncLocalStorage" &&
|
|
903
|
+
checker.declarationsOf(psym).some((d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) &&
|
|
904
|
+
ctx.isStdlibFile(d.getSourceFile()) &&
|
|
905
|
+
isDeclaredInAmbientModule(d, "async_hooks"))) {
|
|
906
|
+
return F64;
|
|
907
|
+
}
|
|
908
|
+
// diagnostics_channel.TracingChannel: the same f64-handle story over the
|
|
909
|
+
// runtime's tracing registry (five event channels per entry).
|
|
910
|
+
if (psym?.name === "TracingChannel" &&
|
|
911
|
+
checker.declarationsOf(psym).some((d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) &&
|
|
912
|
+
ctx.isStdlibFile(d.getSourceFile()) &&
|
|
913
|
+
isDeclaredInAmbientModule(d, "diagnostics_channel"))) {
|
|
914
|
+
return F64;
|
|
915
|
+
}
|
|
916
|
+
// string_decoder.StringDecoder: the decoder value is a two-field record
|
|
917
|
+
// — the CANONICAL encoding name (construction normalizes aliases; the
|
|
918
|
+
// `.encoding` property reads it) and the f64 packing the pending
|
|
919
|
+
// partial sequence (the %strdec helpers' state cell). Provenance-
|
|
920
|
+
// checked like Stats; construction and the write/end methods are
|
|
921
|
+
// special-cased in lowerNew / lowerStringDecoderMethodCall, so the
|
|
922
|
+
// record shape never surfaces.
|
|
923
|
+
if (psym?.name === "StringDecoder" &&
|
|
924
|
+
checker.declarationsOf(psym).some((d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) &&
|
|
925
|
+
ctx.isStdlibFile(d.getSourceFile()))) {
|
|
926
|
+
return {
|
|
927
|
+
kind: "record",
|
|
928
|
+
shapeId: ctx.shapes.intern([
|
|
929
|
+
{ name: "%enc", type: STRING },
|
|
930
|
+
{ name: "%pending", type: F64 },
|
|
931
|
+
]),
|
|
932
|
+
};
|
|
933
|
+
}
|
|
934
|
+
// Promise.withResolvers's return type — `{ promise, resolve, reject }`
|
|
935
|
+
// from the overrides declaration (or es2024's PromiseWithResolvers,
|
|
936
|
+
// the same shape): a real data record, but its anonymous literal lives
|
|
937
|
+
// in a declaration file, which recordProvenanceOk rightly refuses for
|
|
938
|
+
// lib type worlds. Map it manually with the executor's own scriptc
|
|
939
|
+
// field shapes (plain-value resolve — () => void for Promise<void> —
|
|
940
|
+
// and the Error-pinned reject), so both the destructured and the
|
|
941
|
+
// record-holding binding forms compile. User-declared literals with
|
|
942
|
+
// these member names keep the ordinary record path (their declarations
|
|
943
|
+
// are not in declaration files).
|
|
944
|
+
if (flags & ts.TypeFlags.Object &&
|
|
945
|
+
(psym ? checker.declarationsOf(psym).length : 0) > 0 &&
|
|
946
|
+
(psym ? checker.declarationsOf(psym) : []).every((d) => d.getSourceFile().isDeclarationFile)) {
|
|
947
|
+
const props = checker.getPropertiesOfType(widened);
|
|
948
|
+
if (props.length === 3 &&
|
|
949
|
+
["promise", "resolve", "reject"].every((n) => props.some((p) => p.name === n))) {
|
|
950
|
+
const promProp = props.find((p) => p.name === "promise");
|
|
951
|
+
const promT = mapType(checker.getTypeOfSymbol(promProp), ctx);
|
|
952
|
+
if (promT?.kind === "promise") {
|
|
953
|
+
const inner = promT.inner;
|
|
954
|
+
return {
|
|
955
|
+
kind: "record",
|
|
956
|
+
shapeId: ctx.shapes.intern([
|
|
957
|
+
{ name: "promise", type: promT },
|
|
958
|
+
{
|
|
959
|
+
name: "reject",
|
|
960
|
+
type: { kind: "func", params: [{ kind: "object", className: "%Error" }], ret: VOID },
|
|
961
|
+
},
|
|
962
|
+
{
|
|
963
|
+
name: "resolve",
|
|
964
|
+
type: { kind: "func", params: inner.kind === "void" ? [] : [inner], ret: VOID },
|
|
965
|
+
},
|
|
966
|
+
], false, undefined, ["promise", "resolve", "reject"]),
|
|
967
|
+
};
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
// NodeJS.ErrnoException (@types/node): an Error with optional
|
|
972
|
+
// code/errno/syscall/path members. The VALUE is a plain runtime error
|
|
973
|
+
// (fs/exec throw sites stamp `code`), so the type maps to the %Error
|
|
974
|
+
// root — `err as NodeJS.ErrnoException` from an Error-typed value is a
|
|
975
|
+
// no-op cast, error-typed listener params accept it, and the `.code`
|
|
976
|
+
// read has its own lowering (errno/syscall/path stay per-member fences).
|
|
977
|
+
if (isStdlibInterface("ErrnoException"))
|
|
978
|
+
return { kind: "object", className: "%Error" };
|
|
979
|
+
// The fetch ambient slice (Response, RequestInit, AbortSignal): island-
|
|
980
|
+
// backed ambient TYPES — the values behind them live in the embedded
|
|
981
|
+
// engine (fetch's Response, AbortSignal.timeout's signal), so under
|
|
982
|
+
// --dynamic they map to island handles exactly like npm-declared types,
|
|
983
|
+
// and every operation on them rides the engine ops with validated exits
|
|
984
|
+
// at typed boundaries. Provenance, not names: a user's own `interface
|
|
985
|
+
// Response` maps as a record like any other. Without the flag they stay
|
|
986
|
+
// unmapped; the use sites report the per-site requires-dynamic story
|
|
987
|
+
// (the fetch/AbortSignal.timeout lowerings' SC2012, badType's naming
|
|
988
|
+
// for a bare value of the type).
|
|
989
|
+
// Interface OR class declarations (the shipped fallback declares
|
|
990
|
+
// interfaces; @types/node's undici-types declares Response as a class).
|
|
991
|
+
if (psym &&
|
|
992
|
+
ISLAND_AMBIENT_TYPES.includes(psym.name) &&
|
|
993
|
+
checker.declarationsOf(psym).some((d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) &&
|
|
994
|
+
ctx.isStdlibFile(d.getSourceFile()))) {
|
|
995
|
+
return ctx.dynamic ? JSVAL : null;
|
|
996
|
+
}
|
|
997
|
+
// ReadonlyMap/ReadonlySet are the SAME runtime values as Map/Set — the
|
|
998
|
+
// readonly-ness is a checker-only view (no mutating members on the
|
|
999
|
+
// interface), so both map to the identical IR kinds and the read-side
|
|
1000
|
+
// method lowerings (.has/.get/.size/iteration) dispatch unchanged.
|
|
1001
|
+
if (isStdlibInterface("Map") || isStdlibInterface("ReadonlyMap")) {
|
|
1002
|
+
const args = checker.getTypeArguments(widened);
|
|
1003
|
+
if (args.length !== 2)
|
|
1004
|
+
return null;
|
|
1005
|
+
const key = mapType(args[0], ctx);
|
|
1006
|
+
if (!key || !isSupportedMapKey(key))
|
|
1007
|
+
return null;
|
|
1008
|
+
const value = mapType(args[1], ctx);
|
|
1009
|
+
if (!value || !isSupportedMapValue(value))
|
|
1010
|
+
return null;
|
|
1011
|
+
return mapOf(key, value);
|
|
1012
|
+
}
|
|
1013
|
+
// Set<T>: Map's sibling — same provenance rule, elements fenced to Map's
|
|
1014
|
+
// KEY kinds (f64/string, SameValueZero). Anything else stays unmapped;
|
|
1015
|
+
// the `new Set` lowering names the offending element type specifically.
|
|
1016
|
+
if (isStdlibInterface("Set") || isStdlibInterface("ReadonlySet")) {
|
|
1017
|
+
const args = checker.getTypeArguments(widened);
|
|
1018
|
+
if (args.length !== 1)
|
|
1019
|
+
return null;
|
|
1020
|
+
const elem = mapType(args[0], ctx);
|
|
1021
|
+
if (!elem || !isSupportedSetElem(elem))
|
|
1022
|
+
return null;
|
|
1023
|
+
return setOf(elem);
|
|
1024
|
+
}
|
|
1025
|
+
// RegExp: a reference to the lib RegExp interface — provenance, not the
|
|
1026
|
+
// name (a user's own `interface RegExp` maps as a record). Regex values
|
|
1027
|
+
// are literals interned as immortal statics; the kind is fenced out of
|
|
1028
|
+
// array elements, union arms, and Map keys/values above/below.
|
|
1029
|
+
if (isStdlibInterface("RegExp")) {
|
|
1030
|
+
return { kind: "regex" };
|
|
1031
|
+
}
|
|
1032
|
+
// Typed arrays: references to the lib's Uint8Array/Uint32Array/
|
|
1033
|
+
// Float32Array interfaces (provenance, not names). The es2022+ lib
|
|
1034
|
+
// declares them generic over the backing buffer (`Uint8Array<ArrayBuffer>`
|
|
1035
|
+
// in error text) — the type argument is irrelevant here: no views exist,
|
|
1036
|
+
// every value owns its storage. The other TypedArray flavors stay
|
|
1037
|
+
// unmapped (the record path's index-signature check rejects them).
|
|
1038
|
+
// RegExpMatchArray (s.match's result) IS a string[] here — the honest
|
|
1039
|
+
// slice: [whole match, ...captures] (a nonparticipating capture reads
|
|
1040
|
+
// "" — SEMANTICS.md). The `.index`/`.input`/`.groups` extras fence per
|
|
1041
|
+
// member like any other unlowered array property.
|
|
1042
|
+
if (isStdlibInterface("RegExpMatchArray"))
|
|
1043
|
+
return arrayOf(STRING);
|
|
1044
|
+
// TemplateStringsArray (a tag function's first parameter): a string[] —
|
|
1045
|
+
// the cooked spans the templateStrings site value carries. The `.raw`
|
|
1046
|
+
// extra fences per member like RegExpMatchArray's `.index`/`.input`.
|
|
1047
|
+
if (isStdlibInterface("TemplateStringsArray"))
|
|
1048
|
+
return arrayOf(STRING);
|
|
1049
|
+
// RegExpExecArray (matchAll's row type): the same honest slice.
|
|
1050
|
+
if (isStdlibInterface("RegExpExecArray"))
|
|
1051
|
+
return arrayOf(STRING);
|
|
1052
|
+
// RegExpStringIterator<RegExpExecArray> (matchAll's checker result): the
|
|
1053
|
+
// VALUE-position spelling of the eager drain — the intrinsic already
|
|
1054
|
+
// materializes string[][] (lazy vs eager is unobservable: strings are
|
|
1055
|
+
// immutable and the spec clones the regex at the call), so a stored
|
|
1056
|
+
// `const urlMatches = output.matchAll(re)` types as exactly that array.
|
|
1057
|
+
if (isStdlibInterface("RegExpStringIterator"))
|
|
1058
|
+
return arrayOf(arrayOf(STRING));
|
|
1059
|
+
// NodeJS.Timeout / the fallback `Timeout` interface (setTimeout's return)
|
|
1060
|
+
// maps to F64 — the numeric timer handle. The `.ref`/`.unref`/`.hasRef`
|
|
1061
|
+
// methods lower over that handle (loop-liveness bookkeeping); `Timeout |
|
|
1062
|
+
// null` becomes `number | null`, and clearTimeout/clearInterval take the
|
|
1063
|
+
// handle. Provenance-checked like the other named interfaces.
|
|
1064
|
+
if (isStdlibInterface("Timeout"))
|
|
1065
|
+
return F64;
|
|
1066
|
+
// NodeJS.Immediate / the fallback `Immediate` (setImmediate's return):
|
|
1067
|
+
// the same numeric-handle story over the check-phase queue — its own id
|
|
1068
|
+
// space, so clearTimeout of an Immediate no-ops like Node.
|
|
1069
|
+
if (isStdlibInterface("Immediate"))
|
|
1070
|
+
return F64;
|
|
1071
|
+
if (isStdlibInterface("Uint8Array"))
|
|
1072
|
+
return bytesOf("u8");
|
|
1073
|
+
if (isStdlibInterface("Uint32Array"))
|
|
1074
|
+
return bytesOf("u32");
|
|
1075
|
+
// Int32Array: the signed 32-bit kind (element reads sign-extend, writes
|
|
1076
|
+
// ToInt32-wrap) — the Atomics.wait sleep idiom constructs one over a
|
|
1077
|
+
// SharedArrayBuffer, and the i32 semantics hold for every other use.
|
|
1078
|
+
if (isStdlibInterface("Int32Array"))
|
|
1079
|
+
return bytesOf("i32");
|
|
1080
|
+
if (isStdlibInterface("Float32Array"))
|
|
1081
|
+
return bytesOf("f32");
|
|
1082
|
+
// DataView: the ONE view kind — a u8 bytes value whose runtime
|
|
1083
|
+
// representation borrows (aliases) its owner's storage, so reads through
|
|
1084
|
+
// it see writes to the source exactly like JS. The checker keeps the
|
|
1085
|
+
// DataView and typed-array member surfaces apart; at the IR level both
|
|
1086
|
+
// are bytes<u8>.
|
|
1087
|
+
if (isStdlibInterface("DataView"))
|
|
1088
|
+
return bytesOf("u8");
|
|
1089
|
+
// Node's Buffer IS a Uint8Array subclass — ONE runtime representation
|
|
1090
|
+
// (the u8 bytes kind). Declared `interface Buffer` by the shipped
|
|
1091
|
+
// fallback and by @types/node (whose NonSharedBuffer alias resolves to
|
|
1092
|
+
// the same symbol); provenance-checked like URL.
|
|
1093
|
+
if (psym?.name === "Buffer" &&
|
|
1094
|
+
checker.declarationsOf(psym).some((d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) &&
|
|
1095
|
+
ctx.isStdlibFile(d.getSourceFile()))) {
|
|
1096
|
+
return bytesOf("u8");
|
|
1097
|
+
}
|
|
1098
|
+
// URL: the WHATWG URL instance type — declared as a global `interface
|
|
1099
|
+
// URL` (the shipped fallback, and @types/node's globals) AND as `class
|
|
1100
|
+
// URL` in @types/node's "url" module (`new URL(x)` types as the class's
|
|
1101
|
+
// instance type). Provenance, not the name: a user's own URL maps as a
|
|
1102
|
+
// record/class like any other.
|
|
1103
|
+
if (psym?.name === "URL" &&
|
|
1104
|
+
checker.declarationsOf(psym).some((d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) &&
|
|
1105
|
+
ctx.isStdlibFile(d.getSourceFile()))) {
|
|
1106
|
+
return { kind: "url" };
|
|
1107
|
+
}
|
|
1108
|
+
// URLSearchParams: the WHATWG live query view — declared as a global
|
|
1109
|
+
// interface by the shipped fallback and by @types/node (plus `class
|
|
1110
|
+
// URLSearchParams` in the "url" module). Provenance-checked like URL.
|
|
1111
|
+
if (psym?.name === "URLSearchParams" &&
|
|
1112
|
+
checker.declarationsOf(psym).some((d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) &&
|
|
1113
|
+
ctx.isStdlibFile(d.getSourceFile()))) {
|
|
1114
|
+
return { kind: "searchParams" };
|
|
1115
|
+
}
|
|
1116
|
+
// fs.Stats: @types/node's `class Stats` (module "fs") or the fallback
|
|
1117
|
+
// declarations' interface. Provenance-checked like URL.
|
|
1118
|
+
if (psym?.name === "Stats" &&
|
|
1119
|
+
checker.declarationsOf(psym).some((d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) &&
|
|
1120
|
+
ctx.isStdlibFile(d.getSourceFile()))) {
|
|
1121
|
+
return { kind: "stats" };
|
|
1122
|
+
}
|
|
1123
|
+
// child_process.SpawnSyncReturns: @types/node's generic interface (the
|
|
1124
|
+
// Buffer/string split is re-checked at the stdout/stderr READ — status
|
|
1125
|
+
// reads work either way) or the fallback declarations' plain interface.
|
|
1126
|
+
// Provenance-checked like Stats.
|
|
1127
|
+
if (psym?.name === "SpawnSyncReturns" &&
|
|
1128
|
+
checker.declarationsOf(psym).some((d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) &&
|
|
1129
|
+
ctx.isStdlibFile(d.getSourceFile()))) {
|
|
1130
|
+
return { kind: "spawnRes" };
|
|
1131
|
+
}
|
|
1132
|
+
// child_process.ChildProcess: @types/node's class or the fallback
|
|
1133
|
+
// declarations' interface. Provenance-checked like Stats.
|
|
1134
|
+
if (psym?.name === "ChildProcess" &&
|
|
1135
|
+
checker.declarationsOf(psym).some((d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) &&
|
|
1136
|
+
ctx.isStdlibFile(d.getSourceFile()))) {
|
|
1137
|
+
return { kind: "child" };
|
|
1138
|
+
}
|
|
1139
|
+
// net.Server / net.Socket: @types/node's classes or the fallback
|
|
1140
|
+
// declarations' interfaces. The NAME is ambiguous across builtin
|
|
1141
|
+
// modules (http.Server, tls.Socket share it), so the provenance check
|
|
1142
|
+
// adds the ENCLOSING AMBIENT MODULE: both @types/node and the fallback
|
|
1143
|
+
// declare these inside `declare module "net"` — a declaration outside
|
|
1144
|
+
// that module (http's Server) stays unmapped and fences at its use
|
|
1145
|
+
// site.
|
|
1146
|
+
if ((psym?.name === "Server" || psym?.name === "Socket" || psym?.name === "TLSSocket") &&
|
|
1147
|
+
checker.declarationsOf(psym).some((d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) &&
|
|
1148
|
+
ctx.isStdlibFile(d.getSourceFile()) &&
|
|
1149
|
+
(isDeclaredInAmbientModule(d, "net") ||
|
|
1150
|
+
// http.Server extends net.Server in @types/node — and tls.Server
|
|
1151
|
+
// / https.Server extend it transitively: same lowered surface
|
|
1152
|
+
// (listen/close/address/'error'), same handle kind. tls.TLSSocket
|
|
1153
|
+
// extends net.Socket the same way: post-handshake it IS a net
|
|
1154
|
+
// socket to every lowered member.
|
|
1155
|
+
(psym.name === "Server" &&
|
|
1156
|
+
(isDeclaredInAmbientModule(d, "http") ||
|
|
1157
|
+
isDeclaredInAmbientModule(d, "tls") ||
|
|
1158
|
+
isDeclaredInAmbientModule(d, "https"))) ||
|
|
1159
|
+
(psym.name === "TLSSocket" && isDeclaredInAmbientModule(d, "tls"))))) {
|
|
1160
|
+
return psym.name === "Server" ? { kind: "netServer" } : { kind: "netSocket" };
|
|
1161
|
+
}
|
|
1162
|
+
// tls.SecureContext: the opaque parsed-cert/key handle SNI callbacks
|
|
1163
|
+
// answer with (tls.createSecureContext({ cert, key }) mints it; the TLS
|
|
1164
|
+
// server's handshake consumes it). Module-checked like net.Server —
|
|
1165
|
+
// @types/node declares `interface SecureContext` inside `declare module
|
|
1166
|
+
// "tls"`, and the shipped fallback mirrors it.
|
|
1167
|
+
if (psym?.name === "SecureContext" &&
|
|
1168
|
+
checker.declarationsOf(psym).some((d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) &&
|
|
1169
|
+
ctx.isStdlibFile(d.getSourceFile()) &&
|
|
1170
|
+
isDeclaredInAmbientModule(d, "tls"))) {
|
|
1171
|
+
return { kind: "secureCtx" };
|
|
1172
|
+
}
|
|
1173
|
+
// fs.FSWatcher: the fs.watch handle (scr_watch.c). Module-checked like
|
|
1174
|
+
// net.Server — @types/node declares `interface FSWatcher extends
|
|
1175
|
+
// EventEmitter` inside `declare module "fs"`, and the shipped fallback
|
|
1176
|
+
// mirrors it.
|
|
1177
|
+
if (psym?.name === "FSWatcher" &&
|
|
1178
|
+
checker.declarationsOf(psym).some((d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) &&
|
|
1179
|
+
ctx.isStdlibFile(d.getSourceFile()) &&
|
|
1180
|
+
isDeclaredInAmbientModule(d, "fs"))) {
|
|
1181
|
+
return { kind: "fsWatcher" };
|
|
1182
|
+
}
|
|
1183
|
+
// The piped child-output stream, under BOTH spellings @types/node uses
|
|
1184
|
+
// for it: stream.Readable (ChildProcess.stdout's declared type — the
|
|
1185
|
+
// class inside `declare module "stream"`) and NodeJS.ReadableStream
|
|
1186
|
+
// (the global-namespace interface real code writes in its own
|
|
1187
|
+
// signatures — the portless prefixStream/NgrokChildProcess idiom). In
|
|
1188
|
+
// a static program every value of either type ARISES from
|
|
1189
|
+
// child.stdout/stderr — anything else producing one fences at its
|
|
1190
|
+
// producing site. The NodeJS namespace check keeps the web
|
|
1191
|
+
// ReadableStream (undici-types' whatwg stream, generic, no ambient
|
|
1192
|
+
// namespace) unmapped.
|
|
1193
|
+
// The process output streams as first-class VALUES, under the two
|
|
1194
|
+
// spellings @types/node uses: tty.WriteStream (process.stdout's own
|
|
1195
|
+
// intersection base — `WriteStream & { fd: 1 }` resolves through the
|
|
1196
|
+
// handle-refinement path below) and NodeJS.WritableStream (the
|
|
1197
|
+
// global-namespace interface real code writes in its own signatures —
|
|
1198
|
+
// prefixStream's `output` param). The representation is the raw fd
|
|
1199
|
+
// scalar; the lowered surface is write(string).
|
|
1200
|
+
if ((psym?.name === "WriteStream" &&
|
|
1201
|
+
checker.declarationsOf(psym).some((d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) &&
|
|
1202
|
+
ctx.isStdlibFile(d.getSourceFile()) &&
|
|
1203
|
+
isDeclaredInAmbientModule(d, "tty"))) ||
|
|
1204
|
+
(psym?.name === "WritableStream" &&
|
|
1205
|
+
checker.declarationsOf(psym).some((d) => ts.isInterfaceDeclaration(d) &&
|
|
1206
|
+
ctx.isStdlibFile(d.getSourceFile()) &&
|
|
1207
|
+
isDeclaredInAmbientNamespace(d, "NodeJS")))) {
|
|
1208
|
+
return PROCSTREAM_T;
|
|
1209
|
+
}
|
|
1210
|
+
// The static node:stream classes (the shipped fallback declarations —
|
|
1211
|
+
// NOT @types/node, whose stream.Readable also types child stdio; under
|
|
1212
|
+
// @types/node the childStream mapping below keeps priority and the
|
|
1213
|
+
// static stream classes stand down). Checked BEFORE the childStream
|
|
1214
|
+
// branch: the fallback's Readable must map to the runtime class, and
|
|
1215
|
+
// under the fallback child.stdout is NodeJS.ReadableStream (its own
|
|
1216
|
+
// branch below), so the two never collide.
|
|
1217
|
+
for (const [irName, rec] of RUNTIME_STREAM_CLASSES) {
|
|
1218
|
+
if (psym?.name === rec.lib &&
|
|
1219
|
+
checker.declarationsOf(psym).some((d) => ts.isClassDeclaration(d) &&
|
|
1220
|
+
ctx.isStdlibFile(d.getSourceFile()) &&
|
|
1221
|
+
!isNodeTypesPath(d.getSourceFile().fileName) &&
|
|
1222
|
+
isDeclaredInAmbientModule(d, "stream"))) {
|
|
1223
|
+
return { kind: "object", className: irName };
|
|
1224
|
+
}
|
|
1225
|
+
}
|
|
1226
|
+
if ((psym?.name === "Readable" &&
|
|
1227
|
+
checker.declarationsOf(psym).some((d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) &&
|
|
1228
|
+
ctx.isStdlibFile(d.getSourceFile()) &&
|
|
1229
|
+
isDeclaredInAmbientModule(d, "stream"))) ||
|
|
1230
|
+
(psym?.name === "ReadableStream" &&
|
|
1231
|
+
checker.declarationsOf(psym).some((d) => ts.isInterfaceDeclaration(d) &&
|
|
1232
|
+
ctx.isStdlibFile(d.getSourceFile()) &&
|
|
1233
|
+
isDeclaredInAmbientNamespace(d, "NodeJS")))) {
|
|
1234
|
+
return { kind: "childStream" };
|
|
1235
|
+
}
|
|
1236
|
+
// dgram.Socket: the same ambiguous "Socket" name disambiguated by its
|
|
1237
|
+
// enclosing ambient module — @types/node's `class Socket extends
|
|
1238
|
+
// EventEmitter` and the fallback declarations' interface both live
|
|
1239
|
+
// inside `declare module "dgram"`.
|
|
1240
|
+
if (psym?.name === "Socket" &&
|
|
1241
|
+
checker.declarationsOf(psym).some((d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) &&
|
|
1242
|
+
ctx.isStdlibFile(d.getSourceFile()) &&
|
|
1243
|
+
isDeclaredInAmbientModule(d, "dgram"))) {
|
|
1244
|
+
return { kind: "dgramSocket" };
|
|
1245
|
+
}
|
|
1246
|
+
// node:test's TestContext — the test-body parameter (`test('x', (t) =>
|
|
1247
|
+
// ...)`). @types/node's `class TestContext` and the fallback
|
|
1248
|
+
// declarations' interface both live inside `declare module "node:test"`
|
|
1249
|
+
// (isDeclaredInAmbientModule answers for both spellings).
|
|
1250
|
+
if (psym?.name === "TestContext" &&
|
|
1251
|
+
checker.declarationsOf(psym).some((d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) &&
|
|
1252
|
+
ctx.isStdlibFile(d.getSourceFile()) &&
|
|
1253
|
+
isDeclaredInAmbientModule(d, "test"))) {
|
|
1254
|
+
return { kind: "testCtx" };
|
|
1255
|
+
}
|
|
1256
|
+
// NodeJS.ErrnoException: @types/node's `interface ErrnoException extends
|
|
1257
|
+
// Error` (dns/fs callback error types). It IS an Error at runtime — map
|
|
1258
|
+
// it to the runtime %Error hierarchy like the lib Error interfaces
|
|
1259
|
+
// above; the optional errno/code/path/syscall extras fence per member
|
|
1260
|
+
// (the .stack stance). Provenance-checked like the rest.
|
|
1261
|
+
if (psym?.name === "ErrnoException" &&
|
|
1262
|
+
checker.declarationsOf(psym).some((d) => ts.isInterfaceDeclaration(d) && ctx.isStdlibFile(d.getSourceFile()))) {
|
|
1263
|
+
return { kind: "object", className: "%Error" };
|
|
1264
|
+
}
|
|
1265
|
+
// http.IncomingMessage / http.ServerResponse — module-checked like
|
|
1266
|
+
// net.Server (the names repeat nowhere today, but provenance stays the
|
|
1267
|
+
// rule).
|
|
1268
|
+
if ((psym?.name === "IncomingMessage" || psym?.name === "ServerResponse") &&
|
|
1269
|
+
checker.declarationsOf(psym).some((d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) &&
|
|
1270
|
+
ctx.isStdlibFile(d.getSourceFile()) &&
|
|
1271
|
+
isDeclaredInAmbientModule(d, "http"))) {
|
|
1272
|
+
return psym.name === "IncomingMessage" ? { kind: "httpReq" } : { kind: "httpRes" };
|
|
1273
|
+
}
|
|
1274
|
+
// http2's compatibility surface (the allowHTTP1 lowering): the secure
|
|
1275
|
+
// server IS a net server (listen/close/address/emit ride the same
|
|
1276
|
+
// handle), and Http2ServerRequest/Response ARE the http req/res kinds —
|
|
1277
|
+
// every connection the lowering accepts is HTTP/1.1, exactly the case
|
|
1278
|
+
// where Node hands the compatibility objects the http.IncomingMessage /
|
|
1279
|
+
// ServerResponse surface. h2-only members (stream, session) fence per
|
|
1280
|
+
// member at their use sites (lower-server.ts). Module-checked like the
|
|
1281
|
+
// rest.
|
|
1282
|
+
if ((psym?.name === "Http2SecureServer" ||
|
|
1283
|
+
psym?.name === "Http2ServerRequest" ||
|
|
1284
|
+
psym?.name === "Http2ServerResponse") &&
|
|
1285
|
+
checker.declarationsOf(psym).some((d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) &&
|
|
1286
|
+
ctx.isStdlibFile(d.getSourceFile()) &&
|
|
1287
|
+
isDeclaredInAmbientModule(d, "http2"))) {
|
|
1288
|
+
return psym.name === "Http2SecureServer"
|
|
1289
|
+
? { kind: "netServer" }
|
|
1290
|
+
: psym.name === "Http2ServerRequest"
|
|
1291
|
+
? { kind: "httpReq" }
|
|
1292
|
+
: { kind: "httpRes" };
|
|
1293
|
+
}
|
|
1294
|
+
// The REAL h2 surface (scr_http2.c): the h2c server is a net server
|
|
1295
|
+
// like the secure one; sessions and streams are first-class handle
|
|
1296
|
+
// kinds (client and server flavors share a kind — the runtime handle
|
|
1297
|
+
// carries the role). Module-checked like the rest.
|
|
1298
|
+
if ((psym?.name === "Http2Server" || psym?.name === "Http2Session" ||
|
|
1299
|
+
psym?.name === "ClientHttp2Session" || psym?.name === "ServerHttp2Session" ||
|
|
1300
|
+
psym?.name === "ClientHttp2Stream" || psym?.name === "ServerHttp2Stream") &&
|
|
1301
|
+
checker.declarationsOf(psym).some((d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) &&
|
|
1302
|
+
ctx.isStdlibFile(d.getSourceFile()) &&
|
|
1303
|
+
isDeclaredInAmbientModule(d, "http2"))) {
|
|
1304
|
+
return psym.name === "Http2Server"
|
|
1305
|
+
? { kind: "netServer" }
|
|
1306
|
+
: psym.name === "ClientHttp2Stream" || psym.name === "ServerHttp2Stream"
|
|
1307
|
+
? { kind: "http2Stream" }
|
|
1308
|
+
: { kind: "http2Session" };
|
|
1309
|
+
}
|
|
1310
|
+
// http.ClientRequest (http.request/http.get results) — module-checked
|
|
1311
|
+
// like IncomingMessage.
|
|
1312
|
+
if (psym?.name === "ClientRequest" &&
|
|
1313
|
+
checker.declarationsOf(psym).some((d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) &&
|
|
1314
|
+
ctx.isStdlibFile(d.getSourceFile()) &&
|
|
1315
|
+
isDeclaredInAmbientModule(d, "http"))) {
|
|
1316
|
+
return { kind: "httpClientReq" };
|
|
1317
|
+
}
|
|
1318
|
+
// NodeJS.ProcessEnv: @types/node's interface (Dict<string> plus a TZ?
|
|
1319
|
+
// member) maps to the PURE index-signature shape it structurally is —
|
|
1320
|
+
// `{ [k: string]: string | undefined }` (TZ is just another
|
|
1321
|
+
// string-or-undefined key and reads through the overflow like the rest).
|
|
1322
|
+
// What makes `process.env` as a VALUE representable (the snapshot
|
|
1323
|
+
// record) and ProcessEnv-typed parameters mappable. The fallback
|
|
1324
|
+
// declarations' anonymous env type takes the pureIndexShape path below
|
|
1325
|
+
// and interns the SAME shape.
|
|
1326
|
+
if (psym?.name === "ProcessEnv" &&
|
|
1327
|
+
checker.declarationsOf(psym).some((d) => ts.isInterfaceDeclaration(d) && ctx.isStdlibFile(d.getSourceFile()))) {
|
|
1328
|
+
const value = withUndefinedArm(STRING, ctx.unions);
|
|
1329
|
+
if (!value)
|
|
1330
|
+
return null;
|
|
1331
|
+
return { kind: "record", shapeId: ctx.shapes.intern([], false, value, []) };
|
|
1332
|
+
}
|
|
1333
|
+
// Promise<T>: a reference to the lib Promise interface. The inner type
|
|
1334
|
+
// maps recursively; promise-of-unmappable stays unmappable.
|
|
1335
|
+
if (isStdlibInterface("Promise")) {
|
|
1336
|
+
const arg = checker.getTypeArguments(widened)[0];
|
|
1337
|
+
if (!arg)
|
|
1338
|
+
return null;
|
|
1339
|
+
// Promise<never> — a throw-only async function's inference. Like sync
|
|
1340
|
+
// `never` in return position (declaredReturnType), the honest reading
|
|
1341
|
+
// is "void with a stronger guarantee": the promise only ever rejects,
|
|
1342
|
+
// so no await can observe a fulfillment value. `never` VALUES outside
|
|
1343
|
+
// a promise stay unmapped.
|
|
1344
|
+
if (arg.flags & ts.TypeFlags.Never)
|
|
1345
|
+
return { kind: "promise", inner: VOID };
|
|
1346
|
+
// Promise<any> in a STATIC build (untyped JS entries: `new
|
|
1347
|
+
// Promise((resolve) => ...)` infers any): the settled value rides the
|
|
1348
|
+
// dyn arm exactly like Promise<unknown> — every use of the awaited
|
|
1349
|
+
// value is checked-dynamic, the same honesty `unknown` gets. Dynamic
|
|
1350
|
+
// builds keep the jsval mapping below (an engine promise is one
|
|
1351
|
+
// handle).
|
|
1352
|
+
if (arg.flags & ts.TypeFlags.Any && !ctx.dynamic)
|
|
1353
|
+
return { kind: "promise", inner: DYN };
|
|
1354
|
+
const inner = mapType(arg, ctx);
|
|
1355
|
+
if (!inner)
|
|
1356
|
+
return null;
|
|
1357
|
+
return { kind: "promise", inner };
|
|
1358
|
+
}
|
|
1359
|
+
// Generator<T, TReturn, TNext> (and the lib's IterableIterator<T, ...>,
|
|
1360
|
+
// the older annotation spelling — Generator extends it): the sync
|
|
1361
|
+
// generator kind. Channel normalization keeps the runtime honest:
|
|
1362
|
+
// yield channel — T must be a real value type (a generator that could
|
|
1363
|
+
// only yield undefined has no C value form); `never`
|
|
1364
|
+
// (a generator that never yields) rides the VOID
|
|
1365
|
+
// sentinel — the suspended branch is unreachable.
|
|
1366
|
+
// return channel — void/undefined/never carry no value (VOID: the
|
|
1367
|
+
// done-value is the undefined arm); any/unknown ride
|
|
1368
|
+
// dyn; else the mapped type.
|
|
1369
|
+
// next channel — void/undefined/never mean valueless resumes (the
|
|
1370
|
+
// undefined UNIT: `.next()` sends nothing, yields are
|
|
1371
|
+
// statement-position); any/unknown ride dyn (`.next(v)`
|
|
1372
|
+
// boxes; the yield expression reads checked-dynamic);
|
|
1373
|
+
// else the mapped type (`.next(v)` requires its
|
|
1374
|
+
// argument — fenced at the call).
|
|
1375
|
+
// Mixed dyn/concrete channels stay unmapped: the shared result record's
|
|
1376
|
+
// value slot would need a dyn union arm, which does not exist.
|
|
1377
|
+
if (isStdlibInterface("Generator") || isStdlibInterface("IterableIterator")) {
|
|
1378
|
+
const args = checker.getTypeArguments(widened);
|
|
1379
|
+
const channels = genChannels(args[0], args[1], args[2], ctx);
|
|
1380
|
+
if (!channels)
|
|
1381
|
+
return null;
|
|
1382
|
+
// The result record must exist too (its union must be legal), or
|
|
1383
|
+
// `.next()` could never answer — mapped generators always resume.
|
|
1384
|
+
if (!genResultRecord(channels.yieldT, channels.retT, ctx.shapes, unions))
|
|
1385
|
+
return null;
|
|
1386
|
+
return { kind: "generator", ...channels };
|
|
1387
|
+
}
|
|
1388
|
+
// IteratorResult<T, TReturn> — the checker's type of `g.next()` (an
|
|
1389
|
+
// alias for IteratorYieldResult<T> | IteratorReturnResult<TReturn>):
|
|
1390
|
+
// maps to the SAME interned record genResume answers, so `const r =
|
|
1391
|
+
// g.next()` binds without an annotation and reads flow.
|
|
1392
|
+
{
|
|
1393
|
+
const alias = widened.getAliasSymbol();
|
|
1394
|
+
if (alias?.name === "IteratorResult" &&
|
|
1395
|
+
checker.declarationsOf(alias).some((d) => ts.isTypeAliasDeclaration(d) && ctx.isStdlibFile(d.getSourceFile()))) {
|
|
1396
|
+
const targs = widened.getAliasTypeArguments() ?? [];
|
|
1397
|
+
const channels = genChannels(targs[0], targs[1], undefined, ctx);
|
|
1398
|
+
if (!channels)
|
|
1399
|
+
return null;
|
|
1400
|
+
return genResultRecord(channels.yieldT, channels.retT, ctx.shapes, unions);
|
|
1401
|
+
}
|
|
1402
|
+
}
|
|
1403
|
+
// PromiseSettledResult<T>'s parts: the lib's PromiseFulfilledResult<T> /
|
|
1404
|
+
// PromiseRejectedResult interfaces map to the documented HONEST SUBSET
|
|
1405
|
+
// record { status: string } — `value` (T) and `reason` (any) have no
|
|
1406
|
+
// record-field representation here (void fields don't exist; a jsval
|
|
1407
|
+
// field would absorb the record), and the manual-allSettled pattern this
|
|
1408
|
+
// mapping exists for (pMap) discards them. Construction literals still
|
|
1409
|
+
// EVALUATE a dropped initializer for its effects — the awaited mapper —
|
|
1410
|
+
// via the object-literal lowering's drop path. Both interfaces intern
|
|
1411
|
+
// the SAME shape, so the settled union collapses to one record arm and
|
|
1412
|
+
// status keeps its string reads/comparisons. SEMANTICS.md 46.
|
|
1413
|
+
// net/dgram AddressInfo and dgram RemoteInfo: stdlib interfaces over
|
|
1414
|
+
// data properties, interned EXPLICITLY (the record path's provenance
|
|
1415
|
+
// fence keeps .d.ts shapes out of the structural walk — the
|
|
1416
|
+
// PromiseFulfilledResult precedent). RemoteInfo's 'IPv4' | 'IPv6'
|
|
1417
|
+
// family widens to string here; the runtime only ever answers "IPv4"
|
|
1418
|
+
// (udp4 is the one lowered socket type).
|
|
1419
|
+
// child_process.ExecFileSyncOptionsWithStringEncoding: the exec-options
|
|
1420
|
+
// interface interned explicitly on the AddressInfo pattern, so a TYPED
|
|
1421
|
+
// options value (`const commandOptions: ExecFileSyncOptions... = {...}`,
|
|
1422
|
+
// a runner function's options parameter) flows to execFileSync/execSync
|
|
1423
|
+
// as a runtime record — the windows-ca command-runner idiom. The fields
|
|
1424
|
+
// are the members the exec lowering honestly implements: encoding
|
|
1425
|
+
// (required — the "WithStringEncoding" refinement), cwd/input/timeout,
|
|
1426
|
+
// stdio (the "pipe"/"ignore" string or 3-tuple forms — a runtime array
|
|
1427
|
+
// is string[]; unsupported runtime modes throw at the call), and the
|
|
1428
|
+
// accepted-but-inert maxBuffer/windowsHide. Members outside this set
|
|
1429
|
+
// (env, killSignal, shell, uid, ...) keep the shape unmatched — their
|
|
1430
|
+
// literals fence at the shape check instead of silently dropping.
|
|
1431
|
+
if (isStdlibInterface("ExecFileSyncOptionsWithStringEncoding")) {
|
|
1432
|
+
const optStr = withUndefinedArm(STRING, ctx.unions);
|
|
1433
|
+
const optNum = withUndefinedArm(F64, ctx.unions);
|
|
1434
|
+
const optBool = withUndefinedArm(BOOL, ctx.unions);
|
|
1435
|
+
const stdioArms = [arrayOf(STRING), STRING, UNDEFINED_T].sort((a, b) => (typeKey(a) < typeKey(b) ? -1 : 1));
|
|
1436
|
+
const stdio = { kind: "union", unionId: ctx.unions.intern(stdioArms) };
|
|
1437
|
+
if (!optStr || !optNum || !optBool)
|
|
1438
|
+
return null;
|
|
1439
|
+
return {
|
|
1440
|
+
kind: "record",
|
|
1441
|
+
shapeId: ctx.shapes.intern([
|
|
1442
|
+
{ name: "cwd", type: optStr },
|
|
1443
|
+
{ name: "encoding", type: STRING },
|
|
1444
|
+
{ name: "input", type: optStr },
|
|
1445
|
+
{ name: "maxBuffer", type: optNum },
|
|
1446
|
+
{ name: "stdio", type: stdio },
|
|
1447
|
+
{ name: "timeout", type: optNum },
|
|
1448
|
+
{ name: "windowsHide", type: optBool },
|
|
1449
|
+
], false, undefined, ["encoding", "cwd", "input", "timeout", "stdio", "maxBuffer", "windowsHide"]),
|
|
1450
|
+
};
|
|
1451
|
+
}
|
|
1452
|
+
if (isStdlibInterface("AddressInfo")) {
|
|
1453
|
+
return {
|
|
1454
|
+
kind: "record",
|
|
1455
|
+
shapeId: ctx.shapes.intern([
|
|
1456
|
+
{ name: "address", type: STRING },
|
|
1457
|
+
{ name: "family", type: STRING },
|
|
1458
|
+
{ name: "port", type: F64 },
|
|
1459
|
+
], false, undefined, ["address", "family", "port"]),
|
|
1460
|
+
};
|
|
1461
|
+
}
|
|
1462
|
+
// fs.Dirent (readdirSync's withFileTypes rows): interned explicitly on
|
|
1463
|
+
// the AddressInfo pattern. The VISIBLE fields are Node's own keys (name,
|
|
1464
|
+
// parentPath — Object.keys order matches Node via declaredOrder); the
|
|
1465
|
+
// hidden %dtype field carries the entry kind (libuv's UV_DIRENT
|
|
1466
|
+
// encoding) that the isFile/isDirectory/isSymbolicLink call lowerings
|
|
1467
|
+
// read (lowerDirentMethodCall — the StringDecoder pattern, so the
|
|
1468
|
+
// %-field never surfaces through the lowered Dirent surface itself;
|
|
1469
|
+
// JSON.stringify of a Dirent would show it — the documented
|
|
1470
|
+
// internal-shape edge). @types/node declares Dirent<Name> generic over
|
|
1471
|
+
// the name's encoding — only the string instantiation maps (the
|
|
1472
|
+
// encoding:'buffer' readdir form has no lowering); the shipped fallback
|
|
1473
|
+
// declares it concrete (an interface; @types/node uses a class — both
|
|
1474
|
+
// provenance-checked, the Buffer/URL pattern).
|
|
1475
|
+
if (psym?.name === "Dirent" &&
|
|
1476
|
+
checker.declarationsOf(psym).some((d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) &&
|
|
1477
|
+
ctx.isStdlibFile(d.getSourceFile()))) {
|
|
1478
|
+
const args = checker.getTypeArguments(widened);
|
|
1479
|
+
if (args.length > 1)
|
|
1480
|
+
return null;
|
|
1481
|
+
if (args.length === 1) {
|
|
1482
|
+
const t = args[0] && mapType(args[0], ctx);
|
|
1483
|
+
if (!t || t.kind !== "string")
|
|
1484
|
+
return null;
|
|
1485
|
+
}
|
|
1486
|
+
return {
|
|
1487
|
+
kind: "record",
|
|
1488
|
+
shapeId: ctx.shapes.intern([
|
|
1489
|
+
{ name: "%dtype", type: F64 },
|
|
1490
|
+
{ name: "name", type: STRING },
|
|
1491
|
+
{ name: "parentPath", type: STRING },
|
|
1492
|
+
], false, undefined, ["name", "parentPath"]),
|
|
1493
|
+
};
|
|
1494
|
+
}
|
|
1495
|
+
// os.UserInfo (os.userInfo()'s result): a stdlib data interface interned
|
|
1496
|
+
// explicitly, the AddressInfo pattern. The shipped fallback declares it
|
|
1497
|
+
// concrete; @types/node declares UserInfo<T>, where only the string
|
|
1498
|
+
// instantiation maps (UserInfo<Buffer> — the encoding:'buffer' option —
|
|
1499
|
+
// stays unmapped; the call-site lowering fences the option form anyway).
|
|
1500
|
+
// shell is the declared `string | null` union; POSIX always answers the
|
|
1501
|
+
// string arm. Key order is Node's runtime insertion order (node_os.cc
|
|
1502
|
+
// builds uid, gid, username, homedir, shell), so Object.keys over the
|
|
1503
|
+
// record answers exactly Node.
|
|
1504
|
+
if (isStdlibInterface("UserInfo")) {
|
|
1505
|
+
const args = checker.getTypeArguments(widened);
|
|
1506
|
+
if (args.length > 1)
|
|
1507
|
+
return null;
|
|
1508
|
+
if (args.length === 1) {
|
|
1509
|
+
const t = args[0] && mapType(args[0], ctx);
|
|
1510
|
+
if (!t || t.kind !== "string")
|
|
1511
|
+
return null;
|
|
1512
|
+
}
|
|
1513
|
+
const shellArms = [NULL_T, STRING].sort((a, b) => (typeKey(a) < typeKey(b) ? -1 : 1));
|
|
1514
|
+
const shell = { kind: "union", unionId: ctx.unions.intern(shellArms) };
|
|
1515
|
+
return {
|
|
1516
|
+
kind: "record",
|
|
1517
|
+
shapeId: ctx.shapes.intern([
|
|
1518
|
+
{ name: "gid", type: F64 },
|
|
1519
|
+
{ name: "homedir", type: STRING },
|
|
1520
|
+
{ name: "shell", type: shell },
|
|
1521
|
+
{ name: "uid", type: F64 },
|
|
1522
|
+
{ name: "username", type: STRING },
|
|
1523
|
+
], false, undefined, ["uid", "gid", "username", "homedir", "shell"]),
|
|
1524
|
+
};
|
|
1525
|
+
}
|
|
1526
|
+
// crypto.X509Certificate — the Dirent-style data record: THREE visible
|
|
1527
|
+
// fields (fingerprint and the validFrom/validTo validity window, all
|
|
1528
|
+
// computed at construction by the new-expression lowering). Module-
|
|
1529
|
+
// checked like SecureContext; @types/node declares the class inside
|
|
1530
|
+
// `declare module "crypto"`, the shipped fallback mirrors it.
|
|
1531
|
+
// JSON.stringify of one shows the three fields where Node's
|
|
1532
|
+
// internal-slot object shows {} — the Dirent %dtype edge's cousin.
|
|
1533
|
+
if (psym?.name === "X509Certificate" &&
|
|
1534
|
+
checker.declarationsOf(psym).some((d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) &&
|
|
1535
|
+
ctx.isStdlibFile(d.getSourceFile()) &&
|
|
1536
|
+
isDeclaredInAmbientModule(d, "crypto"))) {
|
|
1537
|
+
return {
|
|
1538
|
+
kind: "record",
|
|
1539
|
+
shapeId: ctx.shapes.intern([
|
|
1540
|
+
{ name: "fingerprint", type: STRING },
|
|
1541
|
+
{ name: "validFrom", type: STRING },
|
|
1542
|
+
{ name: "validTo", type: STRING },
|
|
1543
|
+
], false, undefined, ["fingerprint", "validFrom", "validTo"]),
|
|
1544
|
+
};
|
|
1545
|
+
}
|
|
1546
|
+
// process.cpuUsage()/threadCpuUsage()'s {user, system} microsecond
|
|
1547
|
+
// record and resourceUsage()'s getrusage record — stdlib data
|
|
1548
|
+
// interfaces interned explicitly (the RemoteInfo pattern; declared
|
|
1549
|
+
// order is Node's own construction order, so Object.keys agrees).
|
|
1550
|
+
if (isStdlibInterface("CpuUsage")) {
|
|
1551
|
+
return {
|
|
1552
|
+
kind: "record",
|
|
1553
|
+
shapeId: ctx.shapes.intern([
|
|
1554
|
+
{ name: "system", type: F64 },
|
|
1555
|
+
{ name: "user", type: F64 },
|
|
1556
|
+
], false, undefined, ["user", "system"]),
|
|
1557
|
+
};
|
|
1558
|
+
}
|
|
1559
|
+
if (isStdlibInterface("ResourceUsage")) {
|
|
1560
|
+
const names = [
|
|
1561
|
+
"userCPUTime", "systemCPUTime", "maxRSS", "sharedMemorySize",
|
|
1562
|
+
"unsharedDataSize", "unsharedStackSize", "minorPageFault",
|
|
1563
|
+
"majorPageFault", "swappedOut", "fsRead", "fsWrite", "ipcSent",
|
|
1564
|
+
"ipcReceived", "signalsCount", "voluntaryContextSwitches",
|
|
1565
|
+
"involuntaryContextSwitches",
|
|
1566
|
+
];
|
|
1567
|
+
return {
|
|
1568
|
+
kind: "record",
|
|
1569
|
+
shapeId: ctx.shapes.intern([...names].sort().map((name) => ({ name, type: F64 })), false, undefined, names),
|
|
1570
|
+
};
|
|
1571
|
+
}
|
|
1572
|
+
if (isStdlibInterface("RemoteInfo")) {
|
|
1573
|
+
return {
|
|
1574
|
+
kind: "record",
|
|
1575
|
+
shapeId: ctx.shapes.intern([
|
|
1576
|
+
{ name: "address", type: STRING },
|
|
1577
|
+
{ name: "family", type: STRING },
|
|
1578
|
+
{ name: "port", type: F64 },
|
|
1579
|
+
{ name: "size", type: F64 },
|
|
1580
|
+
], false, undefined, ["address", "family", "port", "size"]),
|
|
1581
|
+
};
|
|
1582
|
+
}
|
|
1583
|
+
// os.NetworkInterfaceInfoIPv4/IPv6 (the arms of networkInterfaces()'s
|
|
1584
|
+
// NetworkInterfaceInfo union): stdlib data interfaces interned explicitly,
|
|
1585
|
+
// the AddressInfo pattern. The family literal types widen to string
|
|
1586
|
+
// (RemoteInfo's rule); IPv4's optional scopeid is the undefined-armed
|
|
1587
|
+
// union (holding undefined at runtime — Node omits the key), IPv6's is a
|
|
1588
|
+
// plain number. Declared order is Node's runtime insertion order
|
|
1589
|
+
// (lib/os.js builds address..cidr then appends scopeid), so Object.keys
|
|
1590
|
+
// over a row answers exactly Node.
|
|
1591
|
+
if (isStdlibInterface("NetworkInterfaceInfoIPv4") || isStdlibInterface("NetworkInterfaceInfoIPv6")) {
|
|
1592
|
+
const v6 = psym.name === "NetworkInterfaceInfoIPv6";
|
|
1593
|
+
const cidrArms = [NULL_T, STRING].sort((a, b) => (typeKey(a) < typeKey(b) ? -1 : 1));
|
|
1594
|
+
const cidr = { kind: "union", unionId: ctx.unions.intern(cidrArms) };
|
|
1595
|
+
const scopeid = v6 ? F64 : withUndefinedArm(F64, ctx.unions);
|
|
1596
|
+
if (!scopeid)
|
|
1597
|
+
return null;
|
|
1598
|
+
const fields = [
|
|
1599
|
+
{ name: "address", type: STRING },
|
|
1600
|
+
{ name: "cidr", type: cidr },
|
|
1601
|
+
{ name: "family", type: STRING },
|
|
1602
|
+
{ name: "internal", type: BOOL },
|
|
1603
|
+
{ name: "mac", type: STRING },
|
|
1604
|
+
{ name: "netmask", type: STRING },
|
|
1605
|
+
{ name: "scopeid", type: scopeid },
|
|
1606
|
+
];
|
|
1607
|
+
return {
|
|
1608
|
+
kind: "record",
|
|
1609
|
+
shapeId: ctx.shapes.intern(fields, false, undefined, [
|
|
1610
|
+
"address", "netmask", "family", "mac", "internal", "cidr", "scopeid",
|
|
1611
|
+
]),
|
|
1612
|
+
};
|
|
1613
|
+
}
|
|
1614
|
+
if (isStdlibInterface("PromiseFulfilledResult") ||
|
|
1615
|
+
isStdlibInterface("PromiseRejectedResult")) {
|
|
1616
|
+
return {
|
|
1617
|
+
kind: "record",
|
|
1618
|
+
shapeId: ctx.shapes.intern([{ name: "status", type: STRING }], false, undefined, ["status"]),
|
|
1619
|
+
};
|
|
1620
|
+
}
|
|
1621
|
+
// Function types: exactly one call signature, no generics, and every
|
|
1622
|
+
// parameter positional — the supported closure surface. OPTIONAL
|
|
1623
|
+
// parameters (`ctx?: T` — no initializer, no rest) map as their
|
|
1624
|
+
// checker-given `T | undefined` union: optionality lives entirely in the
|
|
1625
|
+
// parameter TYPE, exactly the optional-record-field rule, and a call
|
|
1626
|
+
// that omits the argument completes it with the undefined arm (the
|
|
1627
|
+
// lowerer's trailing completion). DEFAULTED parameters (`x: T = e`) map
|
|
1628
|
+
// the same way — the completed-signature contract: the TYPE spells
|
|
1629
|
+
// `T | undefined`, the default's VALUE lives in the closure body's own
|
|
1630
|
+
// prologue (declareParams), and the undefined arm is what triggers it.
|
|
1631
|
+
// Rest parameters keep the fence (a rest signature is never spellable
|
|
1632
|
+
// as one completed arity).
|
|
1633
|
+
// The chalk-style FUNCTION-WITH-PROPERTIES hybrid: an intersection of
|
|
1634
|
+
// exactly one callable part with plain data-property parts (`F & { bold:
|
|
1635
|
+
// F }` — Object.assign(fn, {...})'s type) maps to a RECORD carrying the
|
|
1636
|
+
// callable in a reserved `%call` field ('%' cannot appear in a TS
|
|
1637
|
+
// property name, so no user field collides). Calls through the value
|
|
1638
|
+
// read the slot (lowerRecordFieldCall / the call-value path), extraction
|
|
1639
|
+
// into a plain F slot reads it too (coerceToExpected), and Object.assign
|
|
1640
|
+
// constructs the record. Intersections that don't fit fall through to
|
|
1641
|
+
// the plain-func mapping below (historic behavior: the properties drop
|
|
1642
|
+
// from the type).
|
|
1643
|
+
if (widened.isIntersectionType() && checker.getConstructSignatures(widened).length === 0) {
|
|
1644
|
+
const hybrid = mapHybridCallableIntersection(widened, ctx);
|
|
1645
|
+
if (hybrid)
|
|
1646
|
+
return hybrid;
|
|
1647
|
+
}
|
|
1648
|
+
const callSigs = checker.getCallSignatures(widened);
|
|
1649
|
+
if (callSigs.length === 1) {
|
|
1650
|
+
const sig = callSigs[0];
|
|
1651
|
+
if (sig.getTypeParameters().length)
|
|
1652
|
+
return null;
|
|
1653
|
+
// A SYNTHESIZED rest param (tsc's JS inference for a function body
|
|
1654
|
+
// reading `arguments` — `(...args: any[]) => any` whose args symbol
|
|
1655
|
+
// has no declaration): the dotDotDot check below can't see it, and a
|
|
1656
|
+
// fixed-arity func mapping would LIE about the value's calling
|
|
1657
|
+
// convention. Detected by the param-count mismatch against the
|
|
1658
|
+
// signature's own declaration; unmappable like spelled rest.
|
|
1659
|
+
{
|
|
1660
|
+
const sigDecl = checker.signatureDeclaration(sig);
|
|
1661
|
+
const declParams = sigDecl !== undefined && ts.isFunctionLike(sigDecl) ? sigDecl.parameters : undefined;
|
|
1662
|
+
if (declParams !== undefined && declParams.length !== sig.getParameters().length) {
|
|
1663
|
+
return null;
|
|
1664
|
+
}
|
|
1665
|
+
// tsgo never SYNTHESIZES that rest param into the inferred signature
|
|
1666
|
+
// (5.9.3 did — the count mismatch above was the whole detector
|
|
1667
|
+
// there), so the declaration's own body answers directly.
|
|
1668
|
+
if (sigDecl !== undefined && ts.isFunctionLike(sigDecl) && bodyReadsArgumentsLocal(sigDecl)) {
|
|
1669
|
+
return null;
|
|
1670
|
+
}
|
|
1671
|
+
}
|
|
1672
|
+
const params = [];
|
|
1673
|
+
for (const p of sig.getParameters()) {
|
|
1674
|
+
const decl = checker.valueDeclarationOf(p);
|
|
1675
|
+
if (decl && ts.isParameter(decl) && decl.dotDotDotToken) {
|
|
1676
|
+
return null;
|
|
1677
|
+
}
|
|
1678
|
+
const optional = decl !== undefined &&
|
|
1679
|
+
ts.isParameter(decl) &&
|
|
1680
|
+
(decl.questionToken !== undefined || decl.initializer !== undefined);
|
|
1681
|
+
let pt = mapType(checker.getTypeOfSymbol(p), ctx);
|
|
1682
|
+
// Belt and braces for non-strict type worlds: an optional param's
|
|
1683
|
+
// ABI slot is always the undefined-armed union (strictNullChecks
|
|
1684
|
+
// already spells it that way; arm it here if the world didn't).
|
|
1685
|
+
if (optional && pt && pt.kind !== "void" && pt.kind !== "jsval") {
|
|
1686
|
+
const armed = withUndefinedArm(pt, ctx.unions);
|
|
1687
|
+
if (!armed)
|
|
1688
|
+
return null;
|
|
1689
|
+
pt = armed;
|
|
1690
|
+
}
|
|
1691
|
+
if (!pt)
|
|
1692
|
+
return null;
|
|
1693
|
+
// `(value: void) => void` (Promise<void>'s resolve) is callable with
|
|
1694
|
+
// no arguments — a void param is dropped, not a mapping failure.
|
|
1695
|
+
if (pt.kind === "void")
|
|
1696
|
+
continue;
|
|
1697
|
+
params.push(pt);
|
|
1698
|
+
}
|
|
1699
|
+
const retT = checker.getReturnTypeOfSignature(sig);
|
|
1700
|
+
// `() => never` (a throw-only lambda's inferred type) is assignable to
|
|
1701
|
+
// `() => void` and its calls never produce a value — map the return
|
|
1702
|
+
// like declaredReturnType does for declarations.
|
|
1703
|
+
const ret = retT.flags & ts.TypeFlags.Never ? VOID : mapType(retT, ctx);
|
|
1704
|
+
if (!ret)
|
|
1705
|
+
return null;
|
|
1706
|
+
return funcOf(params, ret);
|
|
1707
|
+
}
|
|
1708
|
+
// Records: object types whose members are all data properties (shorthand
|
|
1709
|
+
// methods in type position count — they're func-typed fields) with
|
|
1710
|
+
// mappable types; no call/construct signatures, no index signatures, not
|
|
1711
|
+
// a tuple, and declared in USER code (lib interfaces like
|
|
1712
|
+
// Function/Object/Iterable stay unmapped — the standard-library types are
|
|
1713
|
+
// a type world, not data shapes). Optional properties (`a?: T`) need no special handling: under
|
|
1714
|
+
// strictNullChecks the checker types them `T | undefined`, so the field
|
|
1715
|
+
// maps to an undefined-armed union — optionality lives entirely in the
|
|
1716
|
+
// field TYPE, which is also what keeps `{a: string}` and `{a?: string}`
|
|
1717
|
+
// distinct interned shapes (and makes `{a?: string}` and
|
|
1718
|
+
// `{a: string | undefined}` deliberately the SAME shape, matching tsc's
|
|
1719
|
+
// mutual assignability without exactOptionalPropertyTypes). Two
|
|
1720
|
+
// structurally identical types intern to one shapeId.
|
|
1721
|
+
//
|
|
1722
|
+
// Mapped-type results (`Partial<Config>`, `Record<"a" | "b", number>`,
|
|
1723
|
+
// Pick/Omit spellings) and intersections of object types resolve through
|
|
1724
|
+
// the SAME path: the member walk below asks the checker for the resolved
|
|
1725
|
+
// property list, so a utility type interns the identical shape its
|
|
1726
|
+
// longhand spelling would. Intersections are a distinct TypeFlag, hence
|
|
1727
|
+
// the widened condition.
|
|
1728
|
+
// A USER-declared child-process-shaped interface (the ngrok
|
|
1729
|
+
// NgrokChildProcess idiom — `spawn(...) as NgrokChildProcess`, declared
|
|
1730
|
+
// so tests can hand in mocks): every member is named on the lowered
|
|
1731
|
+
// ChildProcess surface AND at least two are the handle-defining ones
|
|
1732
|
+
// (kill/on/stdout/stderr/unref/exitCode — a data record like
|
|
1733
|
+
// `{ pid: number }` never qualifies). The TYPE maps to the child
|
|
1734
|
+
// handle: in a compiled program the only VALUE producer is spawn
|
|
1735
|
+
// itself (a mock object literal in this slot fences at its
|
|
1736
|
+
// construction site), and every member the interface may declare is
|
|
1737
|
+
// exactly a lowered child member, so uses typecheck against the
|
|
1738
|
+
// interface and lower against the handle.
|
|
1739
|
+
if (flags & ts.TypeFlags.Object &&
|
|
1740
|
+
callSigs.length === 0 &&
|
|
1741
|
+
psym !== undefined &&
|
|
1742
|
+
checker.declarationsOf(psym).length > 0 &&
|
|
1743
|
+
checker.declarationsOf(psym).every((d) => ts.isInterfaceDeclaration(d) && !ctx.isStdlibFile(d.getSourceFile()))) {
|
|
1744
|
+
const CHILD_SURFACE = new Set([
|
|
1745
|
+
"pid", "exitCode", "killed", "kill", "on", "once", "off",
|
|
1746
|
+
"removeListener", "unref", "ref", "stdout", "stderr",
|
|
1747
|
+
]);
|
|
1748
|
+
const CHILD_CORE = new Set(["kill", "on", "stdout", "stderr", "unref", "exitCode"]);
|
|
1749
|
+
const props = checker.getPropertiesOfType(widened);
|
|
1750
|
+
let core = 0;
|
|
1751
|
+
const childShaped = props.length > 0 &&
|
|
1752
|
+
props.every((p) => {
|
|
1753
|
+
if (!CHILD_SURFACE.has(p.name))
|
|
1754
|
+
return false;
|
|
1755
|
+
if (CHILD_CORE.has(p.name))
|
|
1756
|
+
core++;
|
|
1757
|
+
return true;
|
|
1758
|
+
});
|
|
1759
|
+
if (childShaped && core >= 2)
|
|
1760
|
+
return CHILD_T;
|
|
1761
|
+
}
|
|
1762
|
+
if (flags & (ts.TypeFlags.Object | ts.TypeFlags.Intersection) && callSigs.length === 0) {
|
|
1763
|
+
const viaAlias = mapGenericUtilityAlias(widened, ctx);
|
|
1764
|
+
if (viaAlias)
|
|
1765
|
+
return viaAlias;
|
|
1766
|
+
const record = mapRecordType(widened, ctx);
|
|
1767
|
+
if (record)
|
|
1768
|
+
return record;
|
|
1769
|
+
}
|
|
1770
|
+
// Unions. `boolean` is internally `true | false`, and narrowing produces
|
|
1771
|
+
// literal unions like `"a" | "b"` — parts that map to the SAME IR type
|
|
1772
|
+
// collapse (deduplicated by typeKey), so a single surviving arm is just
|
|
1773
|
+
// that type. Two or more distinct arms become a TAGGED union: the
|
|
1774
|
+
// typeKey-sorted arm list is interned (its identity), and an arm's index
|
|
1775
|
+
// in that list is its runtime tag. `undefined` and `null` PARTS become
|
|
1776
|
+
// the unit arms undefinedT/nullT — strictNullChecks spells optionality
|
|
1777
|
+
// as exactly these unions (`string | undefined`, `number | null`), and
|
|
1778
|
+
// union membership is the only position where the units are
|
|
1779
|
+
// representable. Deliberately unmapped: any other unmappable part
|
|
1780
|
+
// (poisons the union), void parts, func arms (closure identity vs tag
|
|
1781
|
+
// interplay, later), and recursive unions (`type T = A | { next: T }` —
|
|
1782
|
+
// an RC cycle anyway).
|
|
1783
|
+
if (widened.isUnionType()) {
|
|
1784
|
+
if (unions.inProgress.has(widened))
|
|
1785
|
+
return null;
|
|
1786
|
+
unions.inProgress.add(widened);
|
|
1787
|
+
try {
|
|
1788
|
+
const byKey = new Map();
|
|
1789
|
+
for (const part of widened.getTypes()) {
|
|
1790
|
+
// A `void` PART is inhabited only by undefined (`Promise<void> |
|
|
1791
|
+
// void` return types, `string | void`): it becomes the undefinedT
|
|
1792
|
+
// unit arm, exactly like an undefined part — the value either
|
|
1793
|
+
// exists or is undefined, and the union tag is the test. Standalone
|
|
1794
|
+
// void stays VOID (the return-position mapping above).
|
|
1795
|
+
if (part.flags & (ts.TypeFlags.Undefined | ts.TypeFlags.Void)) {
|
|
1796
|
+
byKey.set(typeKey(UNDEFINED_T), UNDEFINED_T);
|
|
1797
|
+
continue;
|
|
1798
|
+
}
|
|
1799
|
+
if (part.flags & ts.TypeFlags.Null) {
|
|
1800
|
+
byKey.set(typeKey(NULL_T), NULL_T);
|
|
1801
|
+
continue;
|
|
1802
|
+
}
|
|
1803
|
+
const mapped = mapType(part, ctx);
|
|
1804
|
+
// A jsval PART absorbs the union: `GeneratedFile | undefined` (a
|
|
1805
|
+
// package-declared arm) is one island handle — the engine's
|
|
1806
|
+
// undefined/null ARE handle values, and any data sibling is
|
|
1807
|
+
// representable in the engine too, so the handle is the one
|
|
1808
|
+
// runtime representation. Exactly tsc's own collapse for `any | T`
|
|
1809
|
+
// (which never even reaches here as a union). Sibling arms that
|
|
1810
|
+
// already mapped don't matter — and ones that would NOT map on
|
|
1811
|
+
// their own don't block the absorb either: a STATIC value of such
|
|
1812
|
+
// an arm flowing in still fences per-site at the marshal
|
|
1813
|
+
// (jsvalIn's boundary message).
|
|
1814
|
+
if (mapped?.kind === "jsval")
|
|
1815
|
+
return JSVAL;
|
|
1816
|
+
if (!mapped) {
|
|
1817
|
+
// Before failing the whole union, let a LATER jsval part absorb.
|
|
1818
|
+
for (const rest of widened.getTypes()) {
|
|
1819
|
+
if (mapType(rest, ctx)?.kind === "jsval")
|
|
1820
|
+
return JSVAL;
|
|
1821
|
+
}
|
|
1822
|
+
return null;
|
|
1823
|
+
}
|
|
1824
|
+
byKey.set(typeKey(mapped), mapped);
|
|
1825
|
+
}
|
|
1826
|
+
const arms = [...byKey.values()];
|
|
1827
|
+
// A single surviving UNIT arm cannot stand alone (degenerate — the
|
|
1828
|
+
// checker collapsed everything else away); anything else single is
|
|
1829
|
+
// just that type.
|
|
1830
|
+
if (arms.length === 1 && isUnitType(arms[0]))
|
|
1831
|
+
return null;
|
|
1832
|
+
if (arms.length === 1)
|
|
1833
|
+
return arms[0];
|
|
1834
|
+
if (arms.some((a) => a.kind === "void" || a.kind === "union" ||
|
|
1835
|
+
// Map/Set and regex arms stay out (like func against data
|
|
1836
|
+
// arms: no narrowing test — no discriminant fields on them).
|
|
1837
|
+
a.kind === "map" || a.kind === "set" || a.kind === "regex" || a.kind === "dyn" ||
|
|
1838
|
+
// Generator arms follow the map/set rule: no narrowing test.
|
|
1839
|
+
a.kind === "generator" ||
|
|
1840
|
+
// A func arm has no narrowing test against sibling DATA arms —
|
|
1841
|
+
// but against pure unit companions the unit TAG tests are the
|
|
1842
|
+
// narrowing (cb !== null, cb ?? f, cb?.()), so the nullable-
|
|
1843
|
+
// callback shape `(() => void) | null | undefined` maps.
|
|
1844
|
+
(a.kind === "func" && !arms.every((b) => b === a || isUnitType(b))) ||
|
|
1845
|
+
// Promise arms follow the func rule (typeof gives no test
|
|
1846
|
+
// against sibling data arms): only the promise-or-absent shape
|
|
1847
|
+
// maps — `Promise<T> | undefined`, and `Promise<T> | void`
|
|
1848
|
+
// return types whose void part became the undefined arm above.
|
|
1849
|
+
(a.kind === "promise" && !arms.every((b) => b === a || isUnitType(b))))) {
|
|
1850
|
+
return null;
|
|
1851
|
+
}
|
|
1852
|
+
arms.sort((a, b) => (typeKey(a) < typeKey(b) ? -1 : 1));
|
|
1853
|
+
return { kind: "union", unionId: unions.intern(arms) };
|
|
1854
|
+
}
|
|
1855
|
+
finally {
|
|
1856
|
+
unions.inProgress.delete(widened);
|
|
1857
|
+
}
|
|
1858
|
+
}
|
|
1859
|
+
return null;
|
|
1860
|
+
}
|
|
1861
|
+
/** The narrowed-type-parameter recognizer behind mapType's early return:
|
|
1862
|
+
* a union/intersection over exactly ONE type parameter whose companions
|
|
1863
|
+
* are all `{}`-empty shapes or the null/undefined units. Returns the
|
|
1864
|
+
* bound type filtered to the arms the companions allow, null when the
|
|
1865
|
+
* pattern matches but cannot map (unbound parameter, nothing left), and
|
|
1866
|
+
* undefined when the type is not this pattern at all (the caller falls
|
|
1867
|
+
* through to the ordinary mapping). */
|
|
1868
|
+
function mapNarrowedTypeParam(type, ctx) {
|
|
1869
|
+
const { checker, unions, resolveTypeParam } = ctx;
|
|
1870
|
+
if (!resolveTypeParam)
|
|
1871
|
+
return undefined;
|
|
1872
|
+
const isEmptyShape = (t) => (t.flags & ts.TypeFlags.Object) !== 0 &&
|
|
1873
|
+
checker.getPropertiesOfType(t).length === 0 &&
|
|
1874
|
+
checker.getCallSignatures(t).length === 0 &&
|
|
1875
|
+
checker.getConstructSignatures(t).length === 0 &&
|
|
1876
|
+
checker.getIndexInfosOfType(t).length === 0;
|
|
1877
|
+
let tp = null;
|
|
1878
|
+
let allowNonUnit = false;
|
|
1879
|
+
let allowUndefined = false;
|
|
1880
|
+
let allowNull = false;
|
|
1881
|
+
const visitCompanion = (c) => {
|
|
1882
|
+
if (isEmptyShape(c))
|
|
1883
|
+
return (allowNonUnit = true);
|
|
1884
|
+
if (c.flags & (ts.TypeFlags.Undefined | ts.TypeFlags.Void))
|
|
1885
|
+
return (allowUndefined = true);
|
|
1886
|
+
if (c.flags & ts.TypeFlags.Null)
|
|
1887
|
+
return (allowNull = true);
|
|
1888
|
+
// A companion UNION (`T & ({} | null)`): each part is an allowance.
|
|
1889
|
+
if (c.isUnionType())
|
|
1890
|
+
return c.getTypes().every(visitCompanion);
|
|
1891
|
+
return false;
|
|
1892
|
+
};
|
|
1893
|
+
const visitPart = (part) => {
|
|
1894
|
+
if (!part.isIntersectionType())
|
|
1895
|
+
return false;
|
|
1896
|
+
for (const p of part.getTypes()) {
|
|
1897
|
+
if (p.flags & ts.TypeFlags.TypeParameter) {
|
|
1898
|
+
if (tp && tp !== p)
|
|
1899
|
+
return false; // two different parameters: not this pattern
|
|
1900
|
+
tp = p;
|
|
1901
|
+
continue;
|
|
1902
|
+
}
|
|
1903
|
+
if (!visitCompanion(p))
|
|
1904
|
+
return false;
|
|
1905
|
+
}
|
|
1906
|
+
return true;
|
|
1907
|
+
};
|
|
1908
|
+
const parts = type.isUnionType() ? type.getTypes() : [type];
|
|
1909
|
+
if (!parts.every(visitPart) || !tp)
|
|
1910
|
+
return undefined;
|
|
1911
|
+
const bound = resolveTypeParam(tp);
|
|
1912
|
+
if (!bound)
|
|
1913
|
+
return null;
|
|
1914
|
+
if (bound.kind !== "union")
|
|
1915
|
+
return allowNonUnit ? bound : null;
|
|
1916
|
+
const def = unions.get(bound.unionId);
|
|
1917
|
+
if (!def)
|
|
1918
|
+
return null;
|
|
1919
|
+
const arms = def.arms.filter((a) => a.kind === "undefinedT" ? allowUndefined : a.kind === "nullT" ? allowNull : allowNonUnit);
|
|
1920
|
+
if (arms.length === 0)
|
|
1921
|
+
return null;
|
|
1922
|
+
if (arms.length === 1)
|
|
1923
|
+
return isUnitType(arms[0]) ? null : arms[0];
|
|
1924
|
+
// Filtering preserves the canonical (typeKey-sorted) arm order.
|
|
1925
|
+
return { kind: "union", unionId: unions.intern(arms) };
|
|
1926
|
+
}
|
|
1927
|
+
/** `Partial<T>` / `Readonly<T>` where T is a STILL-GENERIC type parameter —
|
|
1928
|
+
* the one mapped-type spelling a monomorphized body can contain that the
|
|
1929
|
+
* checker cannot resolve for us (`keyof T` is unknown inside the body, so
|
|
1930
|
+
* getPropertiesOfType returns nothing). The lowerer's binding can: Readonly
|
|
1931
|
+
* maps exactly like T (writes are tsc errors, trusted), and Partial arms
|
|
1932
|
+
* every field of T's record with undefined — precisely what the longhand
|
|
1933
|
+
* optional spelling produces, so the result interns to the same shape as
|
|
1934
|
+
* the concrete `Partial<Config>` at the call boundary. Other utility
|
|
1935
|
+
* aliases (Pick, Omit, Record, Required) need literal KEYS or the
|
|
1936
|
+
* optional/required distinction, which IR types deliberately do not carry
|
|
1937
|
+
* (`{a?: string}` and `{a: string | undefined}` are ONE shape) — those stay
|
|
1938
|
+
* unmapped inside generic bodies; their concrete uses resolve structurally
|
|
1939
|
+
* through mapRecordType. */
|
|
1940
|
+
function mapGenericUtilityAlias(widened, ctx) {
|
|
1941
|
+
const { resolveTypeParam, shapes, unions } = ctx;
|
|
1942
|
+
if (!resolveTypeParam)
|
|
1943
|
+
return null;
|
|
1944
|
+
const alias = widened.getAliasSymbol();
|
|
1945
|
+
if (!alias || (alias.name !== "Partial" && alias.name !== "Readonly"))
|
|
1946
|
+
return null;
|
|
1947
|
+
const aliasArgs = widened.getAliasTypeArguments();
|
|
1948
|
+
const arg = aliasArgs[0];
|
|
1949
|
+
if (aliasArgs.length !== 1 || !arg)
|
|
1950
|
+
return null;
|
|
1951
|
+
if (!(arg.flags & ts.TypeFlags.TypeParameter))
|
|
1952
|
+
return null;
|
|
1953
|
+
// Provenance: the STANDARD LIBRARY's alias, not a user's shadowing one.
|
|
1954
|
+
const isLibAlias = ctx.checker.declarationsOf(alias).some((d) => ts.isTypeAliasDeclaration(d) && ctx.isStdlibFile(d.getSourceFile()));
|
|
1955
|
+
if (!isLibAlias)
|
|
1956
|
+
return null;
|
|
1957
|
+
const bound = resolveTypeParam(arg);
|
|
1958
|
+
if (!bound || bound.kind !== "record")
|
|
1959
|
+
return null;
|
|
1960
|
+
if (alias.name === "Readonly")
|
|
1961
|
+
return bound;
|
|
1962
|
+
const shape = shapes.get(bound.shapeId);
|
|
1963
|
+
if (!shape)
|
|
1964
|
+
return null;
|
|
1965
|
+
// Partial over an index-signature shape would need `V | undefined`
|
|
1966
|
+
// overflow arming; no generic body needs it yet — stay unmapped.
|
|
1967
|
+
if (shape.indexValue)
|
|
1968
|
+
return null;
|
|
1969
|
+
const fields = [];
|
|
1970
|
+
for (const f of shape.fields) {
|
|
1971
|
+
const armed = withUndefinedArm(f.type, unions);
|
|
1972
|
+
if (!armed)
|
|
1973
|
+
return null;
|
|
1974
|
+
fields.push({ name: f.name, type: armed });
|
|
1975
|
+
}
|
|
1976
|
+
// Fields inherit the source shape's canonical (name-sorted) order — and
|
|
1977
|
+
// its declaration order (mapped types preserve property order in TS).
|
|
1978
|
+
return { kind: "record", shapeId: shapes.intern(fields, false, undefined, shape.declaredOrder) };
|
|
1979
|
+
}
|
|
1980
|
+
/** The UNIT-ONLY slot type: the interned `null | undefined` union, the one
|
|
1981
|
+
* representation the IR already has for values that are nothing but units.
|
|
1982
|
+
* Bindings and fields whose CHECKER type is a bare unit (`null`,
|
|
1983
|
+
* `undefined`, `void` in value position, or unions of only those) ride it:
|
|
1984
|
+
* the runtime value is always one of the two interned unit instances, the
|
|
1985
|
+
* tag is the narrowing test (`x === null`, `typeof x === "undefined"`),
|
|
1986
|
+
* and JSON keeps Node's split (null serializes, undefined omits). The type
|
|
1987
|
+
* admits an arm the checker may not spell (a `null`-typed slot gets an
|
|
1988
|
+
* undefined arm too) — the representation is coarser than the TS type,
|
|
1989
|
+
* exactly like literal widening, and tsc has already rejected every write
|
|
1990
|
+
* that could inhabit the extra arm. */
|
|
1991
|
+
export function unitOnlyUnion(unions) {
|
|
1992
|
+
const arms = [NULL_T, UNDEFINED_T];
|
|
1993
|
+
arms.sort((a, b) => (typeKey(a) < typeKey(b) ? -1 : 1));
|
|
1994
|
+
return { kind: "union", unionId: unions.intern(arms) };
|
|
1995
|
+
}
|
|
1996
|
+
/** The IteratorResult record of a generator's value channels: `{ done:
|
|
1997
|
+
* boolean, value: V }` where V is dyn (an any/unknown channel) or the
|
|
1998
|
+
* canonical union of the yield arms, the return arms (when the return
|
|
1999
|
+
* channel carries a value), and `undefined` (an exhausted `.next()`
|
|
2000
|
+
* answers undefined). ONE shape per channel pair — `g.next()`'s lowering,
|
|
2001
|
+
* `.return()`, `.throw()`, the for-of desugar, and mapType's
|
|
2002
|
+
* IteratorResult alias mapping all intern through here, so reads agree.
|
|
2003
|
+
* Null when the combined union would be illegal (a func/set arm beside
|
|
2004
|
+
* data arms, a map/regex arm — kinds with no narrowing test): such
|
|
2005
|
+
* generators stay unmapped. */
|
|
2006
|
+
export function genResultRecord(yieldT, retT, shapes, unions) {
|
|
2007
|
+
let valueT;
|
|
2008
|
+
if (yieldT.kind === "dyn" || retT.kind === "dyn") {
|
|
2009
|
+
valueT = DYN;
|
|
2010
|
+
}
|
|
2011
|
+
else {
|
|
2012
|
+
const byKey = new Map();
|
|
2013
|
+
const add = (t) => {
|
|
2014
|
+
if (t.kind === "void")
|
|
2015
|
+
return true; // no value on this channel
|
|
2016
|
+
if (t.kind === "union") {
|
|
2017
|
+
for (const a of unions.get(t.unionId)?.arms ?? [])
|
|
2018
|
+
byKey.set(typeKey(a), a);
|
|
2019
|
+
return true;
|
|
2020
|
+
}
|
|
2021
|
+
if (t.kind === "map" || t.kind === "regex" || t.kind === "jsval" || t.kind === "generator") {
|
|
2022
|
+
return false; // no legal union arm exists for these kinds
|
|
2023
|
+
}
|
|
2024
|
+
byKey.set(typeKey(t), t);
|
|
2025
|
+
return true;
|
|
2026
|
+
};
|
|
2027
|
+
if (!add(yieldT) || !add(retT))
|
|
2028
|
+
return null;
|
|
2029
|
+
byKey.set(typeKey(UNDEFINED_T), UNDEFINED_T);
|
|
2030
|
+
const arms = [...byKey.values()];
|
|
2031
|
+
// func/set arms are legal only beside unit arms (no narrowing test
|
|
2032
|
+
// against data siblings — the union rule).
|
|
2033
|
+
if (arms.some((a) => (a.kind === "func" || a.kind === "set") && !arms.every((b) => b === a || isUnitType(b)))) {
|
|
2034
|
+
return null;
|
|
2035
|
+
}
|
|
2036
|
+
if (arms.every(isUnitType) && arms.length < 2) {
|
|
2037
|
+
// The degenerate no-value generator (never yields, void return):
|
|
2038
|
+
// the slot still needs a readable undefined — the unit-only union.
|
|
2039
|
+
valueT = unitOnlyUnion(unions);
|
|
2040
|
+
}
|
|
2041
|
+
else {
|
|
2042
|
+
arms.sort((a, b) => (typeKey(a) < typeKey(b) ? -1 : 1));
|
|
2043
|
+
valueT = { kind: "union", unionId: unions.intern(arms) };
|
|
2044
|
+
}
|
|
2045
|
+
}
|
|
2046
|
+
return {
|
|
2047
|
+
kind: "record",
|
|
2048
|
+
shapeId: shapes.intern([
|
|
2049
|
+
{ name: "done", type: BOOL },
|
|
2050
|
+
{ name: "value", type: valueT },
|
|
2051
|
+
]),
|
|
2052
|
+
};
|
|
2053
|
+
}
|
|
2054
|
+
/** True when a checker type is inhabited ONLY by the unit values —
|
|
2055
|
+
* undefined (or `void`, whose runtime inhabitant is undefined) and null,
|
|
2056
|
+
* or a union of just those. The positions that today fence VOID (record
|
|
2057
|
+
* fields, tuple/array elements, variable slots) substitute unitOnlyUnion
|
|
2058
|
+
* for these; RETURN positions keep the VOID mapping (a void return is not
|
|
2059
|
+
* a value). */
|
|
2060
|
+
export function isUnitOnlyTsType(t) {
|
|
2061
|
+
const UNIT = ts.TypeFlags.Undefined | ts.TypeFlags.Void | ts.TypeFlags.Null;
|
|
2062
|
+
const parts = t.isUnionType() ? t.getTypes() : [t];
|
|
2063
|
+
return parts.every((p) => (p.flags & UNIT) !== 0);
|
|
2064
|
+
}
|
|
2065
|
+
/** IR-level `t | undefined`, canonicalized and fenced exactly like the
|
|
2066
|
+
* ts-union branch of mapType (typeKey-sorted arms, deduplicated; map/
|
|
2067
|
+
* regex/dyn/void arms unrepresentable; a func arm IS representable — the
|
|
2068
|
+
* result is exactly the nullable-callback shape mapType's union branch
|
|
2069
|
+
* admits, `(() => void) | undefined`) so the interned union is IDENTICAL
|
|
2070
|
+
* to what mapping the checker's own `T | undefined` produces. */
|
|
2071
|
+
export function withUndefinedArm(t, unions) {
|
|
2072
|
+
if (t.kind === "union") {
|
|
2073
|
+
const def = unions.get(t.unionId);
|
|
2074
|
+
if (!def)
|
|
2075
|
+
return null;
|
|
2076
|
+
if (def.arms.some((a) => a.kind === "undefinedT"))
|
|
2077
|
+
return t;
|
|
2078
|
+
const arms = [...def.arms, UNDEFINED_T];
|
|
2079
|
+
arms.sort((a, b) => (typeKey(a) < typeKey(b) ? -1 : 1));
|
|
2080
|
+
return { kind: "union", unionId: unions.intern(arms) };
|
|
2081
|
+
}
|
|
2082
|
+
if (t.kind === "void" || t.kind === "map" ||
|
|
2083
|
+
t.kind === "regex" || t.kind === "dyn" ||
|
|
2084
|
+
// A bare unit field type cannot occur (units live only inside unions),
|
|
2085
|
+
// but guard against constructing a single-arm union from one.
|
|
2086
|
+
isUnitType(t)) {
|
|
2087
|
+
return null;
|
|
2088
|
+
}
|
|
2089
|
+
const arms = [t, UNDEFINED_T];
|
|
2090
|
+
arms.sort((a, b) => (typeKey(a) < typeKey(b) ? -1 : 1));
|
|
2091
|
+
return { kind: "union", unionId: unions.intern(arms) };
|
|
2092
|
+
}
|
|
2093
|
+
/** The chalk-shape recognizer behind mapType's hybrid branch: an
|
|
2094
|
+
* intersection with ONE call signature whose callable part maps to a func
|
|
2095
|
+
* and whose other parts are plain object refinements over data properties
|
|
2096
|
+
* (no construct signatures, no class identity, no index signatures). The
|
|
2097
|
+
* result interns a record of the data properties plus the reserved
|
|
2098
|
+
* `%call` slot holding the callable. `declaredOrder` lists only the DATA
|
|
2099
|
+
* properties — Object.keys over the hybrid answers the enumerable
|
|
2100
|
+
* assigned keys, which is also Node's answer for Object.assign(fn, obj)
|
|
2101
|
+
* (a function's own length/name are non-enumerable). Null whenever the
|
|
2102
|
+
* shape doesn't fit — the caller falls through to the plain-func mapping. */
|
|
2103
|
+
function mapHybridCallableIntersection(widened, ctx) {
|
|
2104
|
+
const { checker, shapes } = ctx;
|
|
2105
|
+
if (!widened.isIntersectionType())
|
|
2106
|
+
return null;
|
|
2107
|
+
if (checker.getCallSignatures(widened).length !== 1)
|
|
2108
|
+
return null;
|
|
2109
|
+
let funcPart = null;
|
|
2110
|
+
for (const part of widened.getTypes()) {
|
|
2111
|
+
if (checker.getCallSignatures(part).length > 0) {
|
|
2112
|
+
if (funcPart)
|
|
2113
|
+
return null; // exactly one callable part
|
|
2114
|
+
funcPart = part;
|
|
2115
|
+
continue;
|
|
2116
|
+
}
|
|
2117
|
+
const partSym = part.getSymbol();
|
|
2118
|
+
if ((part.flags & ts.TypeFlags.Object) === 0 ||
|
|
2119
|
+
checker.getConstructSignatures(part).length > 0 ||
|
|
2120
|
+
(partSym !== undefined && (partSym.flags & ts.SymbolFlags.Class) !== 0) ||
|
|
2121
|
+
checker.getIndexInfosOfType(part).length > 0) {
|
|
2122
|
+
return null;
|
|
2123
|
+
}
|
|
2124
|
+
}
|
|
2125
|
+
if (!funcPart)
|
|
2126
|
+
return null;
|
|
2127
|
+
const fn = mapType(funcPart, ctx);
|
|
2128
|
+
if (!fn || fn.kind !== "func")
|
|
2129
|
+
return null;
|
|
2130
|
+
const fields = [{ name: "%call", type: fn }];
|
|
2131
|
+
const declaredOrder = [];
|
|
2132
|
+
for (const p of checker.getPropertiesOfType(widened)) {
|
|
2133
|
+
if (p.flags & (ts.SymbolFlags.GetAccessor | ts.SymbolFlags.SetAccessor))
|
|
2134
|
+
return null;
|
|
2135
|
+
const pt = mapType(checker.getTypeOfSymbol(p), ctx);
|
|
2136
|
+
// No jsval absorb here: an island-entangled hybrid is not this shape.
|
|
2137
|
+
if (!pt || pt.kind === "void" || pt.kind === "jsval")
|
|
2138
|
+
return null;
|
|
2139
|
+
fields.push({ name: p.name, type: pt });
|
|
2140
|
+
declaredOrder.push(p.name);
|
|
2141
|
+
}
|
|
2142
|
+
if (fields.length === 1)
|
|
2143
|
+
return null; // no properties — it is just F
|
|
2144
|
+
fields.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
|
|
2145
|
+
return { kind: "record", shapeId: shapes.intern(fields, false, undefined, declaredOrder) };
|
|
2146
|
+
}
|
|
2147
|
+
/** True for MAPPED-type results — `Partial<Config>`, `Record<"a", n>`,
|
|
2148
|
+
* whatever `Pick`/`Omit` reduce to. Their shape is computed by the checker,
|
|
2149
|
+
* not declared anywhere: the only declaration behind them is the utility
|
|
2150
|
+
* type's `{ [P in keyof T]: ... }` machinery in lib.es5.d.ts. */
|
|
2151
|
+
function isMappedShape(t) {
|
|
2152
|
+
return ((t.flags & ts.TypeFlags.Object) !== 0 &&
|
|
2153
|
+
(t.objectFlags & ts.ObjectFlags.Mapped) !== 0);
|
|
2154
|
+
}
|
|
2155
|
+
/** The record path's provenance fence. Declared shapes (object literals,
|
|
2156
|
+
* interfaces, type literals) must come from user code, never a .d.ts — the
|
|
2157
|
+
* empty ambient interfaces (Object, Function, Boolean, ...) exist only to
|
|
2158
|
+
* satisfy tsc and must not become zero-field records. Checker-COMPUTED
|
|
2159
|
+
* shapes (mapped-type results, intersections) have no user declaration to
|
|
2160
|
+
* point at: mapped types pass here and get per-MEMBER provenance in the
|
|
2161
|
+
* field walk instead; an intersection passes when every part is itself an
|
|
2162
|
+
* ordinary provenance-passing object type (class parts keep their nominal
|
|
2163
|
+
* identity and never flatten into a struct). */
|
|
2164
|
+
function recordProvenanceOk(t, checker) {
|
|
2165
|
+
if (t.isIntersectionType()) {
|
|
2166
|
+
return t.getTypes().every((part) => {
|
|
2167
|
+
const partSym = part.getSymbol();
|
|
2168
|
+
return (part.flags & ts.TypeFlags.Object) !== 0 &&
|
|
2169
|
+
!(partSym && partSym.flags & ts.SymbolFlags.Class) &&
|
|
2170
|
+
checker.getCallSignatures(part).length === 0 &&
|
|
2171
|
+
checker.getConstructSignatures(part).length === 0 &&
|
|
2172
|
+
recordProvenanceOk(part, checker);
|
|
2173
|
+
});
|
|
2174
|
+
}
|
|
2175
|
+
if (isMappedShape(t))
|
|
2176
|
+
return true;
|
|
2177
|
+
const tSym = t.getSymbol();
|
|
2178
|
+
const decls = tSym ? checker.declarationsOf(tSym) : undefined;
|
|
2179
|
+
if (!decls || decls.length === 0)
|
|
2180
|
+
return false;
|
|
2181
|
+
return !decls.some((d) => d.getSourceFile().isDeclarationFile);
|
|
2182
|
+
}
|
|
2183
|
+
/** A GENERIC-callable member type (`m<T>(x: T): T` / `f: <T>(x: T) => T` in
|
|
2184
|
+
* an object type): pure function-shaped — call signatures only, every one
|
|
2185
|
+
* carrying its own type parameters — with no data properties, no construct
|
|
2186
|
+
* signatures, no index signatures. Such members are EXCLUDED from record
|
|
2187
|
+
* shapes instead of failing them: no single closure slot can hold a generic
|
|
2188
|
+
* function, so the shape keeps its data fields and calls of the member
|
|
2189
|
+
* monomorphize per call site against the defining declaration (see
|
|
2190
|
+
* lower-calls.ts, lowerObjLitGenericMethodCall). Node's JSON.stringify drops
|
|
2191
|
+
* function-valued properties, so serialization of the shape stays exact;
|
|
2192
|
+
* Object.keys over such a shape omits the member (a pin — every excluded
|
|
2193
|
+
* member is a function the key walk would name). */
|
|
2194
|
+
export function isGenericCallableMemberType(t, checker) {
|
|
2195
|
+
const sigs = checker.getCallSignatures(t);
|
|
2196
|
+
if (sigs.length === 0 || !sigs.every((s) => s.getTypeParameters().length > 0))
|
|
2197
|
+
return false;
|
|
2198
|
+
return (checker.getConstructSignatures(t).length === 0 &&
|
|
2199
|
+
checker.getPropertiesOfType(t).length === 0 &&
|
|
2200
|
+
checker.getIndexInfosOfType(t).length === 0);
|
|
2201
|
+
}
|
|
2202
|
+
function mapRecordType(widened, ctx) {
|
|
2203
|
+
const { checker, shapes } = ctx;
|
|
2204
|
+
if (checker.getConstructSignatures(widened).length > 0)
|
|
2205
|
+
return null;
|
|
2206
|
+
if (checker.isTupleType(widened) || checker.isArrayLikeType(widened))
|
|
2207
|
+
return null;
|
|
2208
|
+
// ENUM OBJECTS (`typeof e` — the enum used as a first-class value) look
|
|
2209
|
+
// exactly like a number-keyed hybrid (member fields + the reverse-map
|
|
2210
|
+
// index signature), but the VALUE has no lowering: an enum identifier in
|
|
2211
|
+
// value position resolves to nothing. Mapping the type would strand the
|
|
2212
|
+
// reference sites, so the enum-object type stays unmapped and badType
|
|
2213
|
+
// names the construct.
|
|
2214
|
+
const widenedSym = widened.getSymbol();
|
|
2215
|
+
if (widenedSym !== undefined && (widenedSym.flags & ts.SymbolFlags.Enum) !== 0)
|
|
2216
|
+
return null;
|
|
2217
|
+
// An index signature maps to a hybrid shape: declared fields keep
|
|
2218
|
+
// struct slots, undeclared keys ride the shape's overflow map, valued
|
|
2219
|
+
// uniformly by the signature's value type (`unknown` → dyn — a model-pricing table's
|
|
2220
|
+
// ModelPricing; `Record<string, T>` resolves here too, declared-field-
|
|
2221
|
+
// free). STRING and NUMBER key domains both compile — JS object keys ARE
|
|
2222
|
+
// strings (`o[1]` reads `o["1"]`), so a number-keyed signature is the
|
|
2223
|
+
// same string-keyed store with every access canonicalized through the
|
|
2224
|
+
// JS-exact number formatter (the access lowerings own that step). The
|
|
2225
|
+
// `string & {}` key — the special-intersection idiom mapped types use to
|
|
2226
|
+
// keep a literal-key union wide (`Record<(string & {}) | "left", T>`) —
|
|
2227
|
+
// is a string key: every part is string-flavored or the empty refinement.
|
|
2228
|
+
// BOTH signatures at once (string + number) collapse into the one store
|
|
2229
|
+
// when their value types intern identically (tsc requires only
|
|
2230
|
+
// assignability; unequal slots would need a per-key answer the store
|
|
2231
|
+
// cannot give). Value types outside the map-value world stay unmapped
|
|
2232
|
+
// (isSupportedIndexValue).
|
|
2233
|
+
let indexValue;
|
|
2234
|
+
const indexInfos = checker.getIndexInfosOfType(widened);
|
|
2235
|
+
if (indexInfos.length > 0) {
|
|
2236
|
+
const stringKey = (k) => (k.flags & ts.TypeFlags.String) !== 0 ||
|
|
2237
|
+
(k.isIntersectionType() &&
|
|
2238
|
+
k.getTypes().every((p) => (p.flags & ts.TypeFlags.String) !== 0 ||
|
|
2239
|
+
((p.flags & ts.TypeFlags.Object) !== 0 &&
|
|
2240
|
+
checker.getPropertiesOfType(p).length === 0 &&
|
|
2241
|
+
checker.getCallSignatures(p).length === 0 &&
|
|
2242
|
+
checker.getConstructSignatures(p).length === 0 &&
|
|
2243
|
+
checker.getIndexInfosOfType(p).length === 0)));
|
|
2244
|
+
if (indexInfos.length > 2)
|
|
2245
|
+
return null;
|
|
2246
|
+
let v = null;
|
|
2247
|
+
for (const info of indexInfos) {
|
|
2248
|
+
if (!stringKey(info.keyType) && !(info.keyType.flags & ts.TypeFlags.Number))
|
|
2249
|
+
return null;
|
|
2250
|
+
const iv = mapType(info.valueType, ctx);
|
|
2251
|
+
// A jsval-valued signature absorbs the shape: `Record<string,
|
|
2252
|
+
// JSONValue>` (a package's own JSON alias) and `Record<string, any>`
|
|
2253
|
+
// are one island object — the overflow map has no handle slot, and
|
|
2254
|
+
// island objects hold arbitrary engine values natively.
|
|
2255
|
+
if (iv?.kind === "jsval")
|
|
2256
|
+
return JSVAL;
|
|
2257
|
+
if (!iv || !isSupportedIndexValue(iv))
|
|
2258
|
+
return null;
|
|
2259
|
+
if (v !== null && !typeEquals(v, iv))
|
|
2260
|
+
return null;
|
|
2261
|
+
v = iv;
|
|
2262
|
+
}
|
|
2263
|
+
indexValue = v;
|
|
2264
|
+
}
|
|
2265
|
+
// The HEADER-FAMILY canonicalization: an index-signature shape whose
|
|
2266
|
+
// slot carries a `string[]` arm (alongside string/number/undefined —
|
|
2267
|
+
// nothing else) is the http header world: @types/node's
|
|
2268
|
+
// IncomingHttpHeaders and OutgoingHttpHeaders both declare ~60 OPTIONAL
|
|
2269
|
+
// well-known members over a Dict slot, and every literal SPREAD of them
|
|
2270
|
+
// (`{ ...req.headers, host }`) inherits those phantom members — struct
|
|
2271
|
+
// slots they are not; they are well-known KEYS of the slot. All such
|
|
2272
|
+
// shapes intern as ONE pure index-signature record over the canonical
|
|
2273
|
+
// OUTGOING slot `number | string | string[] | undefined` (numbers join
|
|
2274
|
+
// type-level only — parsed header values are strings), provided every
|
|
2275
|
+
// declared member's type fits inside the slot. Identical shapes make
|
|
2276
|
+
// the proxy's forwarded-header build (`{ ...req.headers }` into an
|
|
2277
|
+
// outgoing literal) a plain copy instead of an arm-wise re-tag the
|
|
2278
|
+
// merge machinery cannot do.
|
|
2279
|
+
if (indexValue?.kind === "union") {
|
|
2280
|
+
const slotDef = ctx.unions.get(indexValue.unionId);
|
|
2281
|
+
const armOk = (a) => a.kind === "f64" || a.kind === "string" || a.kind === "undefinedT" ||
|
|
2282
|
+
(a.kind === "array" && a.elem.kind === "string");
|
|
2283
|
+
if (slotDef !== undefined &&
|
|
2284
|
+
slotDef.arms.some((a) => a.kind === "array" && a.elem.kind === "string") &&
|
|
2285
|
+
slotDef.arms.every(armOk)) {
|
|
2286
|
+
const armsRaw = [F64, STRING, arrayOf(STRING), UNDEFINED_T];
|
|
2287
|
+
armsRaw.sort((a, b) => (typeKey(a) < typeKey(b) ? -1 : 1));
|
|
2288
|
+
const canonical = { kind: "union", unionId: ctx.unions.intern(armsRaw) };
|
|
2289
|
+
const fits = (t) => {
|
|
2290
|
+
if (!t)
|
|
2291
|
+
return false;
|
|
2292
|
+
if (t.kind === "union") {
|
|
2293
|
+
const def = ctx.unions.get(t.unionId);
|
|
2294
|
+
return def !== undefined && def.arms.every(armOk);
|
|
2295
|
+
}
|
|
2296
|
+
return armOk(t);
|
|
2297
|
+
};
|
|
2298
|
+
if (checker.getPropertiesOfType(widened).every((p) => fits(mapType(checker.getTypeOfSymbol(p), ctx)))) {
|
|
2299
|
+
return { kind: "record", shapeId: shapes.intern([], false, canonical, []) };
|
|
2300
|
+
}
|
|
2301
|
+
}
|
|
2302
|
+
}
|
|
2303
|
+
// Provenance keeps LIB type worlds out — but a PURE string index
|
|
2304
|
+
// signature with no declared members is structurally Record<string, V>
|
|
2305
|
+
// wherever it is declared (the lib's Object.fromEntries return type
|
|
2306
|
+
// `{ [k: string]: T }` — a data shape, not an API surface). Members, if
|
|
2307
|
+
// any, still walk the per-member provenance below.
|
|
2308
|
+
const pureIndexShape = indexValue !== undefined && checker.getPropertiesOfType(widened).length === 0;
|
|
2309
|
+
// The ANONYMOUS empty object type (`var v: {}` — the checker's shared
|
|
2310
|
+
// `{}` intrinsic carries a declaration-less `__type` symbol, so
|
|
2311
|
+
// declaration provenance has nothing to inspect): structurally the empty
|
|
2312
|
+
// record. tsc admits ANY non-nullish assignment into a `{}` slot;
|
|
2313
|
+
// non-record values fence at their assignment site (the exact-shape
|
|
2314
|
+
// stance), records of other shapes at theirs — the declaration itself is
|
|
2315
|
+
// representable.
|
|
2316
|
+
const anonSym = widened.getSymbol();
|
|
2317
|
+
const anonymousEmpty = (anonSym === undefined || checker.declarationsOf(anonSym).length === 0) &&
|
|
2318
|
+
!widened.isIntersectionType() &&
|
|
2319
|
+
indexValue === undefined &&
|
|
2320
|
+
checker.getPropertiesOfType(widened).length === 0;
|
|
2321
|
+
if (!recordProvenanceOk(widened, checker) && !pureIndexShape && !anonymousEmpty)
|
|
2322
|
+
return null;
|
|
2323
|
+
// Checker-computed shapes (no user declaration) need two extra fences in
|
|
2324
|
+
// the member walk below; see the comments there.
|
|
2325
|
+
const computed = widened.isIntersectionType() || isMappedShape(widened);
|
|
2326
|
+
// Recursive shapes (`type T = { next: T }`) cannot intern (and would be RC
|
|
2327
|
+
// cycles anyway): break the recursion by failing the inner reference.
|
|
2328
|
+
if (shapes.inProgress.has(widened))
|
|
2329
|
+
return null;
|
|
2330
|
+
shapes.inProgress.add(widened);
|
|
2331
|
+
try {
|
|
2332
|
+
const props = checker.getPropertiesOfType(widened);
|
|
2333
|
+
// A computed shape with NO members is not a real empty record: inside a
|
|
2334
|
+
// generic body `Partial<T>` resolves to no members at all (keyof T is
|
|
2335
|
+
// still unknown there) — interning `{}` would be silently wrong. The
|
|
2336
|
+
// degenerate empties (`Partial<{}>`, `Omit<C, keyof C>`) go with it.
|
|
2337
|
+
// An INDEX-SIGNATURE shape is exempt: `Record<string, T>` legitimately
|
|
2338
|
+
// has zero declared members — the signature is the shape.
|
|
2339
|
+
if (computed && props.length === 0 && !indexValue)
|
|
2340
|
+
return null;
|
|
2341
|
+
// A DECLARED empty object type — `{}` (spelled or the checker's shared
|
|
2342
|
+
// intrinsic), `interface Empty {}` — is tsc's TOP type over non-nullish
|
|
2343
|
+
// values: every number, string, record, array, function, or class
|
|
2344
|
+
// instance is assignable into the slot, so the exact-record stance
|
|
2345
|
+
// cannot hold it. It lowers like `unknown` (the dyn DOM), the same rule
|
|
2346
|
+
// as `object` and the lib's `Object`: assignments convert at the site
|
|
2347
|
+
// (sources outside the DOM fence there, exactly as unknown does), reads
|
|
2348
|
+
// narrow back out through the same checked casts. The type INFERRED
|
|
2349
|
+
// from an empty object literal (`const o = {}`) stays the empty record
|
|
2350
|
+
// — it describes a value the program built, not an annotation that
|
|
2351
|
+
// admits everything — and computed empties (`Partial<T>` in a generic
|
|
2352
|
+
// body) returned null above. Empty shapes WITH an index signature
|
|
2353
|
+
// (`Record<string, T>`) are not empty: the signature is the shape.
|
|
2354
|
+
if (props.length === 0 &&
|
|
2355
|
+
indexValue === undefined &&
|
|
2356
|
+
checker.getCallSignatures(widened).length === 0 &&
|
|
2357
|
+
checker.getConstructSignatures(widened).length === 0 &&
|
|
2358
|
+
!(anonSym !== undefined && checker.declarationsOf(anonSym).some((d) => ts.isObjectLiteralExpression(d)))) {
|
|
2359
|
+
return DYN;
|
|
2360
|
+
}
|
|
2361
|
+
const fields = [];
|
|
2362
|
+
for (const p of props) {
|
|
2363
|
+
if (p.flags & (ts.SymbolFlags.GetAccessor | ts.SymbolFlags.SetAccessor)) {
|
|
2364
|
+
// OBJECT-LITERAL get/set accessors (TS sources): the property has
|
|
2365
|
+
// no data slot — the shape carries reserved closure fields instead
|
|
2366
|
+
// (`%get:x` invoked per read, `%set:x` per write; see
|
|
2367
|
+
// accessorSlotProp in ir/nodes.ts). Type-literal and interface
|
|
2368
|
+
// accessor MEMBERS map the same way so accessor records cross
|
|
2369
|
+
// annotated boundaries (`function f(p: { get x(): number })`) —
|
|
2370
|
+
// tsc lets a plain DATA property satisfy such a member, but the
|
|
2371
|
+
// exact-shape stance turns that into a loud shape-mismatch fence
|
|
2372
|
+
// at the literal, never a silent split. JS-file literals keep
|
|
2373
|
+
// their existing paths (the CJS export-table narrowing and the
|
|
2374
|
+
// checked-dynamic fallback), and .d.ts declarations stay out with
|
|
2375
|
+
// the rest of the lib's type worlds.
|
|
2376
|
+
const decls = checker.declarationsOf(p);
|
|
2377
|
+
const getDecl = decls.find((d) => ts.isGetAccessorDeclaration(d));
|
|
2378
|
+
const setDecl = decls.find((d) => ts.isSetAccessorDeclaration(d));
|
|
2379
|
+
const accessorOwned = decls.length > 0 &&
|
|
2380
|
+
decls.every((d) => (ts.isGetAccessorDeclaration(d) || ts.isSetAccessorDeclaration(d)) &&
|
|
2381
|
+
d.parent !== undefined &&
|
|
2382
|
+
(ts.isObjectLiteralExpression(d.parent) ||
|
|
2383
|
+
ts.isInterfaceDeclaration(d.parent) ||
|
|
2384
|
+
d.parent.kind === ts.SyntaxKind.TypeLiteral) &&
|
|
2385
|
+
!d.getSourceFile().isDeclarationFile &&
|
|
2386
|
+
!isJsSourceFile(d.getSourceFile()));
|
|
2387
|
+
if (!accessorOwned || (!getDecl && !setDecl))
|
|
2388
|
+
return null;
|
|
2389
|
+
// Symbol-keyed accessors have no foldable literal name to fill at
|
|
2390
|
+
// the literal — no slot to make.
|
|
2391
|
+
if (p.name.startsWith("__@"))
|
|
2392
|
+
return null;
|
|
2393
|
+
// An index-signature shape stores accessor NAMES nowhere the keyed
|
|
2394
|
+
// read/walk machinery can answer (the overflow would miss where
|
|
2395
|
+
// Node dispatches the getter) — those shapes stay unmapped.
|
|
2396
|
+
if (indexValue !== undefined)
|
|
2397
|
+
return null;
|
|
2398
|
+
let readT = null;
|
|
2399
|
+
if (getDecl) {
|
|
2400
|
+
readT = mapType(checker.getTypeOfSymbol(p), ctx);
|
|
2401
|
+
if (readT === null || readT.kind === "jsval")
|
|
2402
|
+
return null;
|
|
2403
|
+
}
|
|
2404
|
+
let writeT = null;
|
|
2405
|
+
if (setDecl) {
|
|
2406
|
+
const param = setDecl.parameters[0];
|
|
2407
|
+
if (!param)
|
|
2408
|
+
return null;
|
|
2409
|
+
writeT = mapType(checker.getTypeAtLocation(param), ctx);
|
|
2410
|
+
// A void/unit-typed write slot has no value form; DIVERGENT
|
|
2411
|
+
// getter/setter types stay out too (one property, one type —
|
|
2412
|
+
// the class-accessor stance).
|
|
2413
|
+
if (writeT === null || writeT.kind === "void" || writeT.kind === "jsval" || isUnitType(writeT))
|
|
2414
|
+
return null;
|
|
2415
|
+
if (readT !== null && typeKey(readT) !== typeKey(writeT))
|
|
2416
|
+
return null;
|
|
2417
|
+
}
|
|
2418
|
+
if (readT !== null || getDecl)
|
|
2419
|
+
fields.push({ name: `%get:${p.name}`, type: funcOf([], readT ?? VOID) });
|
|
2420
|
+
if (writeT !== null)
|
|
2421
|
+
fields.push({ name: `%set:${p.name}`, type: funcOf([writeT], VOID) });
|
|
2422
|
+
continue;
|
|
2423
|
+
}
|
|
2424
|
+
// Computed shapes carry provenance per MEMBER: a utility type over a
|
|
2425
|
+
// lib interface (`Readonly<Date>`) is still the lib's type world, not
|
|
2426
|
+
// a data shape. Synthesized members (a literal-key Record's) have no
|
|
2427
|
+
// declarations and pass.
|
|
2428
|
+
if (computed && checker.declarationsOf(p).some((d) => d.getSourceFile().isDeclarationFile)) {
|
|
2429
|
+
return null;
|
|
2430
|
+
}
|
|
2431
|
+
const fieldTs = checker.getTypeOfSymbol(p);
|
|
2432
|
+
// GENERIC-callable members leave the shape (no single closure slot
|
|
2433
|
+
// can hold them — see isGenericCallableMemberType): the shape keeps
|
|
2434
|
+
// its data fields, and calls of the member monomorphize per call
|
|
2435
|
+
// site against the defining object literal's declaration.
|
|
2436
|
+
if (isGenericCallableMemberType(fieldTs, checker))
|
|
2437
|
+
continue;
|
|
2438
|
+
let pt = mapType(fieldTs, ctx);
|
|
2439
|
+
// Unit-only FIELDS (`{ msg?: undefined }` — the discriminated-union
|
|
2440
|
+
// absent-field idiom — and `{ p: undefined }` spellings): the
|
|
2441
|
+
// unit-only union. The runtime value is the interned unit, JSON
|
|
2442
|
+
// omits the undefined arm exactly like an omitted optional.
|
|
2443
|
+
if (pt?.kind === "void" && isUnitOnlyTsType(fieldTs))
|
|
2444
|
+
pt = unitOnlyUnion(ctx.unions);
|
|
2445
|
+
// dyn FIELDS map now (`{ v: unknown }`, `[string, unknown]` tuples):
|
|
2446
|
+
// the slot carries a DOM value exactly like an `unknown`-valued
|
|
2447
|
+
// overflow entry — same RC adapters, same dynFrom conversion on the
|
|
2448
|
+
// way in, same checked casts on the way out. (JSON.stringify of a
|
|
2449
|
+
// dyn-field-bearing shape keeps its fence: jsonSafe stays false.)
|
|
2450
|
+
if (!pt || pt.kind === "void")
|
|
2451
|
+
return null;
|
|
2452
|
+
// A DATA property spelled like a reserved accessor slot (`{ "%get:x":
|
|
2453
|
+
// v }` — a string-literal key): mapping it would collide with the
|
|
2454
|
+
// accessor dispatch, so the shape stays unmapped.
|
|
2455
|
+
if (accessorSlotProp(p.name) !== null)
|
|
2456
|
+
return null;
|
|
2457
|
+
// A bare jsval FIELD absorbs the record: shapes have no handle slot
|
|
2458
|
+
// (the IR forbids jsval fields — no JSON story), while an island
|
|
2459
|
+
// OBJECT holds engine values natively — `{ model: gateway(id),
|
|
2460
|
+
// prompt }` is one island object, built field by field (jsval
|
|
2461
|
+
// members as the same handle, static members marshaled). jsval-
|
|
2462
|
+
// BEARING composite fields (`content: any[]`) keep their static
|
|
2463
|
+
// shape — the lift covers them.
|
|
2464
|
+
if (pt.kind === "jsval")
|
|
2465
|
+
return JSVAL;
|
|
2466
|
+
fields.push({ name: p.name, type: pt });
|
|
2467
|
+
}
|
|
2468
|
+
// getPropertiesOfType yields DECLARATION order (interface/alias/literal
|
|
2469
|
+
// source order) — captured before the canonical sort so Object.keys/
|
|
2470
|
+
// entries/values can emit it (SEMANTICS.md 36). Two rules compose:
|
|
2471
|
+
// accessor slots stay out like every '%'-field (key-order surfaces
|
|
2472
|
+
// over accessor-carrying shapes fence by name at their lowerings),
|
|
2473
|
+
// and JS's own-key order applies — integer-like keys (canonical array
|
|
2474
|
+
// indices, from folded numeric computed keys and quoted numeric keys)
|
|
2475
|
+
// enumerate FIRST ascending, then string keys in insertion order
|
|
2476
|
+
// (OrdinaryOwnPropertyKeys) — Object.keys/JSON/inspect all read it.
|
|
2477
|
+
const declaredOrder = esOwnKeyOrder(fields.filter((f) => accessorSlotProp(f.name) === null).map((f) => f.name));
|
|
2478
|
+
fields.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
|
|
2479
|
+
return { kind: "record", shapeId: shapes.intern(fields, false, indexValue, declaredOrder) };
|
|
2480
|
+
}
|
|
2481
|
+
finally {
|
|
2482
|
+
shapes.inProgress.delete(widened);
|
|
2483
|
+
}
|
|
2484
|
+
}
|
|
2485
|
+
//# sourceMappingURL=types.js.map
|