@scriptc/compiler 0.0.10 → 0.0.12
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/ambient/scriptc-node-fallback.d.ts +63 -15
- package/ambient/scriptc.d.ts +2 -2
- package/dist/backend/cc.d.ts +2 -2
- package/dist/backend/emission/emit-async.js +1 -1
- package/dist/backend/emission/emit-exprs.js +131 -54
- package/dist/backend/emission/emit-exprs.js.map +1 -1
- package/dist/backend/emission/emit-island.js +1 -1
- package/dist/backend/emission/emit-shapes.js +1 -1
- package/dist/backend/emission/emit-walkers.d.ts +14 -14
- package/dist/backend/emission/emit-walkers.js +44 -44
- package/dist/backend/emission/emit-walkers.js.map +1 -1
- package/dist/backend/emission/emitter.d.ts +3 -3
- package/dist/backend/emission/emitter.js +33 -9
- package/dist/backend/emission/emitter.js.map +1 -1
- package/dist/backend/emission/may-throw.js +1 -1
- package/dist/backend/llvm/dyn.d.ts +10 -10
- package/dist/backend/llvm/dyn.js +26 -26
- package/dist/backend/llvm/dyn.js.map +1 -1
- package/dist/backend/llvm/emitter.js +156 -47
- package/dist/backend/llvm/emitter.js.map +1 -1
- package/dist/backend/llvm/shapes.js +1 -1
- package/dist/backend/llvm/walkers.js +2 -2
- package/dist/backend/llvm/walkers.js.map +1 -1
- package/dist/coverage/surface-manifest.js +16 -2
- package/dist/coverage/surface-manifest.js.map +1 -1
- package/dist/diagnostics/diagnostic.d.ts +21 -8
- package/dist/diagnostics/diagnostic.js +48 -14
- package/dist/diagnostics/diagnostic.js.map +1 -1
- package/dist/frontend/lowering/lib-boundary.js +5 -5
- package/dist/frontend/lowering/lib-boundary.js.map +1 -1
- package/dist/frontend/lowering/lower-assert.js +12 -12
- package/dist/frontend/lowering/lower-assert.js.map +1 -1
- package/dist/frontend/lowering/lower-builtins.d.ts +63 -7
- package/dist/frontend/lowering/lower-builtins.js +327 -35
- package/dist/frontend/lowering/lower-builtins.js.map +1 -1
- package/dist/frontend/lowering/lower-calls.d.ts +4 -4
- package/dist/frontend/lowering/lower-calls.js +104 -70
- package/dist/frontend/lowering/lower-calls.js.map +1 -1
- package/dist/frontend/lowering/lower-classes.d.ts +7 -0
- package/dist/frontend/lowering/lower-classes.js +82 -6
- package/dist/frontend/lowering/lower-classes.js.map +1 -1
- package/dist/frontend/lowering/lower-containers.d.ts +9 -5
- package/dist/frontend/lowering/lower-containers.js +88 -26
- package/dist/frontend/lowering/lower-containers.js.map +1 -1
- package/dist/frontend/lowering/lower-emitter.d.ts +70 -3
- package/dist/frontend/lowering/lower-emitter.js +438 -31
- package/dist/frontend/lowering/lower-emitter.js.map +1 -1
- package/dist/frontend/lowering/lower-exprs.d.ts +87 -12
- package/dist/frontend/lowering/lower-exprs.js +630 -125
- package/dist/frontend/lowering/lower-exprs.js.map +1 -1
- package/dist/frontend/lowering/lower-inspect.js +5 -5
- package/dist/frontend/lowering/lower-inspect.js.map +1 -1
- package/dist/frontend/lowering/lower-island.js +12 -3
- package/dist/frontend/lowering/lower-island.js.map +1 -1
- package/dist/frontend/lowering/lower-modules.d.ts +8 -5
- package/dist/frontend/lowering/lower-modules.js +157 -63
- package/dist/frontend/lowering/lower-modules.js.map +1 -1
- package/dist/frontend/lowering/lower-namespaces.d.ts +24 -0
- package/dist/frontend/lowering/lower-namespaces.js +158 -16
- package/dist/frontend/lowering/lower-namespaces.js.map +1 -1
- package/dist/frontend/lowering/lower-server.js +11 -11
- package/dist/frontend/lowering/lower-server.js.map +1 -1
- package/dist/frontend/lowering/lower-stmts.d.ts +1 -1
- package/dist/frontend/lowering/lower-stmts.js +271 -36
- package/dist/frontend/lowering/lower-stmts.js.map +1 -1
- package/dist/frontend/lowering/lower-stream.js +171 -20
- package/dist/frontend/lowering/lower-stream.js.map +1 -1
- package/dist/frontend/lowering/lowerer.d.ts +27 -10
- package/dist/frontend/lowering/lowerer.js +156 -54
- package/dist/frontend/lowering/lowerer.js.map +1 -1
- package/dist/frontend/lowering/surfaces.d.ts +44 -0
- package/dist/frontend/lowering/surfaces.js +236 -5
- package/dist/frontend/lowering/surfaces.js.map +1 -1
- package/dist/frontend/npm.d.ts +12 -0
- package/dist/frontend/npm.js +30 -0
- package/dist/frontend/npm.js.map +1 -1
- package/dist/frontend/program.d.ts +25 -0
- package/dist/frontend/program.js +145 -136
- package/dist/frontend/program.js.map +1 -1
- package/dist/frontend/shared.d.ts +1 -1
- package/dist/frontend/shared.js +8 -8
- package/dist/frontend/shared.js.map +1 -1
- package/dist/frontend/types.d.ts +2 -2
- package/dist/frontend/types.js +15 -15
- package/dist/frontend/types.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +169 -5
- package/dist/index.js.map +1 -1
- package/dist/ir/nodes.d.ts +174 -106
- package/dist/ir/nodes.js +69 -24
- package/dist/ir/nodes.js.map +1 -1
- package/dist/ir/validate.js +55 -36
- package/dist/ir/validate.js.map +1 -1
- package/dist/library/fence-eval.js +12 -2
- package/dist/library/fence-eval.js.map +1 -1
- package/dist/library/int-infer.d.ts +119 -0
- package/dist/library/int-infer.js +1314 -0
- package/dist/library/int-infer.js.map +1 -0
- package/dist/library/int-infer.test.d.ts +1 -0
- package/dist/library/int-infer.test.js +279 -0
- package/dist/library/int-infer.test.js.map +1 -0
- package/dist/library/profile.d.ts +34 -5
- package/dist/library/profile.js +77 -13
- package/dist/library/profile.js.map +1 -1
- package/dist/library/sidecar-validate.js +1 -1
- package/dist/library/sidecar-validate.js.map +1 -1
- package/dist/library/sidecar.d.ts +24 -1
- package/dist/library/sidecar.js +163 -16
- package/dist/library/sidecar.js.map +1 -1
- package/package.json +2 -2
- package/surface-manifest.json +210 -12
|
@@ -8,7 +8,7 @@ import * as ts from "../ts7/adapter.js";
|
|
|
8
8
|
import { dirname } from "node:path";
|
|
9
9
|
import { BOOL, CAUGHT, DYN, DYN_HANDLE_KINDS, F64, JSVAL, NULL_T, REF_TRUTHY_KINDS, REGEX, RUNTIME_ERROR_CLASSES, SEARCH_PARAMS_T, STRING, UNDEFINED_T, VOID, arrayOf, canAdaptDynFuncTo, canBoxFuncIntoDyn, canDynCheckTo, funcOf, isJsonSafeType, isUnitType, jsOpResultKind, shapeHasAccessorSlots, typeEquals, typeKey, unionFuncSetArmsOk } from "../../ir/nodes.js";
|
|
10
10
|
import { cjsClassExprWholeExportOf, cjsExportAssignmentOf, cjsExportDiscardReason, isCjsExportTableLiteral, isCjsJsFile, isJsSourceFile, isModuleExportsAccess, isNodeEsmFile, locOf } from "../program.js";
|
|
11
|
-
import { ARRAY_METHODS, builtinConstLit, builtinFenceHintOf, builtinModuleConstOf, builtinModuleFnOf, ISLAND_SURFACE, isChildSurfaceMember, MAP_METHODS, NARROW_FIRST, SET_METHODS, STR_METHODS, UNSUPPORTED_EXPR, sideEffectFreeOptionValue, stdlibGlobalNameOf } from "./surfaces.js";
|
|
11
|
+
import { ARRAY_METHODS, builtinConstLit, builtinFenceHintOf, builtinModuleConstOf, builtinModulesArrayLit, builtinModuleFnOf, ISLAND_SURFACE, isChildSurfaceMember, MAP_METHODS, NARROW_FIRST, SET_METHODS, STR_METHODS, UNSUPPORTED_EXPR, sideEffectFreeOptionValue, stdlibGlobalNameOf } from "./surfaces.js";
|
|
12
12
|
import { blockedBindingUseDiag, recordShapeMismatchDiag, requiresDynamicPackageDiag, unsupportedDiag } from "../../diagnostics/diagnostic.js";
|
|
13
13
|
import { PoisonError, dynUndefinedExpr, jsFuncNameOf, neverTaintedJsType, nodeThrowExpr, own } from "./lowerer.js";
|
|
14
14
|
import { lowerIndexMergeHelper, lowerNpmStaticSafeIndexRead, strCharsCall } from "./lower-containers.js";
|
|
@@ -83,7 +83,22 @@ function lowerExprInner(L, expr) {
|
|
|
83
83
|
if (chainRecv)
|
|
84
84
|
return { ...chainRecv, loc };
|
|
85
85
|
if (ts.isNumericLiteral(expr)) {
|
|
86
|
-
|
|
86
|
+
const value = Number(expr.text.replace(/_/g, ""));
|
|
87
|
+
// Ask 4's representability input: a DECIMAL INTEGER source spelling
|
|
88
|
+
// that does not survive the trip through f64 (parse, format back,
|
|
89
|
+
// compare) rides the literal so the library integer-boundary check
|
|
90
|
+
// can refuse on the author's source text. `expr.text` is the
|
|
91
|
+
// scanner's COOKED value (already the nearest double), so the
|
|
92
|
+
// source spelling comes from the file text; numeric separators are
|
|
93
|
+
// spelling sugar and strip first. Round-tripping literals (every
|
|
94
|
+
// integer within ±(2^53−1)) and non-integer spellings carry
|
|
95
|
+
// nothing, so the IR is unchanged for programs that held their
|
|
96
|
+
// numbers.
|
|
97
|
+
const spelled = expr.getText().replace(/_/g, "");
|
|
98
|
+
if (/^\d+$/.test(spelled) && String(Number(spelled)) !== spelled) {
|
|
99
|
+
return { kind: "numLit", value: Number(spelled), spelling: spelled, type: F64, loc };
|
|
100
|
+
}
|
|
101
|
+
return { kind: "numLit", value, type: F64, loc };
|
|
87
102
|
}
|
|
88
103
|
if (expr.kind === ts.SyntaxKind.TrueKeyword) {
|
|
89
104
|
return { kind: "boolLit", value: true, type: BOOL, loc };
|
|
@@ -207,9 +222,9 @@ function lowerExprInner(L, expr) {
|
|
|
207
222
|
// A JS variable INITIALIZER whose BINDING registered as a
|
|
208
223
|
// checked-dynamic module global (the typedef-annotated
|
|
209
224
|
// doc-builder consts whose JSDoc types degraded — DYN slots):
|
|
210
|
-
// the island build could never land (no engine→
|
|
225
|
+
// the island build could never land (no engine→dyn crossing
|
|
211
226
|
// exists), so the literal takes its own world's path instead —
|
|
212
|
-
// the
|
|
227
|
+
// the dyn literal, or a static record the slot converts.
|
|
213
228
|
if (isJsSourceFile(expr.getSourceFile()) &&
|
|
214
229
|
ts.isVariableDeclaration(expr.parent) &&
|
|
215
230
|
expr.parent.initializer === expr &&
|
|
@@ -224,7 +239,7 @@ function lowerExprInner(L, expr) {
|
|
|
224
239
|
// reference degraded to checker-`any` — the tsgo multi-file
|
|
225
240
|
// value-as-type residue) is not an npm slot: when the literal's
|
|
226
241
|
// OWN type maps statically, it builds that way — the binding's
|
|
227
|
-
// checked-dynamic slot takes the
|
|
242
|
+
// checked-dynamic slot takes the dyn conversion. Genuinely
|
|
228
243
|
// island slots (npm .d.ts provenance, plain `any`) keep the
|
|
229
244
|
// native build.
|
|
230
245
|
if (ctxTs !== undefined && (ctxTs.flags & ts.TypeFlags.Any) === 0) {
|
|
@@ -259,13 +274,22 @@ function lowerExprInner(L, expr) {
|
|
|
259
274
|
const argsWith = [];
|
|
260
275
|
const getters = [];
|
|
261
276
|
let spread = null;
|
|
262
|
-
// A MEMBER a JS file cannot lower or marshal
|
|
263
|
-
//
|
|
264
|
-
//
|
|
265
|
-
//
|
|
266
|
-
//
|
|
267
|
-
//
|
|
268
|
-
|
|
277
|
+
// A MEMBER a JS file cannot lower or marshal: defer like a
|
|
278
|
+
// statement fence, shaped by what the member IS. A FUNCTION-shaped
|
|
279
|
+
// member (a generic function as a value, a signature with
|
|
280
|
+
// checked-dynamic parameters — the doc-builder public aggregate's
|
|
281
|
+
// degraded pieces) becomes a host closure that THROWS the captured
|
|
282
|
+
// diagnostic when invoked — building the aggregate compiles and
|
|
283
|
+
// only a CALL through the island stops the run. A DATA-shaped
|
|
284
|
+
// member must NOT become a callable (the retired fence box's
|
|
285
|
+
// silent wrong answers: typeof said "function", downstream errors
|
|
286
|
+
// blamed the wrong thing — the withPlugins `plugins:` slot), so it
|
|
287
|
+
// defines through the engine's getter machinery instead: READING
|
|
288
|
+
// the member throws the diagnostic — the honest granularity, since
|
|
289
|
+
// using the value is exactly what cannot be answered. `getterName`
|
|
290
|
+
// null keeps the closure shape (function-shaped members, and the
|
|
291
|
+
// conditional-spread arms where getters cannot combine).
|
|
292
|
+
const islandMemberFence = (diagsBefore, err, valueNode, getterName = null) => {
|
|
269
293
|
if (!(err instanceof PoisonError) ||
|
|
270
294
|
!isJsSourceFile(expr.getSourceFile()) ||
|
|
271
295
|
L.diagSink !== null ||
|
|
@@ -295,8 +319,33 @@ function lowerExprInner(L, expr) {
|
|
|
295
319
|
loc: locOf(valueNode),
|
|
296
320
|
});
|
|
297
321
|
const fence = { kind: "closure", fnName, captures: [], type: funcOf([], VOID), loc: locOf(valueNode) };
|
|
322
|
+
if (getterName !== null) {
|
|
323
|
+
getters.push({ name: getterName, fn: L.jsvalIn(fence, valueNode), loc: locOf(valueNode) });
|
|
324
|
+
return null;
|
|
325
|
+
}
|
|
298
326
|
return L.jsvalIn(fence, valueNode);
|
|
299
327
|
};
|
|
328
|
+
// The member's SHAPE decides the fence's granularity: syntactic
|
|
329
|
+
// functions and checker-callable values keep the call-time
|
|
330
|
+
// closure; everything else (call results, awaits, data reads —
|
|
331
|
+
// the withPlugins `plugins:` shape) fences at the READ.
|
|
332
|
+
const funcShapedMember = (p) => {
|
|
333
|
+
if (ts.isMethodDeclaration(p))
|
|
334
|
+
return true;
|
|
335
|
+
const src = ts.isPropertyAssignment(p)
|
|
336
|
+
? p.initializer
|
|
337
|
+
: ts.isShorthandPropertyAssignment(p)
|
|
338
|
+
? p.name
|
|
339
|
+
: null;
|
|
340
|
+
if (!src || !ts.isExpression(src))
|
|
341
|
+
return true; // unknown form: keep the closure shape
|
|
342
|
+
let inner = src;
|
|
343
|
+
while (ts.isParenthesizedExpression(inner))
|
|
344
|
+
inner = inner.expression;
|
|
345
|
+
if (ts.isArrowFunction(inner) || ts.isFunctionExpression(inner))
|
|
346
|
+
return true;
|
|
347
|
+
return L.checker.getCallSignatures(L.typeOf(src)).length > 0;
|
|
348
|
+
};
|
|
300
349
|
const pushProp = (name, value, valueNode, into) => {
|
|
301
350
|
const diagsBefore = L.diags.length;
|
|
302
351
|
let marshaled;
|
|
@@ -304,7 +353,15 @@ function lowerExprInner(L, expr) {
|
|
|
304
353
|
marshaled = L.jsvalIn(value, valueNode);
|
|
305
354
|
}
|
|
306
355
|
catch (err) {
|
|
307
|
-
|
|
356
|
+
// A value that LOWERED but cannot cross (a promise, a class
|
|
357
|
+
// instance): func-typed values keep the call-time closure;
|
|
358
|
+
// data-shaped values fence at the read (the getter), unless a
|
|
359
|
+
// conditional spread owns the literal (getters cannot combine).
|
|
360
|
+
const asGetter = value.type.kind !== "func" && spread === null;
|
|
361
|
+
const fence = islandMemberFence(diagsBefore, err, valueNode, asGetter ? name.text : null);
|
|
362
|
+
if (fence === null)
|
|
363
|
+
return; // registered as a fence getter — no data property
|
|
364
|
+
marshaled = fence;
|
|
308
365
|
}
|
|
309
366
|
for (const args of into) {
|
|
310
367
|
args.push({
|
|
@@ -388,7 +445,11 @@ function lowerExprInner(L, expr) {
|
|
|
388
445
|
: null;
|
|
389
446
|
}
|
|
390
447
|
catch (err) {
|
|
391
|
-
|
|
448
|
+
const nameText = name && (ts.isIdentifier(name) || ts.isStringLiteral(name)) ? name.text : null;
|
|
449
|
+
const asGetter = nameText !== null && spread === null && !funcShapedMember(prop);
|
|
450
|
+
value = islandMemberFence(valueDiagsBefore, err, prop, asGetter ? nameText : null);
|
|
451
|
+
if (value === null)
|
|
452
|
+
continue; // registered as a fence getter — no data property
|
|
392
453
|
}
|
|
393
454
|
if (value && name && (ts.isIdentifier(name) || ts.isStringLiteral(name))) {
|
|
394
455
|
pushProp(name, value, prop, [argsWithout, argsWith]);
|
|
@@ -479,7 +540,7 @@ function lowerExprInner(L, expr) {
|
|
|
479
540
|
}
|
|
480
541
|
// A checker-narrowed dyn read arrives as a VALIDATED extraction
|
|
481
542
|
// (maybeNarrow's dynCheck bridge). typeof needs no extraction — the
|
|
482
|
-
//
|
|
543
|
+
// dyn kind table answers the question directly, and answers it even
|
|
483
544
|
// where the flow type lied (the extraction would throw instead) —
|
|
484
545
|
// so unwrap back to the dyn value and take the dyn.typeof path.
|
|
485
546
|
if (operand.kind === "dynCheck" && operand.value.type.kind === "dyn") {
|
|
@@ -524,7 +585,7 @@ function lowerExprInner(L, expr) {
|
|
|
524
585
|
if (operand.type.kind === "dyn") {
|
|
525
586
|
// Bare typeof on a dyn value: the runtime's kind→string table
|
|
526
587
|
// (null answers "object", boxed closures "function" — JS-exact
|
|
527
|
-
// for every
|
|
588
|
+
// for every dyn kind; "bigint"/"symbol" have no producers).
|
|
528
589
|
return { kind: "libCall", fn: "dyn.typeof", args: [operand], type: STRING, loc };
|
|
529
590
|
}
|
|
530
591
|
L.unsupported("SC1090", expr, "typeof expressions on statically-typed values");
|
|
@@ -534,7 +595,7 @@ function lowerExprInner(L, expr) {
|
|
|
534
595
|
return { kind: "selfRef", type: L.ctx.selfType, loc };
|
|
535
596
|
}
|
|
536
597
|
// `arguments` in a variadic JS function (the rest-marked form): the
|
|
537
|
-
// synthetic trailing
|
|
598
|
+
// synthetic trailing dyn-array param — lambdaSignature marked the
|
|
538
599
|
// type and lowerLambda declared the local.
|
|
539
600
|
if (expr.text === "arguments" && L.ctx.argumentsLocal) {
|
|
540
601
|
return { kind: "varRef", localId: L.ctx.argumentsLocal.id, type: DYN, loc };
|
|
@@ -603,7 +664,7 @@ function lowerExprInner(L, expr) {
|
|
|
603
664
|
// optional/default/rest declarations pass the value fence first.
|
|
604
665
|
L.noteEdge(sig.name);
|
|
605
666
|
// dynRest slots stay out of the VALUE type's param list (fn.length
|
|
606
|
-
// semantics); the rest marker carries the trailing
|
|
667
|
+
// semantics); the rest marker carries the trailing dyn-array ABI.
|
|
607
668
|
const funcType = {
|
|
608
669
|
kind: "func",
|
|
609
670
|
params: sig.params.filter((p) => p.mode !== "dynRest").map((p) => p.type),
|
|
@@ -649,6 +710,11 @@ function lowerExprInner(L, expr) {
|
|
|
649
710
|
const c = builtinModuleConstOf(L, bi.module, bi.member);
|
|
650
711
|
if (c !== undefined)
|
|
651
712
|
return builtinConstLit(c, loc);
|
|
713
|
+
// module.builtinModules — the baked Node v24 list, a fresh
|
|
714
|
+
// string[] per read.
|
|
715
|
+
if (bi.module === "module" && bi.member === "builtinModules") {
|
|
716
|
+
return builtinModulesArrayLit(loc);
|
|
717
|
+
}
|
|
652
718
|
// JavaScript sources: a builtin member taken as a bare VALUE is
|
|
653
719
|
// the same identity-token story as stdlib globals above (the
|
|
654
720
|
// harness adds worker_threads.Worker to its identity Set).
|
|
@@ -979,7 +1045,7 @@ function lowerExprInner(L, expr) {
|
|
|
979
1045
|
if (value.type.kind !== "promise") {
|
|
980
1046
|
// A CHECKED-DYNAMIC operand (`await v` where v rode an untyped
|
|
981
1047
|
// binding — a destructured helper's return, a dyn call result):
|
|
982
|
-
// the runtime decides — a
|
|
1048
|
+
// the runtime decides — a dyn promise adopts (rejections
|
|
983
1049
|
// re-throw), anything else takes JS's one-hop non-thenable await
|
|
984
1050
|
// and answers itself. Thenable adoption stays unmodeled
|
|
985
1051
|
// (SEMANTICS.md).
|
|
@@ -1137,9 +1203,9 @@ function lowerExprInner(L, expr) {
|
|
|
1137
1203
|
// bound handle).
|
|
1138
1204
|
if (L.isIslandExpr(expr.expression)) {
|
|
1139
1205
|
const receiver = L.lowerExpr(expr.expression);
|
|
1140
|
-
// The checker said 'any' but the VALUE lives in the
|
|
1206
|
+
// The checker said 'any' but the VALUE lives in the checked-dynamic tree (`this`
|
|
1141
1207
|
// in a plain JS function — dyn.this — or a checked-dynamic local
|
|
1142
|
-
// behind an any-typed spelling): the property read is the
|
|
1208
|
+
// behind an any-typed spelling): the property read is the checked-dynamic tree's
|
|
1143
1209
|
// own keyed read, dyn results and dyn chains exactly like every
|
|
1144
1210
|
// checked-dynamic member access (a nullish receiver throws V8's
|
|
1145
1211
|
// catchable TypeError, exactly Node). Never a jsOp over a dyn —
|
|
@@ -1177,6 +1243,15 @@ function lowerExprInner(L, expr) {
|
|
|
1177
1243
|
}
|
|
1178
1244
|
return read;
|
|
1179
1245
|
}
|
|
1246
|
+
// `m.groups` on a match result whose regex is statically known:
|
|
1247
|
+
// the compile-time record projection over the honest slice (or
|
|
1248
|
+
// Node's undefined when the pattern has no named groups) — see
|
|
1249
|
+
// lowerMatchGroupsRead.
|
|
1250
|
+
{
|
|
1251
|
+
const g = lowerMatchGroupsRead(L, expr);
|
|
1252
|
+
if (g !== null)
|
|
1253
|
+
return g;
|
|
1254
|
+
}
|
|
1180
1255
|
// `m.index` on a for-of-over-matchAll binding reads the companion-
|
|
1181
1256
|
// index array at this iteration's cursor — always a number: every
|
|
1182
1257
|
// drained row matched, so the lib's `.index` is never undefined
|
|
@@ -1414,14 +1489,14 @@ function lowerExprInner(L, expr) {
|
|
|
1414
1489
|
// .length` where the binding is an evolving any[] living as a
|
|
1415
1490
|
// checked-dynamic global): the stdlib member fence below would blame
|
|
1416
1491
|
// the unmappable checker type (`any[].length`), but the VALUE is a
|
|
1417
|
-
//
|
|
1492
|
+
// dyn node — lower the receiver first and read through the dyn keyed
|
|
1418
1493
|
// read (ARR answers length; everything else JS's own-property
|
|
1419
1494
|
// answer). Mapped receivers keep the fence-first order: their
|
|
1420
1495
|
// members' gaps are real surface gaps ([1,2].entries), not
|
|
1421
1496
|
// representation artifacts. `.length` on a receiver whose checker
|
|
1422
1497
|
// ARRAY type lowers to the checked-dynamic representation —
|
|
1423
1498
|
// `unknown[]`, the collapsed `(string | object)[]`, and the `any[]`
|
|
1424
|
-
// an Array.isArray guard narrows a collapsed union to — is the
|
|
1499
|
+
// an Array.isArray guard narrows a collapsed union to — is the checked-dynamic tree
|
|
1425
1500
|
// array's OWN length (a keyed read the ARR kind answers exactly),
|
|
1426
1501
|
// never an `Array.prototype` surface gap; both source languages.
|
|
1427
1502
|
// Prototype-method VALUE reads (`ps.map` unparenthesized) keep the
|
|
@@ -1431,7 +1506,7 @@ function lowerExprInner(L, expr) {
|
|
|
1431
1506
|
if ((isJsSourceFile(expr.getSourceFile()) &&
|
|
1432
1507
|
(L.mapTypeOf(L.typeOf(expr.expression)) === null ||
|
|
1433
1508
|
// A never-tainted receiver type maps (never rides as f64) but
|
|
1434
|
-
// its VALUE lowered checked-dynamic — same
|
|
1509
|
+
// its VALUE lowered checked-dynamic — same dyn read.
|
|
1435
1510
|
neverTaintedJsType(L, expr.expression, L.typeOf(expr.expression)))) ||
|
|
1436
1511
|
(expr.name.text === "length" &&
|
|
1437
1512
|
(L.checkerAnyArray(expr.expression) ||
|
|
@@ -1542,7 +1617,7 @@ function lowerExprInner(L, expr) {
|
|
|
1542
1617
|
// the property and the receiver's type instead of "property access".
|
|
1543
1618
|
const recvLowered = L.lowerExpr(expr.expression);
|
|
1544
1619
|
// A dyn receiver — JSON.parse's `any`, or `unknown` the checker
|
|
1545
|
-
// narrowed to `object` — reads through the
|
|
1620
|
+
// narrowed to `object` — reads through the dyn keyed read: member
|
|
1546
1621
|
// or undefined (JS's own-property answer), throwing JS's TypeError
|
|
1547
1622
|
// on undefined/null receivers unless an earlier `?.` guards the
|
|
1548
1623
|
// chain. Scalar-narrowed occurrences bridge via maybeNarrow's
|
|
@@ -1552,6 +1627,31 @@ function lowerExprInner(L, expr) {
|
|
|
1552
1627
|
const opt = chainGuardedByQuestionDot(expr.expression);
|
|
1553
1628
|
return L.maybeNarrow({ kind: "dynKeyGet", key, ...(opt ? { optional: true } : {}), value: recvLowered, type: DYN, loc }, expr);
|
|
1554
1629
|
}
|
|
1630
|
+
// The lowered receiver is a RECORD the checker spelled wider —
|
|
1631
|
+
// `s.match(re).groups.key` in a JS file: the checker says
|
|
1632
|
+
// `{ [key: string]: string } | undefined`, but the groups
|
|
1633
|
+
// projection already answered the record arm (its null trap
|
|
1634
|
+
// included). Declared fields read directly; an index-signature
|
|
1635
|
+
// shape serves undeclared keys through the overflow read, exactly
|
|
1636
|
+
// the dot-access rule on checker-spelled hybrids.
|
|
1637
|
+
if (recvLowered.type.kind === "record") {
|
|
1638
|
+
const recvShape = L.shapes.get(recvLowered.type.shapeId);
|
|
1639
|
+
const field = recvShape?.fields.find((f) => f.name === expr.name.text);
|
|
1640
|
+
if (field) {
|
|
1641
|
+
return L.maybeNarrow({ kind: "recordGet", obj: recvLowered, shapeId: recvLowered.type.shapeId, field: field.name, type: field.type, loc }, expr);
|
|
1642
|
+
}
|
|
1643
|
+
if (recvShape?.indexValue !== undefined && !recvShape.tuple) {
|
|
1644
|
+
return L.maybeNarrow({
|
|
1645
|
+
kind: "recordKeyGet",
|
|
1646
|
+
obj: recvLowered,
|
|
1647
|
+
shapeId: recvLowered.type.shapeId,
|
|
1648
|
+
key: { kind: "strLit", value: expr.name.text, type: STRING, loc: locOf(expr.name) },
|
|
1649
|
+
overflowOnly: true,
|
|
1650
|
+
type: recvShape.indexValue,
|
|
1651
|
+
loc,
|
|
1652
|
+
}, expr);
|
|
1653
|
+
}
|
|
1654
|
+
}
|
|
1555
1655
|
// An ABSTRACT property through an abstract-typed receiver: the
|
|
1556
1656
|
// declaration is erased at runtime — Node defines no field for it,
|
|
1557
1657
|
// each concrete subclass declares its OWN (at its own layout
|
|
@@ -1763,11 +1863,11 @@ export function lowerTernary(L, expr, expected) {
|
|
|
1763
1863
|
// === "string" ? pkg.name : null` — the lowering world types the
|
|
1764
1864
|
// unknown-receiver read `any`, which a static build cannot hold):
|
|
1765
1865
|
// dyn represents null and undefined directly, so the ternary stays
|
|
1766
|
-
// dyn — a unit arm converts to the
|
|
1866
|
+
// dyn — a unit arm converts to the dyn unit value (the dynFrom form
|
|
1767
1867
|
// the index-signature lowerings already use). A FRESH literal arm
|
|
1768
1868
|
// (`... ? pkg.scripts : {}` — the default-object idiom) converts the
|
|
1769
1869
|
// same way when it is JSON-safe: nothing else aliases a literal, so
|
|
1770
|
-
// the
|
|
1870
|
+
// the dyn copy is unobservable.
|
|
1771
1871
|
const dynish = (e) => e.type.kind === "dyn" ||
|
|
1772
1872
|
e.kind === "unitLit" ||
|
|
1773
1873
|
((e.kind === "recordLit" || e.kind === "arrayLit") && L.dynConvertible(e.type));
|
|
@@ -1853,13 +1953,13 @@ export function maybeNarrow(L, expr, node) {
|
|
|
1853
1953
|
(narrowed.kind === "f64" || narrowed.kind === "bool" || narrowed.kind === "string")) {
|
|
1854
1954
|
return { kind: "dynCheck", value: expr, type: narrowed, loc: expr.loc };
|
|
1855
1955
|
}
|
|
1856
|
-
// An `instanceof Uint8Array` narrow: the
|
|
1956
|
+
// An `instanceof Uint8Array` narrow: the checked-dynamic tree's bytes kind, extracted
|
|
1857
1957
|
// with the same validated copy the checked cast uses (a Buffer that
|
|
1858
1958
|
// crossed in rides the kind too — it IS a Uint8Array in Node).
|
|
1859
1959
|
if (narrowed?.kind === "bytes" && narrowed.elem === "u8") {
|
|
1860
1960
|
return { kind: "dynCheck", value: expr, type: narrowed, loc: expr.loc };
|
|
1861
1961
|
}
|
|
1862
|
-
// An `instanceof Error` narrow: the
|
|
1962
|
+
// An `instanceof Error` narrow: the checked-dynamic tree's error encoding rebuilds a
|
|
1863
1963
|
// fresh %Error (name/message/code from the marker object — a COPY,
|
|
1864
1964
|
// the unknown boundary's stance; SEMANTICS.md 67), validated like
|
|
1865
1965
|
// every dyn extraction.
|
|
@@ -2035,7 +2135,7 @@ export function lowerLooseNullCompare(L, expr, loc) {
|
|
|
2035
2135
|
return { kind: "boolLit", value: !negated, type: BOOL, loc };
|
|
2036
2136
|
}
|
|
2037
2137
|
if (other.type.kind === "dyn") {
|
|
2038
|
-
// `v != null` on unknown: one
|
|
2138
|
+
// `v != null` on unknown: one dyn kind test covers both units.
|
|
2039
2139
|
return {
|
|
2040
2140
|
kind: "dynTest",
|
|
2041
2141
|
test: "nullish",
|
|
@@ -2194,7 +2294,7 @@ export function lowerNullishCoalesce(L, expr, loc) {
|
|
|
2194
2294
|
// runtime kind (scr_dyn_is_nullish — UNDEF/NULL take the default;
|
|
2195
2295
|
// a wrapped island value routes to the engine's test, defensively:
|
|
2196
2296
|
// the wrap constructor scalar-normalizes engine null/undefined
|
|
2197
|
-
// away). Both sides live in the
|
|
2297
|
+
// away). Both sides live in the checked-dynamic tree — the right converts through
|
|
2198
2298
|
// the usual boundary and evaluates lazily in its branch; a default
|
|
2199
2299
|
// with no dyn representation keeps the fence.
|
|
2200
2300
|
const right = L.coerceToExpected(L.lowerExpr(expr.right), DYN);
|
|
@@ -2476,7 +2576,7 @@ export function lowerOptionalChain(L, expr) {
|
|
|
2476
2576
|
}
|
|
2477
2577
|
// NUMBER-typed indices (`entries?.[0]`): the property key is
|
|
2478
2578
|
// ToString(i), exactly JS — the canonical number text answers
|
|
2479
|
-
// array indices in the
|
|
2579
|
+
// array indices in the dyn helper, anything else (fractions,
|
|
2480
2580
|
// negatives, NaN) reads as an absent key.
|
|
2481
2581
|
if (key.type.kind === "f64") {
|
|
2482
2582
|
const skey = { kind: "toString", operand: key, type: STRING, loc: key.loc };
|
|
@@ -2484,12 +2584,12 @@ export function lowerOptionalChain(L, expr) {
|
|
|
2484
2584
|
}
|
|
2485
2585
|
}
|
|
2486
2586
|
// The METHOD-call step (`rawName?.match(re)` on a dyn value): a
|
|
2487
|
-
// nullish receiver short-circuits to the undefined
|
|
2587
|
+
// nullish receiver short-circuits to the undefined dyn singleton
|
|
2488
2588
|
// (dyn represents undefined directly); anything else runs the
|
|
2489
2589
|
// VALIDATED dynamic dispatch — the same one the truthy-guarded
|
|
2490
2590
|
// spelling (`v ? v.match(...) : null`) compiles to, Node-shaped
|
|
2491
2591
|
// TypeError on a kind mismatch included. The dispatch's static
|
|
2492
|
-
// result converts back into the
|
|
2592
|
+
// result converts back into the checked-dynamic tree (the chain's checker type is
|
|
2493
2593
|
// the error-any world's `any`, so dyn IS its representation).
|
|
2494
2594
|
if (ts.isCallExpression(expr) &&
|
|
2495
2595
|
ts.isPropertyAccessExpression(dotNode) &&
|
|
@@ -2660,7 +2760,7 @@ export function finishOptionalChain(L, expr, id, receiver, body, loc) {
|
|
|
2660
2760
|
}
|
|
2661
2761
|
// A dyn body (`pricing?.[key]` — an unknown-valued index-signature
|
|
2662
2762
|
// read) stays dyn: `unknown | undefined` IS unknown, and dyn represents
|
|
2663
|
-
// undefined directly (the unit path yields the undefined
|
|
2763
|
+
// undefined directly (the unit path yields the undefined dyn value).
|
|
2664
2764
|
if (body.type.kind === "dyn") {
|
|
2665
2765
|
return { kind: "optChain", id, receiver, body, type: DYN, loc };
|
|
2666
2766
|
}
|
|
@@ -2716,8 +2816,8 @@ export function ensureBool(L, e, node) {
|
|
|
2716
2816
|
return { kind: "toBool", operand: e, type: BOOL, loc: e.loc };
|
|
2717
2817
|
}
|
|
2718
2818
|
if (e.type.kind === "dyn") {
|
|
2719
|
-
// ToBoolean over the
|
|
2720
|
-
//
|
|
2819
|
+
// ToBoolean over the checked-dynamic tree (`if (pkg)` on a JSON.parse result): every
|
|
2820
|
+
// dyn kind has a JS-exact answer — the truthy dynTest reads the kind
|
|
2721
2821
|
// tag (plus the scalar payload for number/string).
|
|
2722
2822
|
return { kind: "dynTest", test: "truthy", value: e, type: BOOL, loc: e.loc };
|
|
2723
2823
|
}
|
|
@@ -2743,7 +2843,7 @@ export function ensureBool(L, e, node) {
|
|
|
2743
2843
|
L.badType(node, L.typeOf(node));
|
|
2744
2844
|
}
|
|
2745
2845
|
/** Truthiness needs a ToBoolean per arm: dyn/caught arms have none (a
|
|
2746
|
-
* dynamic ToBoolean over the
|
|
2846
|
+
* dynamic ToBoolean over the checked-dynamic tree / the snapshot box) — fence those
|
|
2747
2847
|
* unions; every other arm kind is answerable (units false, scalars and
|
|
2748
2848
|
* strings by value, refs true, jsval by the engine). */
|
|
2749
2849
|
export function requireTruthyUnion(L, unionId, node) {
|
|
@@ -2772,7 +2872,7 @@ export function lowerIntrinsicProperty(L, expr) {
|
|
|
2772
2872
|
return null;
|
|
2773
2873
|
// A never-tainted JS receiver type lowered checked-dynamic
|
|
2774
2874
|
// (neverTaintedJsType — `cmd.length` on `const cmd = ['pwd', []]`):
|
|
2775
|
-
// stand down so the
|
|
2875
|
+
// stand down so the dyn keyed read below the chain answers, instead
|
|
2776
2876
|
// of an array libCall over a dyn receiver hitting the boundary fence.
|
|
2777
2877
|
let kind = neverTaintedJsType(L, expr.expression, L.typeOf(expr.expression))
|
|
2778
2878
|
? undefined
|
|
@@ -2994,8 +3094,8 @@ export function lowerIntrinsicProperty(L, expr) {
|
|
|
2994
3094
|
// No ArrayBuffer VALUE exists — `.buffer` compiles only in the
|
|
2995
3095
|
// one composed position that consumes it, the DataView
|
|
2996
3096
|
// constructor's first argument (lowerDataViewNew peels it).
|
|
2997
|
-
L.unsupported("SC1090", expr, "'.buffer' outside new DataView(x.buffer, ...)
|
|
2998
|
-
"no free-standing ArrayBuffer value exists
|
|
3097
|
+
L.unsupported("SC1090", expr, "'.buffer' outside new DataView(x.buffer, ...) / Buffer.from(x.buffer, ...) " +
|
|
3098
|
+
"(no free-standing ArrayBuffer value exists; subarray() answers an aliasing view directly)");
|
|
2999
3099
|
}
|
|
3000
3100
|
if (name === "slice" || name === "subarray" || name === "set" || name === "toString") {
|
|
3001
3101
|
L.unsupported("SC1090", expr, `typed-array methods as values (call '${name}' directly)`);
|
|
@@ -3036,8 +3136,8 @@ export function lowerIntrinsicProperty(L, expr) {
|
|
|
3036
3136
|
return { kind: "jsExit", value: read, type: F64, loc: locOf(expr) };
|
|
3037
3137
|
}
|
|
3038
3138
|
// A CHECKED-DYNAMIC value behind an array-typed checker spelling
|
|
3039
|
-
// (`Object.keys(u).length` — the dyn walk answers a
|
|
3040
|
-
// tsc spells string[]): the runtime-world dispatch — a
|
|
3139
|
+
// (`Object.keys(u).length` — the dyn walk answers a dyn array while
|
|
3140
|
+
// tsc spells string[]): the runtime-world dispatch — a dyn keyed
|
|
3041
3141
|
// read validated into the declared number (a lying length throws
|
|
3042
3142
|
// the catchable TypeError, the dynCheck stance).
|
|
3043
3143
|
if (receiver.type.kind === "dyn") {
|
|
@@ -3080,7 +3180,7 @@ export function lowerArrayLiteral(L, expr, expected) {
|
|
|
3080
3180
|
// A JS literal whose OWN inferred type is never-tainted
|
|
3081
3181
|
// (neverTaintedJsType — the evolving `const gb = []`, the mixed
|
|
3082
3182
|
// command tuple `['pwd', []]`) carries no element information: route
|
|
3083
|
-
// it to the
|
|
3183
|
+
// it to the checked-dynamic tree fallback below rather than letting never's f64
|
|
3084
3184
|
// representation build a static number array (a later dyn push would
|
|
3085
3185
|
// throw "expected number at $, got string"; the tuple's union arm
|
|
3086
3186
|
// would re-tag as number[] and fence). A REAL slot still wins:
|
|
@@ -3210,14 +3310,14 @@ export function lowerArrayLiteral(L, expr, expected) {
|
|
|
3210
3310
|
if (!mapped || mapped.kind !== "array") {
|
|
3211
3311
|
// The JS declaration fallback, literal-side: an element type with no
|
|
3212
3312
|
// static home (a string | string[] mixed command tuple, an evolving
|
|
3213
|
-
// []) builds as a
|
|
3313
|
+
// []) builds as a dyn ARRAY — one dyn value whose elements each
|
|
3214
3314
|
// convert through the usual boundary (dynFrom's JSON-safe domain);
|
|
3215
3315
|
// an element that cannot convert fences per element. length/index
|
|
3216
|
-
// reads and dynamic consumers ride the keyed-
|
|
3316
|
+
// reads and dynamic consumers ride the keyed-dyn paths. TS literals
|
|
3217
3317
|
// take the same build when the slot ITSELF is checked-dynamic — an
|
|
3218
3318
|
// `unknown[]` annotation or a collapsed `(string | object)[]` maps
|
|
3219
3319
|
// to DYN wholesale now (mapType's dyn-element array rule), so the
|
|
3220
|
-
// literal IS the
|
|
3320
|
+
// literal IS the dyn array.
|
|
3221
3321
|
if (isJsSourceFile(expr.getSourceFile()) || mapped?.kind === "dyn") {
|
|
3222
3322
|
const elems = expr.elements.map((el) => {
|
|
3223
3323
|
if (ts.isSpreadElement(el)) {
|
|
@@ -3503,12 +3603,12 @@ function propNameText(L, name) {
|
|
|
3503
3603
|
return name.text;
|
|
3504
3604
|
}
|
|
3505
3605
|
/** The runtime-keyed JS object literal (a computed key that doesn't fold):
|
|
3506
|
-
* builds a
|
|
3606
|
+
* builds a dyn object member-by-member. Keys evaluate before their values,
|
|
3507
3607
|
* properties in source order — JS's object-literal evaluation exactly.
|
|
3508
3608
|
* Identifier/string keys are compile-time strings; numeric keys take
|
|
3509
3609
|
* their canonical number string (`{ 0x10: v }` stores "16" — JS's
|
|
3510
3610
|
* ToPropertyKey); computed keys evaluate and pass through ToString (the
|
|
3511
|
-
*
|
|
3611
|
+
* dyn's String() for dyn operands — `{ [field]: v }` where field is a
|
|
3512
3612
|
* checked-dynamic param). Values convert through the usual dyn boundary
|
|
3513
3613
|
* (dynFrom's domain, functions box); a value with no dyn representation
|
|
3514
3614
|
* fences per property. Spreads, accessors, and methods stay fenced —
|
|
@@ -3601,7 +3701,7 @@ export function lowerDynObjectLiteral(L, expr) {
|
|
|
3601
3701
|
L.unsupported("SC1101", valueExpr, `holding '${L.fmt(v.type)}' values in a runtime-keyed (computed-key) object literal`);
|
|
3602
3702
|
}
|
|
3603
3703
|
catch (err) {
|
|
3604
|
-
// A member value the
|
|
3704
|
+
// A member value the checked-dynamic tree cannot hold — a func whose signature
|
|
3605
3705
|
// cannot box, an island ('any') handle, a record carrying func
|
|
3606
3706
|
// fields (the export aggregate's typed utilities and npm-handle
|
|
3607
3707
|
// members): the same call-time fence deferral as an unlowerable
|
|
@@ -3671,7 +3771,7 @@ function fenceClosureProbe(L, node, slotType, attempt) {
|
|
|
3671
3771
|
return null;
|
|
3672
3772
|
}
|
|
3673
3773
|
// The trap's ABI: the field's func slot when one exists; otherwise (the
|
|
3674
|
-
//
|
|
3774
|
+
// dyn-object literal path — the value boxes into dyn) the all-dyn
|
|
3675
3775
|
// signature of the lambda's own arity, which canBoxFuncIntoDyn always
|
|
3676
3776
|
// admits.
|
|
3677
3777
|
const fieldType = slotType !== undefined && slotType.kind === "func"
|
|
@@ -3814,7 +3914,7 @@ export function lowerObjectLiteral(L, expr) {
|
|
|
3814
3914
|
const loc = locOf(expr);
|
|
3815
3915
|
// The RUNTIME-KEYED literal (JS): a computed key that doesn't fold to a
|
|
3816
3916
|
// compile-time string means the literal's shape is not a compile-time
|
|
3817
|
-
// fact — no record shape can hold it. The whole literal builds as a
|
|
3917
|
+
// fact — no record shape can hold it. The whole literal builds as a dyn
|
|
3818
3918
|
// object instead (`{ [field]: criteria, actual: 0, ... }` —
|
|
3819
3919
|
// test/common's _mustCallInner context), where keys are runtime string
|
|
3820
3920
|
// values. TypeScript keeps the record world and its fence: this shape
|
|
@@ -4063,13 +4163,13 @@ export function lowerObjectLiteral(L, expr) {
|
|
|
4063
4163
|
const shapeId = L.shapes.intern(fields.map((f) => ({ name: f.name, type: f.value.type })));
|
|
4064
4164
|
return { kind: "recordLit", fields, type: { kind: "record", shapeId }, loc };
|
|
4065
4165
|
}
|
|
4066
|
-
// The JS declaration fallback, literal-side (the
|
|
4166
|
+
// The JS declaration fallback, literal-side (the checked-dynamic tree-array rule's
|
|
4067
4167
|
// object form): a literal whose shape has no static home — an
|
|
4068
4168
|
// unmappable contextual type over unmappable own fields (the
|
|
4069
4169
|
// PropertyDescriptorMap argument of Object.defineProperties, nested
|
|
4070
|
-
// descriptor records with `any` values) — builds as a
|
|
4170
|
+
// descriptor records with `any` values) — builds as a dyn OBJECT:
|
|
4071
4171
|
// each field converts through the usual dyn boundary, and dynamic
|
|
4072
|
-
// consumers ride the keyed-
|
|
4172
|
+
// consumers ride the keyed-dyn paths. TypeScript keeps the fence.
|
|
4073
4173
|
if ((!mapped || mapped.kind === "dyn") && isJsSourceFile(expr.getSourceFile())) {
|
|
4074
4174
|
return lowerDynObjectLiteral(L, expr);
|
|
4075
4175
|
}
|
|
@@ -4335,8 +4435,8 @@ export function lowerObjectLiteral(L, expr) {
|
|
|
4335
4435
|
const recArm = recArms[0];
|
|
4336
4436
|
const recTag = def.arms.indexOf(recArm);
|
|
4337
4437
|
// A checked-dynamic VALUE under the union-mapped checker type
|
|
4338
|
-
// (a JS
|
|
4339
|
-
// union tags a
|
|
4438
|
+
// (a JS dyn-holding binding): the present/absent desugar tests
|
|
4439
|
+
// union tags a dyn box does not carry — fence honestly instead
|
|
4340
4440
|
// of the validator's ICE.
|
|
4341
4441
|
const probedSrc = srcLowered ?? probeLower(L, srcNode);
|
|
4342
4442
|
if (probedSrc?.type.kind === "dyn") {
|
|
@@ -4518,11 +4618,11 @@ export function lowerObjectLiteral(L, expr) {
|
|
|
4518
4618
|
throw new PoisonError();
|
|
4519
4619
|
}
|
|
4520
4620
|
const obj = srcLowered ?? L.lowerExpr(srcNode);
|
|
4521
|
-
// A record-mapped CHECKER type whose VALUE lives in the
|
|
4621
|
+
// A record-mapped CHECKER type whose VALUE lives in the checked-dynamic tree (a
|
|
4522
4622
|
// JS file-scope object-literal global): read each field from
|
|
4523
|
-
// the
|
|
4623
|
+
// the checked-dynamic tree (dynKeyGet) and VALIDATE it into the source shape's
|
|
4524
4624
|
// field type (dynCheck) — the checked-dynamic member-read
|
|
4525
|
-
// discipline. A missing key answers the
|
|
4625
|
+
// discipline. A missing key answers the dyn undefined, exactly
|
|
4526
4626
|
// the undefined-armed optional's absent case; a mismatched
|
|
4527
4627
|
// runtime value throws the catchable TypeError, never a silent
|
|
4528
4628
|
// wrong copy. Runtime-ADDED keys drop — width subtyping in
|
|
@@ -4530,7 +4630,7 @@ export function lowerObjectLiteral(L, expr) {
|
|
|
4530
4630
|
let value;
|
|
4531
4631
|
if (obj.type.kind === "dyn") {
|
|
4532
4632
|
if (!canDynCheckTo(f.type, (id) => L.shapes.get(id), (id) => L.unions.get(id))) {
|
|
4533
|
-
L.unsupported("SC1100", prop, `object spread of a checked-dynamic source whose field '${f.name}' ('${L.fmt(f.type)}') cannot validate out of the
|
|
4633
|
+
L.unsupported("SC1100", prop, `object spread of a checked-dynamic source whose field '${f.name}' ('${L.fmt(f.type)}') cannot validate out of the checked-dynamic tree (copy the fields explicitly)`);
|
|
4534
4634
|
}
|
|
4535
4635
|
value = {
|
|
4536
4636
|
kind: "dynCheck",
|
|
@@ -4669,7 +4769,7 @@ export function lowerObjectLiteral(L, expr) {
|
|
|
4669
4769
|
// lowering: a union field with one array-family arm builds the
|
|
4670
4770
|
// literal AS that arm (lowerExprExpecting's IR-directed rule —
|
|
4671
4771
|
// the option-table `default: [{ value: [] }]` shape), where the
|
|
4672
|
-
// bare lowering would take the JS
|
|
4772
|
+
// bare lowering would take the JS dyn fallback and fence.
|
|
4673
4773
|
let init = prop.initializer;
|
|
4674
4774
|
while (ts.isParenthesizedExpression(init))
|
|
4675
4775
|
init = init.expression;
|
|
@@ -4714,7 +4814,7 @@ export function lowerObjectLiteral(L, expr) {
|
|
|
4714
4814
|
}
|
|
4715
4815
|
if (!fieldType) {
|
|
4716
4816
|
// Overflow entry: the value flows into the index signature's value
|
|
4717
|
-
// slot — dyn slots take a
|
|
4817
|
+
// slot — dyn slots take a dyn conversion (dynFrom), typed slots the
|
|
4718
4818
|
// ordinary coercion path. Later duplicates override (map semantics
|
|
4719
4819
|
// are last-write-wins already; a duplicate literal key is a tsc
|
|
4720
4820
|
// error anyway).
|
|
@@ -4756,7 +4856,7 @@ export function lowerObjectLiteral(L, expr) {
|
|
|
4756
4856
|
for (const f of shape.fields) {
|
|
4757
4857
|
if (provided.has(f.name))
|
|
4758
4858
|
continue;
|
|
4759
|
-
// 'unknown' fields complete with the
|
|
4859
|
+
// 'unknown' fields complete with the dyn undefined — the absent
|
|
4760
4860
|
// property reads as undefined in Node, and a dyn slot holds
|
|
4761
4861
|
// exactly that (the options-record call shape against
|
|
4762
4862
|
// `{ plugins: unknown, ... }` — a JS caller the checker admits).
|
|
@@ -5117,7 +5217,7 @@ export function lowerElementAccess(L, expr) {
|
|
|
5117
5217
|
// A never-tainted JS receiver type (neverTaintedJsType — `cmd[1]` on
|
|
5118
5218
|
// `const cmd = ['pwd', []]`, whose binding lowered checked-dynamic)
|
|
5119
5219
|
// dispatches as unmapped so the dyn-receiver branch below reads
|
|
5120
|
-
// through the
|
|
5220
|
+
// through the checked-dynamic tree instead of dynChecking into never's f64 residue.
|
|
5121
5221
|
const receiverIr = neverTaintedJsType(L, expr.expression, L.typeOf(expr.expression))
|
|
5122
5222
|
? null
|
|
5123
5223
|
: L.mapTypeOf(L.typeOf(expr.expression));
|
|
@@ -5125,7 +5225,7 @@ export function lowerElementAccess(L, expr) {
|
|
|
5125
5225
|
// Dispatch follows the RUNTIME world (383(d)): a checker-'any'
|
|
5126
5226
|
// receiver whose value LOWERED checked-dynamic (`bag.list[0]` where
|
|
5127
5227
|
// bag.list is a routed keyed read off a wrapped island value) takes
|
|
5128
|
-
// the
|
|
5228
|
+
// the dyn keyed read below — the routed JSVAL arm reads the real
|
|
5129
5229
|
// engine element; a jsval-lowered receiver keeps the island read.
|
|
5130
5230
|
const recv = L.lowerExpr(expr.expression);
|
|
5131
5231
|
if (recv.type.kind !== "dyn")
|
|
@@ -5204,7 +5304,7 @@ export function lowerElementAccess(L, expr) {
|
|
|
5204
5304
|
}
|
|
5205
5305
|
}
|
|
5206
5306
|
// `pkg["k"]` / `scripts[name]` / `scopeMatch[1]` on a dyn receiver (a
|
|
5207
|
-
// JSON.parse result): the
|
|
5307
|
+
// JSON.parse result): the dyn keyed read, exactly the dot form.
|
|
5208
5308
|
// NUMBER-typed indices convert through ToString first — JS property
|
|
5209
5309
|
// keys are strings, and the canonical number text answers array
|
|
5210
5310
|
// indices in the helper (fractions, negatives, and NaN read as
|
|
@@ -5314,7 +5414,7 @@ export function lowerElementAccess(L, expr) {
|
|
|
5314
5414
|
return islandElementRead(L, expr, arr);
|
|
5315
5415
|
// The checker sees an array but the VALUE lowered checked-dynamic (a
|
|
5316
5416
|
// jsdoc-typed default export from a .js module — signature 14): the
|
|
5317
|
-
// read rides the dynCheck boundary when the array shape is one the
|
|
5417
|
+
// read rides the dynCheck boundary when the array shape is one the checked-dynamic tree
|
|
5318
5418
|
// can validate, and fences by name when it is not — an arrayGet over a
|
|
5319
5419
|
// dyn receiver is never emitted (the validator ICE).
|
|
5320
5420
|
if (arr.type.kind === "dyn") {
|
|
@@ -5390,7 +5490,7 @@ function islandElementRead(L, expr, obj) {
|
|
|
5390
5490
|
* the index signature's value type (dyn for `unknown`), or its
|
|
5391
5491
|
* undefined-armed union under noUncheckedIndexedAccess. Every declared
|
|
5392
5492
|
* field must be able to SURFACE as that type (equal, an arm of it, or —
|
|
5393
|
-
* for dyn results — JSON-safe for the
|
|
5493
|
+
* for dyn results — JSON-safe for the dyn conversion); shapes mixing in
|
|
5394
5494
|
* fields outside that stay fenced. Declared-only shapes support reads
|
|
5395
5495
|
* whose key type proves membership (tsc's keyof check): all fields must
|
|
5396
5496
|
* share the result type, and a smuggled miss traps. */
|
|
@@ -5409,9 +5509,9 @@ export function lowerRecordKeyRead(L, expr, shapeId, shape) {
|
|
|
5409
5509
|
// The receiver lowers FIRST (both branches below read it, and JS
|
|
5410
5510
|
// evaluates the receiver before the key).
|
|
5411
5511
|
const obj = L.lowerExpr(expr.expression);
|
|
5412
|
-
// A record-mapped CHECKER type over a VALUE living in the
|
|
5512
|
+
// A record-mapped CHECKER type over a VALUE living in the checked-dynamic tree (a JS
|
|
5413
5513
|
// file-scope object-literal global): the checked-dynamic keyed read —
|
|
5414
|
-
// dynKeyGet against the runtime keys (a missing key answers the
|
|
5514
|
+
// dynKeyGet against the runtime keys (a missing key answers the checked-dynamic tree
|
|
5415
5515
|
// undefined, exactly JS); consumers validate (dynCheck) where a
|
|
5416
5516
|
// static type is required, the member-read discipline.
|
|
5417
5517
|
if (obj.type.kind === "dyn") {
|
|
@@ -5520,8 +5620,8 @@ export function recordKeyTypeLiteralText(L, node) {
|
|
|
5520
5620
|
}
|
|
5521
5621
|
/** Can every value a dynamic key can reach (declared fields + the
|
|
5522
5622
|
* overflow) surface as the read's result type? dyn results need
|
|
5523
|
-
*
|
|
5524
|
-
* the top — an optional field's undefined becomes the undefined
|
|
5623
|
+
* dyn-convertible fields (JSON-safe, with the undefined arm allowed at
|
|
5624
|
+
* the top — an optional field's undefined becomes the undefined dyn
|
|
5525
5625
|
* value, exactly what the missing-key path produces); typed results need
|
|
5526
5626
|
* each field to be the type itself or one of its arms, and the overflow
|
|
5527
5627
|
* value to be the type or wrappable into it. */
|
|
@@ -5589,7 +5689,7 @@ export function lowerElementWrite(L, expr) {
|
|
|
5589
5689
|
if (receiverIr?.kind === "jsval") {
|
|
5590
5690
|
const obj = L.lowerExpr(target.expression);
|
|
5591
5691
|
// Dispatch follows the RUNTIME world (383(d)): a checker-'any'
|
|
5592
|
-
// receiver whose value LOWERED checked-dynamic takes the
|
|
5692
|
+
// receiver whose value LOWERED checked-dynamic takes the dyn keyed
|
|
5593
5693
|
// write — the routed JSVAL arm lands it on the real engine object.
|
|
5594
5694
|
if (obj.type.kind === "dyn") {
|
|
5595
5695
|
const loc = locOf(expr);
|
|
@@ -5616,12 +5716,12 @@ export function lowerElementWrite(L, expr) {
|
|
|
5616
5716
|
return { kind: "exprStmt", expr: { kind: "jsOp", op: "setIdx", args: [obj, key, value], type: VOID, loc }, loc };
|
|
5617
5717
|
}
|
|
5618
5718
|
// A checked-dynamic receiver TYPE (`unknown[]` and the collapsed
|
|
5619
|
-
// `(string | object)[]` map to DYN wholesale now): the
|
|
5719
|
+
// `(string | object)[]` map to DYN wholesale now): the dyn keyed
|
|
5620
5720
|
// write — dyn.keySet. Number keys canonicalize through the JS-exact
|
|
5621
5721
|
// formatter; ARR receivers take canonical index keys as element
|
|
5622
5722
|
// set/extend (undefined-hole padding, JS's length growth exactly),
|
|
5623
5723
|
// OBJ receivers set the member, and the runtime throws Node's
|
|
5624
|
-
// TypeErrors on every other kind. Values convert into the
|
|
5724
|
+
// TypeErrors on every other kind. Values convert into the checked-dynamic tree;
|
|
5625
5725
|
// unconvertible ones fence per site (the dynFrom stance).
|
|
5626
5726
|
if (receiverIr?.kind === "dyn") {
|
|
5627
5727
|
const obj = L.lowerExpr(target.expression);
|
|
@@ -5635,7 +5735,7 @@ export function lowerElementWrite(L, expr) {
|
|
|
5635
5735
|
}
|
|
5636
5736
|
const value = L.coerceToExpected(L.lowerExpr(expr.right), DYN);
|
|
5637
5737
|
if (value.type.kind !== "dyn") {
|
|
5638
|
-
L.unsupported("SC1101", expr.right, `storing '${L.fmt(value.type)}' values in a checked-dynamic array (the value cannot convert into the
|
|
5738
|
+
L.unsupported("SC1101", expr.right, `storing '${L.fmt(value.type)}' values in a checked-dynamic array (the value cannot convert into the checked-dynamic tree)`);
|
|
5639
5739
|
}
|
|
5640
5740
|
return { kind: "exprStmt", expr: { kind: "libCall", fn: "dyn.keySet", args: [obj, key, value], type: VOID, loc }, loc };
|
|
5641
5741
|
}
|
|
@@ -5675,11 +5775,11 @@ export function lowerElementWrite(L, expr) {
|
|
|
5675
5775
|
const loc = locOf(expr);
|
|
5676
5776
|
return { kind: "exprStmt", expr: { kind: "jsOp", op: "setIdx", args: [obj, key, value], type: VOID, loc }, loc };
|
|
5677
5777
|
}
|
|
5678
|
-
// A record-mapped CHECKER type over a VALUE living in the
|
|
5778
|
+
// A record-mapped CHECKER type over a VALUE living in the checked-dynamic tree (a
|
|
5679
5779
|
// JS file-scope object-literal global): the checked-dynamic keyed
|
|
5680
5780
|
// write — dyn.keySet (later writes win, insertion order; Node's
|
|
5681
5781
|
// TypeErrors on non-object receivers), the value converting into
|
|
5682
|
-
// the
|
|
5782
|
+
// the checked-dynamic tree. Literal and runtime keys alike: the checked-dynamic tree's key set is
|
|
5683
5783
|
// open, so no declared-field collision analysis applies.
|
|
5684
5784
|
if (obj.type.kind === "dyn") {
|
|
5685
5785
|
const loc = locOf(expr);
|
|
@@ -5694,7 +5794,7 @@ export function lowerElementWrite(L, expr) {
|
|
|
5694
5794
|
}
|
|
5695
5795
|
const value = L.coerceToExpected(L.lowerExpr(expr.right), DYN);
|
|
5696
5796
|
if (value.type.kind !== "dyn") {
|
|
5697
|
-
L.unsupported("SC1101", expr.right, `storing '${L.fmt(value.type)}' values in a checked-dynamic object (the value cannot convert into the
|
|
5797
|
+
L.unsupported("SC1101", expr.right, `storing '${L.fmt(value.type)}' values in a checked-dynamic object (the value cannot convert into the checked-dynamic tree)`);
|
|
5698
5798
|
}
|
|
5699
5799
|
return { kind: "exprStmt", expr: { kind: "libCall", fn: "dyn.keySet", args: [obj, key, value], type: VOID, loc }, loc };
|
|
5700
5800
|
}
|
|
@@ -5782,7 +5882,7 @@ export function lowerElementWrite(L, expr) {
|
|
|
5782
5882
|
// A LITERAL key naming no declared field is a pure overflow insert
|
|
5783
5883
|
// — no collision to validate. Runtime keys must be able to collide
|
|
5784
5884
|
// with every declared field: dyn slots validate through the
|
|
5785
|
-
// dynCheck walker (fields must be
|
|
5885
|
+
// dynCheck walker (fields must be dyn-convertible — the same
|
|
5786
5886
|
// convertibility the read needs); typed slots write through
|
|
5787
5887
|
// directly, so every field must BE the index-value type.
|
|
5788
5888
|
const overflowOnly = litKey !== null;
|
|
@@ -5821,7 +5921,7 @@ export function lowerElementWrite(L, expr) {
|
|
|
5821
5921
|
// `result[key] = value`): dyn.keySet, exactly the record-mapped dyn
|
|
5822
5922
|
// arm above — later writes win in insertion order, number keys
|
|
5823
5923
|
// canonicalize through the JS-exact formatter (ToPropertyKey's string
|
|
5824
|
-
// side), index keys on
|
|
5924
|
+
// side), index keys on dyn arrays set/extend elements, and non-object
|
|
5825
5925
|
// receivers throw Node's TypeErrors at runtime. An UNMAPPED checker
|
|
5826
5926
|
// type (static `any` — mapTypeOf answers null without --dynamic)
|
|
5827
5927
|
// probes the receiver's own lowered world: a dyn value takes the same
|
|
@@ -5844,7 +5944,7 @@ export function lowerElementWrite(L, expr) {
|
|
|
5844
5944
|
}
|
|
5845
5945
|
const value = L.coerceToExpected(L.lowerExpr(expr.right), DYN);
|
|
5846
5946
|
if (value.type.kind !== "dyn") {
|
|
5847
|
-
L.unsupported("SC1101", expr.right, `storing '${L.fmt(value.type)}' values in a checked-dynamic object (the value cannot convert into the
|
|
5947
|
+
L.unsupported("SC1101", expr.right, `storing '${L.fmt(value.type)}' values in a checked-dynamic object (the value cannot convert into the checked-dynamic tree)`);
|
|
5848
5948
|
}
|
|
5849
5949
|
return { kind: "exprStmt", expr: { kind: "libCall", fn: "dyn.keySet", args: [obj, key, value], type: VOID, loc }, loc };
|
|
5850
5950
|
}
|
|
@@ -5878,7 +5978,7 @@ export function ensureString(L, e, node) {
|
|
|
5878
5978
|
if (e.type.kind === "string")
|
|
5879
5979
|
return e;
|
|
5880
5980
|
if (e.type.kind === "dyn") {
|
|
5881
|
-
// String(u) / `${u}`: a runtime dispatch over the
|
|
5981
|
+
// String(u) / `${u}`: a runtime dispatch over the dyn kind — Node's
|
|
5882
5982
|
// String() exactly (undefined/null texts, JS number formatting,
|
|
5883
5983
|
// strings verbatim, arrays via join, objects as "[object Object]").
|
|
5884
5984
|
return { kind: "toString", operand: e, type: STRING, loc: e.loc };
|
|
@@ -6004,7 +6104,7 @@ export function lowerTemplate(L, expr) {
|
|
|
6004
6104
|
* documented divergence: a lying cast throws instead of corrupting
|
|
6005
6105
|
* memory);
|
|
6006
6106
|
* - dyn → anything else (closures, class instances, void) is rejected:
|
|
6007
|
-
* those types cannot be found inside a JSON
|
|
6107
|
+
* those types cannot be found inside a JSON dyn. */
|
|
6008
6108
|
/** `e as T` and the old-style assertion `<T>e` — one node shape (both
|
|
6009
6109
|
* carry `.type` and `.expression`), one lowering. */
|
|
6010
6110
|
export function lowerAsExpression(L, expr) {
|
|
@@ -6108,7 +6208,7 @@ export function lowerAsExpression(L, expr) {
|
|
|
6108
6208
|
return inner; // `as unknown`: erasure
|
|
6109
6209
|
if (target.kind === "void" || !L.jsonSafe(target)) {
|
|
6110
6210
|
// Bare undefined-armed targets pass when every OTHER arm is
|
|
6111
|
-
// JSON-safe: the
|
|
6211
|
+
// JSON-safe: the checked-dynamic tree holds a first-class undefined value now
|
|
6112
6212
|
// (index-signature overflow reads produce it for missing keys), and
|
|
6113
6213
|
// the undefined arm matches exactly it — `p[key] as string |
|
|
6114
6214
|
// undefined` is the missing-key idiom. Parsed JSON still never
|
|
@@ -6117,7 +6217,7 @@ export function lowerAsExpression(L, expr) {
|
|
|
6117
6217
|
if (target.kind === "union" && L.dynConvertible(target)) {
|
|
6118
6218
|
return { kind: "dynCheck", value: inner, type: target, loc: locOf(expr) };
|
|
6119
6219
|
}
|
|
6120
|
-
// Uint8Array targets: the
|
|
6220
|
+
// Uint8Array targets: the checked-dynamic tree carries a bytes kind now (converted
|
|
6121
6221
|
// stdin chunks) — the extraction validates the kind and copies out.
|
|
6122
6222
|
if (target.kind === "bytes" && target.elem === "u8") {
|
|
6123
6223
|
return { kind: "dynCheck", value: inner, type: target, loc: locOf(expr) };
|
|
@@ -6140,7 +6240,7 @@ export function lowerAsExpression(L, expr) {
|
|
|
6140
6240
|
// — the errno-probing idiom): there is nothing to validate (every
|
|
6141
6241
|
// field is unknown, exactly what the dyn value already answers) and
|
|
6142
6242
|
// nothing to build — the cast is pure typing, so it ERASES and the
|
|
6143
|
-
// reads ride the
|
|
6243
|
+
// reads ride the dyn keyed read.
|
|
6144
6244
|
if (target.kind === "record") {
|
|
6145
6245
|
const shape = L.shapes.get(target.shapeId);
|
|
6146
6246
|
if (shape &&
|
|
@@ -6301,7 +6401,7 @@ export function lowerBinary(L, expr) {
|
|
|
6301
6401
|
}
|
|
6302
6402
|
// `events.defaultMaxListeners = v` — the module-property write
|
|
6303
6403
|
// Node validates (validateNumber(n, 'defaultMaxListeners', 0)):
|
|
6304
|
-
// the value crosses into the
|
|
6404
|
+
// the value crosses into the checked-dynamic tree and the runtime ladder throws
|
|
6305
6405
|
// ERR_INVALID_ARG_TYPE / ERR_OUT_OF_RANGE with Node's exact slot
|
|
6306
6406
|
// name; valid numbers apply. The expression's value is the RHS
|
|
6307
6407
|
// (JS's assignment yield).
|
|
@@ -6474,8 +6574,8 @@ export function lowerBinary(L, expr) {
|
|
|
6474
6574
|
}
|
|
6475
6575
|
if (left.type.kind === "dyn" || right.type.kind === "dyn") {
|
|
6476
6576
|
// A checked-dynamic operand (`fn.name || '<anonymous>'` —
|
|
6477
|
-
// test/common's _mustCallInner): both sides live in the
|
|
6478
|
-
// the deciding test is ToBoolean over the
|
|
6577
|
+
// test/common's _mustCallInner): both sides live in the checked-dynamic tree and
|
|
6578
|
+
// the deciding test is ToBoolean over the dyn kind
|
|
6479
6579
|
// (scr_dyn_truthy) — JS value semantics exactly, result dyn. The
|
|
6480
6580
|
// non-dyn side converts through the usual boundary; a value with
|
|
6481
6581
|
// no dyn representation keeps the fence.
|
|
@@ -6611,7 +6711,7 @@ export function lowerBinary(L, expr) {
|
|
|
6611
6711
|
const right = L.lowerExpr(expr.right);
|
|
6612
6712
|
if (left.type.kind === "dyn" || right.type.kind === "dyn") {
|
|
6613
6713
|
// The narrowing unit comparisons ARE answerable on unknown: `v ===
|
|
6614
|
-
// undefined` / `v !== null` test the
|
|
6714
|
+
// undefined` / `v !== null` test the dyn node's kind directly, and
|
|
6615
6715
|
// tsc's control flow narrows the branches (reads then bridge through
|
|
6616
6716
|
// maybeNarrow's validated extraction).
|
|
6617
6717
|
if (op === ts.SyntaxKind.EqualsEqualsEqualsToken ||
|
|
@@ -6631,14 +6731,14 @@ export function lowerBinary(L, expr) {
|
|
|
6631
6731
|
}
|
|
6632
6732
|
// dyn vs SCALAR strict equality (`value !== ""`, `u === 5` — the
|
|
6633
6733
|
// normalizePricing filter): a guarded kind test + payload compare,
|
|
6634
|
-
// JS-exact (strict equality never coerces; a non-matching
|
|
6734
|
+
// JS-exact (strict equality never coerces; a non-matching dyn kind
|
|
6635
6735
|
// answers false). dyn vs dyn keeps the fence below.
|
|
6636
6736
|
const dynSide = left.type.kind === "dyn" ? left : right;
|
|
6637
6737
|
const scalarSide = dynSide === left ? right : left;
|
|
6638
6738
|
if (dynSide.type.kind === "dyn" &&
|
|
6639
6739
|
(scalarSide.type.kind === "f64" || scalarSide.type.kind === "string" || scalarSide.type.kind === "bool" ||
|
|
6640
6740
|
// dyn vs dyn (`context.actual !== context.exact` —
|
|
6641
|
-
// test/common's exit accounting): the runtime's whole-
|
|
6741
|
+
// test/common's exit accounting): the runtime's whole-dyn
|
|
6642
6742
|
// strict equality — scalars by value, units by kind,
|
|
6643
6743
|
// reference kinds by node identity (scr_dyn_strict_eq).
|
|
6644
6744
|
scalarSide.type.kind === "dyn")) {
|
|
@@ -6693,7 +6793,7 @@ export function lowerBinary(L, expr) {
|
|
|
6693
6793
|
}
|
|
6694
6794
|
if (other.type.kind === "string") {
|
|
6695
6795
|
// String-context `+`: JS's answer is String(unknown) — the
|
|
6696
|
-
// JS-exact
|
|
6796
|
+
// JS-exact dyn walker (numbers format, arrays join, objects
|
|
6697
6797
|
// print [object Object], handles the same) — never a checked
|
|
6698
6798
|
// cast: `'status ' + res.statusCode` concatenates like Node.
|
|
6699
6799
|
const strOf = (e) => e.type.kind === "dyn" ? { kind: "toString", operand: e, type: STRING, loc: e.loc } : e;
|
|
@@ -6711,7 +6811,7 @@ export function lowerBinary(L, expr) {
|
|
|
6711
6811
|
L.anyOpFence(`the '${ts.tokenToString(op) ?? ts.SyntaxKind[op]}' operator`, expr);
|
|
6712
6812
|
}
|
|
6713
6813
|
// tsc allows ===/!== on unknown (arithmetic/comparisons it rejects
|
|
6714
|
-
// itself); a dynamic equality would need a
|
|
6814
|
+
// itself); a dynamic equality would need a dyn walk — validate first.
|
|
6715
6815
|
L.unsupported("SC1100", expr, "operators on 'unknown' values");
|
|
6716
6816
|
}
|
|
6717
6817
|
// Operators over 'any' execute in the island with JS-exact semantics
|
|
@@ -7249,7 +7349,7 @@ export function lowerUnionTypeofTest(L, expr, loc) {
|
|
|
7249
7349
|
return null;
|
|
7250
7350
|
}
|
|
7251
7351
|
/** `typeof v === "lit"` / `typeof v !== "lit"` where v is `unknown`-typed
|
|
7252
|
-
* (dyn): the runtime kind test over the
|
|
7352
|
+
* (dyn): the runtime kind test over the dyn node's tag. Only the kinds a
|
|
7253
7353
|
* later read can bridge narrow ("string"/"number"/"boolean") plus
|
|
7254
7354
|
* "undefined" (no payload to read); "object"/"function" tests keep the
|
|
7255
7355
|
* fence — an object-narrowed `unknown` read has no lowering anyway (a
|
|
@@ -7333,7 +7433,7 @@ export function lowerDynTypeofTest(L, expr, loc) {
|
|
|
7333
7433
|
loc,
|
|
7334
7434
|
};
|
|
7335
7435
|
}
|
|
7336
|
-
// `typeof v === "object"`: the
|
|
7436
|
+
// `typeof v === "object"`: the dyn answers exactly (object, array,
|
|
7337
7437
|
// bytes, and null kinds — JS's oldest wart preserved). tsc narrows
|
|
7338
7438
|
// the branch to `object | null`; reads past the narrow take the
|
|
7339
7439
|
// checked-cast path per site.
|
|
@@ -7347,7 +7447,7 @@ export function lowerDynTypeofTest(L, expr, loc) {
|
|
|
7347
7447
|
loc,
|
|
7348
7448
|
};
|
|
7349
7449
|
}
|
|
7350
|
-
// `typeof v === "function"`: a REAL runtime test since the
|
|
7450
|
+
// `typeof v === "function"`: a REAL runtime test since the checked-dynamic tree's
|
|
7351
7451
|
// function kind exists (boxed closures — the mustCall guard
|
|
7352
7452
|
// `if (typeof fn !== 'function') throw` answers honestly).
|
|
7353
7453
|
if (b.text === "function") {
|
|
@@ -7510,7 +7610,7 @@ export function probeLower(L, node) {
|
|
|
7510
7610
|
* over a hierarchy class, `typeof` over a primitive), the read bridges
|
|
7511
7611
|
* with a kind-unchecked `caughtNarrow` extraction (trust-the-checker,
|
|
7512
7612
|
* exactly like unionNarrow). Every OTHER read is the narrowness fence:
|
|
7513
|
-
* the binding's payload is a typed exception snapshot, not a dyn
|
|
7613
|
+
* the binding's payload is a typed exception snapshot, not a dyn, so
|
|
7514
7614
|
* un-narrowed uses have nothing sound to lower to. */
|
|
7515
7615
|
export function caughtRead(L, node, local, loc) {
|
|
7516
7616
|
const ref = { kind: "varRef", localId: local.id, type: CAUGHT, loc };
|
|
@@ -7526,10 +7626,10 @@ export function caughtRead(L, node, local, loc) {
|
|
|
7526
7626
|
}
|
|
7527
7627
|
}
|
|
7528
7628
|
// An UN-narrowed use — the occurrence still types `unknown` — converts
|
|
7529
|
-
// to a dyn
|
|
7629
|
+
// to a dyn value (`options.onError?.(error)` — the caught snapshot
|
|
7530
7630
|
// flowing into an unknown slot): the typed→unknown deep-copy stance
|
|
7531
7631
|
// extended to exception payloads. Error payloads keep their
|
|
7532
|
-
// observability (instanceof Error / .message / String() — the
|
|
7632
|
+
// observability (instanceof Error / .message / String() — the checked-dynamic tree's
|
|
7533
7633
|
// error encoding); other object payloads are type-erased at runtime and
|
|
7534
7634
|
// convert to the "[object Object]" approximation — SEMANTICS.md 67.
|
|
7535
7635
|
if (narrowed?.kind === "dyn") {
|
|
@@ -7635,14 +7735,14 @@ export function lowerInstanceOf(L, expr, loc) {
|
|
|
7635
7735
|
L.builtinStreamInfoOf(rhsMemberSymbol) ??
|
|
7636
7736
|
undefined;
|
|
7637
7737
|
if (!target) {
|
|
7638
|
-
// `u instanceof Uint8Array` on an `unknown` value: the
|
|
7738
|
+
// `u instanceof Uint8Array` on an `unknown` value: the checked-dynamic tree carries a
|
|
7639
7739
|
// bytes kind — one runtime tag test, and tsc's narrowing types the
|
|
7640
7740
|
// true branch (reads bridge through maybeNarrow's validated
|
|
7641
7741
|
// extraction, like the typeof tests). Node's Buffer IS a Uint8Array
|
|
7642
7742
|
// subclass and rides the same bytes kind, so both worlds answer true
|
|
7643
7743
|
// for Buffer payloads — Node-exact (the bytes kind's other
|
|
7644
7744
|
// divergences are SEMANTICS.md 45). Catch bindings stay out (their
|
|
7645
|
-
// payload is a typed snapshot, not a
|
|
7745
|
+
// payload is a typed snapshot, not a dyn).
|
|
7646
7746
|
if (ts.isIdentifier(expr.right) &&
|
|
7647
7747
|
L.isStdlibGlobal(expr.right, "Uint8Array") &&
|
|
7648
7748
|
!L.caughtLocalOf(expr.left)) {
|
|
@@ -7733,7 +7833,7 @@ export function lowerInstanceOf(L, expr, loc) {
|
|
|
7733
7833
|
};
|
|
7734
7834
|
}
|
|
7735
7835
|
const left = L.lowerExpr(expr.left);
|
|
7736
|
-
// `u instanceof Error` on an `unknown` value: the
|
|
7836
|
+
// `u instanceof Error` on an `unknown` value: the checked-dynamic tree's error encoding
|
|
7737
7837
|
// (the shape caughtToDyn builds for Error payloads — the reserved
|
|
7738
7838
|
// "%error" marker) answers the test, so a caught Error passed through
|
|
7739
7839
|
// an unknown slot narrows like Node (`error instanceof Error ?
|
|
@@ -7746,7 +7846,7 @@ export function lowerInstanceOf(L, expr, loc) {
|
|
|
7746
7846
|
return { kind: "dynTest", test: "error", value: left, type: BOOL, loc };
|
|
7747
7847
|
}
|
|
7748
7848
|
// `u instanceof TypeError` (and the other BUILTIN error classes) on a
|
|
7749
|
-
// dyn value: the from_error cache holds the
|
|
7849
|
+
// dyn value: the from_error cache holds the checked-dynamic tree↔error identity edge,
|
|
7750
7850
|
// so the runtime resolves the encoding back to its error and asks the
|
|
7751
7851
|
// vtable's stamped interval — exact for every error that crossed the
|
|
7752
7852
|
// boundary (a hand-built {%error} literal answers false: subclass
|
|
@@ -8039,7 +8139,7 @@ export function lowerInExpression(L, expr, loc) {
|
|
|
8039
8139
|
}
|
|
8040
8140
|
}
|
|
8041
8141
|
// A dyn receiver (`"portless" in pkg` after the `typeof pkg ===
|
|
8042
|
-
// "object"` guard): own-member presence on the
|
|
8142
|
+
// "object"` guard): own-member presence on the checked-dynamic tree — tsc admits `in`
|
|
8043
8143
|
// only on object-typed operands, so unit receivers are unreachable.
|
|
8044
8144
|
if (recv.type.kind === "dyn") {
|
|
8045
8145
|
return { kind: "dynHasKey", key, value: recv, type: BOOL, loc };
|
|
@@ -8255,11 +8355,14 @@ function lowerRuntimeKeyIn(L, expr, loc) {
|
|
|
8255
8355
|
}
|
|
8256
8356
|
/** A regex literal `/ab+c/gi` → regexLit (interned per (pattern, flags)
|
|
8257
8357
|
* by the backend). The TS parser has already syntax-checked the literal;
|
|
8258
|
-
* what remains here
|
|
8259
|
-
* declared-valid TS flags outside this slice)
|
|
8260
|
-
* (
|
|
8261
|
-
*
|
|
8262
|
-
*
|
|
8358
|
+
* what remains here is the flag-alphabet fence (d and v are
|
|
8359
|
+
* declared-valid TS flags outside this slice). Named capture groups
|
|
8360
|
+
* `(?<name>...)` and `\k<name>` backreferences compile — libregexp
|
|
8361
|
+
* executes them natively, replace templates resolve `$<name>` at
|
|
8362
|
+
* runtime, and `.groups` reads desugar at their access sites
|
|
8363
|
+
* (matchResultNamedGroupsOf). The engine validates the pattern itself
|
|
8364
|
+
* lazily at first use (SEMANTICS.md documents the divergence from
|
|
8365
|
+
* Node's parse-time SyntaxError). */
|
|
8263
8366
|
export function lowerRegexLiteral(L, expr) {
|
|
8264
8367
|
const text = expr.text;
|
|
8265
8368
|
const lastSlash = text.lastIndexOf("/");
|
|
@@ -8274,12 +8377,414 @@ export function lowerRegexLiteral(L, expr) {
|
|
|
8274
8377
|
: `the regex '${f}' flag`);
|
|
8275
8378
|
}
|
|
8276
8379
|
}
|
|
8277
|
-
// (?<name>...) — named groups; (?<= / (?<! are lookbehinds and fine.
|
|
8278
|
-
if (/\(\?<(?![=!])/.test(pattern)) {
|
|
8279
|
-
L.unsupported("SC1120", expr, "named capture groups in regex patterns");
|
|
8280
|
-
}
|
|
8281
8380
|
return { kind: "regexLit", pattern, flags, type: REGEX, loc: locOf(expr) };
|
|
8282
8381
|
}
|
|
8382
|
+
/** The pattern's named capture groups, in source order: each `(?<name>`
|
|
8383
|
+
* with its 1-based capture index (numbered and named groups share the
|
|
8384
|
+
* numbering — the index IS the match slice's element position). Null
|
|
8385
|
+
* when the scan can't answer confidently (an unterminated construct, a
|
|
8386
|
+
* `\u`-escaped group name) — callers fence rather than guess. Duplicate
|
|
8387
|
+
* names (ES2025 — valid across alternatives) appear once per
|
|
8388
|
+
* declaration; consumers pick the participating occurrence. The scan
|
|
8389
|
+
* only needs to track escapes, character classes, and `(` kinds; tsc
|
|
8390
|
+
* has already syntax-checked the literal, so a malformed pattern here
|
|
8391
|
+
* answers null and the engine's lazy compile reports it. */
|
|
8392
|
+
export function namedCaptureGroupsOfPattern(pattern) {
|
|
8393
|
+
const groups = [];
|
|
8394
|
+
let captureIndex = 0;
|
|
8395
|
+
let inClass = false;
|
|
8396
|
+
let i = 0;
|
|
8397
|
+
while (i < pattern.length) {
|
|
8398
|
+
const c = pattern[i];
|
|
8399
|
+
if (c === "\\") {
|
|
8400
|
+
i += 2;
|
|
8401
|
+
continue;
|
|
8402
|
+
}
|
|
8403
|
+
if (inClass) {
|
|
8404
|
+
if (c === "]")
|
|
8405
|
+
inClass = false;
|
|
8406
|
+
i++;
|
|
8407
|
+
continue;
|
|
8408
|
+
}
|
|
8409
|
+
if (c === "[") {
|
|
8410
|
+
inClass = true;
|
|
8411
|
+
i++;
|
|
8412
|
+
continue;
|
|
8413
|
+
}
|
|
8414
|
+
if (c !== "(") {
|
|
8415
|
+
i++;
|
|
8416
|
+
continue;
|
|
8417
|
+
}
|
|
8418
|
+
if (pattern[i + 1] !== "?") {
|
|
8419
|
+
captureIndex++;
|
|
8420
|
+
i++;
|
|
8421
|
+
continue;
|
|
8422
|
+
}
|
|
8423
|
+
// (?<name> captures; (?<= and (?<! are lookbehinds; every other
|
|
8424
|
+
// (?… form — (?:, (?=, (?!, modifier groups (?i: — is non-capturing.
|
|
8425
|
+
if (pattern[i + 2] === "<" && pattern[i + 3] !== "=" && pattern[i + 3] !== "!") {
|
|
8426
|
+
const gt = pattern.indexOf(">", i + 3);
|
|
8427
|
+
if (gt < 0)
|
|
8428
|
+
return null;
|
|
8429
|
+
const name = pattern.slice(i + 3, gt);
|
|
8430
|
+
if (name.includes("\\"))
|
|
8431
|
+
return null; // \u-escaped names: bytecode spells them decoded
|
|
8432
|
+
captureIndex++;
|
|
8433
|
+
groups.push({ name, index: captureIndex });
|
|
8434
|
+
i = gt + 1;
|
|
8435
|
+
continue;
|
|
8436
|
+
}
|
|
8437
|
+
i += 2;
|
|
8438
|
+
}
|
|
8439
|
+
return groups;
|
|
8440
|
+
}
|
|
8441
|
+
/** The statically-known regex PATTERN behind an expression: a regex
|
|
8442
|
+
* literal, a const local initialized with one (the crypto.js
|
|
8443
|
+
* `const regexp = /(?<m>\d+)/` shape), or `new RegExp("...")` over a
|
|
8444
|
+
* string literal (the cooked text IS the pattern). Null when the regex
|
|
8445
|
+
* only exists at runtime — .groups consumers fence there. */
|
|
8446
|
+
export function staticRegexPatternOf(L, e) {
|
|
8447
|
+
let expr = e;
|
|
8448
|
+
for (;;) {
|
|
8449
|
+
if (ts.isParenthesizedExpression(expr) || ts.isNonNullExpression(expr)) {
|
|
8450
|
+
expr = expr.expression;
|
|
8451
|
+
continue;
|
|
8452
|
+
}
|
|
8453
|
+
break;
|
|
8454
|
+
}
|
|
8455
|
+
if (ts.isRegularExpressionLiteral(expr)) {
|
|
8456
|
+
const text = expr.text;
|
|
8457
|
+
return text.slice(1, text.lastIndexOf("/"));
|
|
8458
|
+
}
|
|
8459
|
+
if (ts.isNewExpression(expr) &&
|
|
8460
|
+
ts.isIdentifier(expr.expression) &&
|
|
8461
|
+
expr.expression.text === "RegExp" &&
|
|
8462
|
+
L.isStdlibGlobal(expr.expression, "RegExp") &&
|
|
8463
|
+
expr.arguments !== undefined &&
|
|
8464
|
+
expr.arguments.length >= 1 &&
|
|
8465
|
+
(ts.isStringLiteral(expr.arguments[0]) || ts.isNoSubstitutionTemplateLiteral(expr.arguments[0]))) {
|
|
8466
|
+
return expr.arguments[0].text;
|
|
8467
|
+
}
|
|
8468
|
+
if (ts.isIdentifier(expr)) {
|
|
8469
|
+
const init = constInitializerOf(L, expr);
|
|
8470
|
+
if (init !== null)
|
|
8471
|
+
return staticRegexPatternOf(L, init);
|
|
8472
|
+
}
|
|
8473
|
+
return null;
|
|
8474
|
+
}
|
|
8475
|
+
/** The initializer behind a CONST identifier binding (a plain
|
|
8476
|
+
* VariableDeclaration under a const list) — the one-hop provenance step
|
|
8477
|
+
* the regex traces walk. Null for let/var (reassignable), params,
|
|
8478
|
+
* destructured bindings, and unresolvable names. */
|
|
8479
|
+
function constInitializerOf(L, ident) {
|
|
8480
|
+
const sym = L.resolveValueSymbol(ident);
|
|
8481
|
+
const decl = sym ? L.checker.valueDeclarationOf(sym) : undefined;
|
|
8482
|
+
if (decl === undefined || !ts.isVariableDeclaration(decl) || decl.initializer === undefined)
|
|
8483
|
+
return null;
|
|
8484
|
+
if (!ts.isVariableDeclarationList(decl.parent) || (decl.parent.flags & ts.NodeFlags.Const) === 0)
|
|
8485
|
+
return null;
|
|
8486
|
+
return decl.initializer;
|
|
8487
|
+
}
|
|
8488
|
+
/** The named-group table of the regex that PRODUCED a match-result
|
|
8489
|
+
* expression — the `.groups` desugar's provenance question. Traces
|
|
8490
|
+
* (through parens, `!`, and const bindings):
|
|
8491
|
+
* - `re.exec(s)` / `s.match(re)` → re's static pattern
|
|
8492
|
+
* - a matchAll ROW: `rows[i]`, the for-of binding over a direct
|
|
8493
|
+
* `s.matchAll(re)` or a stored const drain → re's static pattern
|
|
8494
|
+
* Answers null when the producing regex isn't statically known (the
|
|
8495
|
+
* caller keeps its fence), or the groups list (possibly empty — a
|
|
8496
|
+
* traced regex WITHOUT named groups is the Node-undefined case). */
|
|
8497
|
+
export function matchResultNamedGroupsOf(L, e) {
|
|
8498
|
+
const reExpr = matchProducerRegexOf(L, e);
|
|
8499
|
+
if (reExpr === null)
|
|
8500
|
+
return null;
|
|
8501
|
+
const pattern = staticRegexPatternOf(L, reExpr);
|
|
8502
|
+
if (pattern === null)
|
|
8503
|
+
return null;
|
|
8504
|
+
return namedCaptureGroupsOfPattern(pattern);
|
|
8505
|
+
}
|
|
8506
|
+
/** The regex EXPRESSION whose match produced `e` (see
|
|
8507
|
+
* matchResultNamedGroupsOf for the traced shapes). */
|
|
8508
|
+
function matchProducerRegexOf(L, e) {
|
|
8509
|
+
let expr = e;
|
|
8510
|
+
for (;;) {
|
|
8511
|
+
if (ts.isParenthesizedExpression(expr) || ts.isNonNullExpression(expr)) {
|
|
8512
|
+
expr = expr.expression;
|
|
8513
|
+
continue;
|
|
8514
|
+
}
|
|
8515
|
+
break;
|
|
8516
|
+
}
|
|
8517
|
+
// The direct producers: re.exec(s) / s.match(re) — the receiver's
|
|
8518
|
+
// mapped type pins the STDLIB operation (a regex for exec, a string
|
|
8519
|
+
// or its nullable spelling for match — the claim lower-containers
|
|
8520
|
+
// makes), so a user method that happens to be named `match` with a
|
|
8521
|
+
// regex argument never traces.
|
|
8522
|
+
if (ts.isCallExpression(expr) && ts.isPropertyAccessExpression(expr.expression)) {
|
|
8523
|
+
const name = expr.expression.name.text;
|
|
8524
|
+
const recvT = L.mapTypeOf(L.typeOf(expr.expression.expression));
|
|
8525
|
+
if (name === "exec" && expr.arguments.length === 1 && recvT?.kind === "regex") {
|
|
8526
|
+
return expr.expression.expression;
|
|
8527
|
+
}
|
|
8528
|
+
if (name === "match" && expr.arguments.length === 1 &&
|
|
8529
|
+
(recvT?.kind === "string" || (recvT !== null && recvT !== undefined && nullableStringType(L, recvT))) &&
|
|
8530
|
+
L.mapTypeOf(L.typeOf(expr.arguments[0]))?.kind === "regex") {
|
|
8531
|
+
return expr.arguments[0];
|
|
8532
|
+
}
|
|
8533
|
+
}
|
|
8534
|
+
// A matchAll ROW by element access: rows[i] with rows tracing to the
|
|
8535
|
+
// drain call — or the direct s.matchAll(re)[i] spelling.
|
|
8536
|
+
if (ts.isElementAccessExpression(expr)) {
|
|
8537
|
+
return matchAllRegexOf(L, expr.expression);
|
|
8538
|
+
}
|
|
8539
|
+
if (ts.isIdentifier(expr)) {
|
|
8540
|
+
// The for-of binding over a matchAll drain: `for (const m of
|
|
8541
|
+
// s.matchAll(re))` (or over a stored const rows).
|
|
8542
|
+
const sym = L.resolveValueSymbol(expr);
|
|
8543
|
+
const decl = sym ? L.checker.valueDeclarationOf(sym) : undefined;
|
|
8544
|
+
if (decl !== undefined && ts.isVariableDeclaration(decl) &&
|
|
8545
|
+
ts.isVariableDeclarationList(decl.parent) && ts.isForOfStatement(decl.parent.parent)) {
|
|
8546
|
+
return matchAllRegexOf(L, decl.parent.parent.expression);
|
|
8547
|
+
}
|
|
8548
|
+
// A stored const match result: `const m = re.exec(s)`.
|
|
8549
|
+
const init = constInitializerOf(L, expr);
|
|
8550
|
+
if (init !== null)
|
|
8551
|
+
return matchProducerRegexOf(L, init);
|
|
8552
|
+
}
|
|
8553
|
+
return null;
|
|
8554
|
+
}
|
|
8555
|
+
/** The regex argument of a (possibly const-stored) `s.matchAll(re)`. */
|
|
8556
|
+
function matchAllRegexOf(L, e) {
|
|
8557
|
+
let expr = e;
|
|
8558
|
+
for (;;) {
|
|
8559
|
+
if (ts.isParenthesizedExpression(expr) || ts.isNonNullExpression(expr)) {
|
|
8560
|
+
expr = expr.expression;
|
|
8561
|
+
continue;
|
|
8562
|
+
}
|
|
8563
|
+
break;
|
|
8564
|
+
}
|
|
8565
|
+
if (ts.isCallExpression(expr) && ts.isPropertyAccessExpression(expr.expression) &&
|
|
8566
|
+
expr.expression.name.text === "matchAll" && expr.arguments.length === 1 &&
|
|
8567
|
+
L.mapTypeOf(L.typeOf(expr.expression.expression))?.kind === "string" &&
|
|
8568
|
+
L.mapTypeOf(L.typeOf(expr.arguments[0]))?.kind === "regex") {
|
|
8569
|
+
return expr.arguments[0];
|
|
8570
|
+
}
|
|
8571
|
+
// The eager-spread spelling: `[...s.matchAll(re)]`.
|
|
8572
|
+
if (ts.isArrayLiteralExpression(expr) && expr.elements.length === 1) {
|
|
8573
|
+
const only = expr.elements[0];
|
|
8574
|
+
if (ts.isSpreadElement(only))
|
|
8575
|
+
return matchAllRegexOf(L, only.expression);
|
|
8576
|
+
}
|
|
8577
|
+
if (ts.isIdentifier(expr)) {
|
|
8578
|
+
const init = constInitializerOf(L, expr);
|
|
8579
|
+
if (init !== null)
|
|
8580
|
+
return matchAllRegexOf(L, init);
|
|
8581
|
+
}
|
|
8582
|
+
return null;
|
|
8583
|
+
}
|
|
8584
|
+
/** `m.groups` on a match result whose producing regex is statically
|
|
8585
|
+
* known: the honest slice already HOLDS every named group's value at
|
|
8586
|
+
* its capture index, so the groups object is a compile-time record
|
|
8587
|
+
* projection — `{ year: m[1], month: m[2] }` — built by one interned
|
|
8588
|
+
* helper per (shape, index list, receiver type). Node's exact shape
|
|
8589
|
+
* decisions:
|
|
8590
|
+
* - no named groups → `undefined` (the identifier-receiver shapes —
|
|
8591
|
+
* a call receiver would lose its own null trap, so those keep the
|
|
8592
|
+
* member fence);
|
|
8593
|
+
* - a nullable receiver (the JS `s.match(re).groups` idiom) throws
|
|
8594
|
+
* Node's exact TypeError on the unit arms;
|
|
8595
|
+
* - every declared name is a key (nonparticipating groups hold "" —
|
|
8596
|
+
* divergence 51's rule, exactly the slice elements they project);
|
|
8597
|
+
* - ES2025 duplicate names (distinct alternatives) project the first
|
|
8598
|
+
* participating occurrence: at most one participates, and a
|
|
8599
|
+
* participating-empty "" falls through to slots that are "" anyway;
|
|
8600
|
+
* - key order is group declaration order (declaredOrder), Node's own.
|
|
8601
|
+
* The record has no prototype — Node's groups object is null-prototype,
|
|
8602
|
+
* observable only through util.inspect's "[Object: null prototype]"
|
|
8603
|
+
* prefix (the Object.groupBy stance, ledgered). Null when the receiver
|
|
8604
|
+
* isn't a match slice or the regex isn't traceable — the caller's
|
|
8605
|
+
* member fence (with the groups hint) names the gap. */
|
|
8606
|
+
export function lowerMatchGroupsRead(L, expr) {
|
|
8607
|
+
if (expr.name.text !== "groups" || expr.questionDotToken !== undefined)
|
|
8608
|
+
return null;
|
|
8609
|
+
const loc = locOf(expr);
|
|
8610
|
+
const strArr = arrayOf(STRING);
|
|
8611
|
+
const recvT = L.mapTypeOf(L.typeOf(expr.expression));
|
|
8612
|
+
if (!recvT || !isMatchSliceType(L, recvT) || !L.isStdlibMember(expr))
|
|
8613
|
+
return null;
|
|
8614
|
+
const groups = matchResultNamedGroupsOf(L, expr.expression);
|
|
8615
|
+
if (groups === null)
|
|
8616
|
+
return null;
|
|
8617
|
+
if (groups.length === 0) {
|
|
8618
|
+
// Node: a match result of a group-less regex answers undefined —
|
|
8619
|
+
// the unit literal, exactly the `undefined` identifier's lowering.
|
|
8620
|
+
// Identifier receivers only (evaluation-free): a CALL receiver's
|
|
8621
|
+
// exec/match must still run and null-trap, which a folded unit
|
|
8622
|
+
// cannot carry — those keep the fence.
|
|
8623
|
+
let r = expr.expression;
|
|
8624
|
+
while (ts.isParenthesizedExpression(r) || ts.isNonNullExpression(r))
|
|
8625
|
+
r = r.expression;
|
|
8626
|
+
if (ts.isIdentifier(r) && typeEquals(recvT, strArr)) {
|
|
8627
|
+
return { kind: "unitLit", unit: "undefined", type: UNDEFINED_T, loc };
|
|
8628
|
+
}
|
|
8629
|
+
return null;
|
|
8630
|
+
}
|
|
8631
|
+
const recv = L.lowerExpr(expr.expression);
|
|
8632
|
+
if (!isMatchSliceType(L, recv.type))
|
|
8633
|
+
return null;
|
|
8634
|
+
return lowerGroupsProjection(L, recv, groups, loc);
|
|
8635
|
+
}
|
|
8636
|
+
/** True when the IR type is a string-or-units union — the Dict<string>
|
|
8637
|
+
* member spelling (`process.versions.openssl`), match's nullable
|
|
8638
|
+
* receiver claim. */
|
|
8639
|
+
function nullableStringType(L, t) {
|
|
8640
|
+
if (t.kind !== "union")
|
|
8641
|
+
return false;
|
|
8642
|
+
const arms = L.unions.get(t.unionId)?.arms ?? [];
|
|
8643
|
+
return arms.some((a) => a.kind === "string") && arms.every((a) => a.kind === "string" || isUnitType(a));
|
|
8644
|
+
}
|
|
8645
|
+
/** True when the IR type is the honest match slice — `string[]`, or a
|
|
8646
|
+
* union of it with unit arms only (`string[] | null`). The groups
|
|
8647
|
+
* projection's receiver gate, shared with the destructure interception. */
|
|
8648
|
+
export function isMatchSliceType(L, t) {
|
|
8649
|
+
if (typeEquals(t, arrayOf(STRING)))
|
|
8650
|
+
return true;
|
|
8651
|
+
return t.kind === "union" && matchSliceUnionArms(L, t.unionId) !== null;
|
|
8652
|
+
}
|
|
8653
|
+
/** The union-arm layout of a nullable match slice (`string[] | null`,
|
|
8654
|
+
* matchAll's checker spellings with undefined arms included): the
|
|
8655
|
+
* string[] arm's tag plus each unit arm's tag and spelling. Null when
|
|
8656
|
+
* the union holds anything else. */
|
|
8657
|
+
function matchSliceUnionArms(L, unionId) {
|
|
8658
|
+
const def = L.unions.get(unionId);
|
|
8659
|
+
if (!def)
|
|
8660
|
+
return null;
|
|
8661
|
+
const strArr = arrayOf(STRING);
|
|
8662
|
+
const arrTag = def.arms.findIndex((a) => typeEquals(a, strArr));
|
|
8663
|
+
if (arrTag < 0)
|
|
8664
|
+
return null;
|
|
8665
|
+
const units = [];
|
|
8666
|
+
for (let i = 0; i < def.arms.length; i++) {
|
|
8667
|
+
if (i === arrTag)
|
|
8668
|
+
continue;
|
|
8669
|
+
const a = def.arms[i];
|
|
8670
|
+
if (!isUnitType(a))
|
|
8671
|
+
return null;
|
|
8672
|
+
units.push({ tag: i, unit: a.kind === "nullT" ? "null" : "undefined" });
|
|
8673
|
+
}
|
|
8674
|
+
return { arrTag, units };
|
|
8675
|
+
}
|
|
8676
|
+
/** The groups-record projection call: the result is the CHECKER'S OWN
|
|
8677
|
+
* shape — `{ [key: string]: string }`, the hybrid index-signature
|
|
8678
|
+
* record with no declared fields — holding one OVERFLOW entry per
|
|
8679
|
+
* group name in declaration order, so every downstream consumer
|
|
8680
|
+
* (recordKeyGet reads, Object.keys, JSON, the `| undefined` union
|
|
8681
|
+
* wrap) meets exactly the type tsc reported. One lifted helper per
|
|
8682
|
+
* (index list, receiver representation) builds it; see
|
|
8683
|
+
* lowerMatchGroupsRead for the semantics the body encodes. */
|
|
8684
|
+
export function lowerGroupsProjection(L, recv, groups, loc) {
|
|
8685
|
+
// Name → its capture indices, in declaration order (duplicates keep
|
|
8686
|
+
// every occurrence — the projection picks the participating one).
|
|
8687
|
+
const order = [];
|
|
8688
|
+
const indicesByName = new Map();
|
|
8689
|
+
for (const g of groups) {
|
|
8690
|
+
const got = indicesByName.get(g.name);
|
|
8691
|
+
if (got) {
|
|
8692
|
+
got.push(g.index);
|
|
8693
|
+
}
|
|
8694
|
+
else {
|
|
8695
|
+
indicesByName.set(g.name, [g.index]);
|
|
8696
|
+
order.push(g.name);
|
|
8697
|
+
}
|
|
8698
|
+
}
|
|
8699
|
+
const shapeId = L.shapes.intern([], false, STRING);
|
|
8700
|
+
const recordT = { kind: "record", shapeId };
|
|
8701
|
+
const recvKey = recv.type.kind === "union" ? recv.type.unionId : "arr";
|
|
8702
|
+
const key = `regexgroups:${shapeId}:${groups.map((g) => `${g.name}=${g.index}`).join(",")}:${recvKey}`;
|
|
8703
|
+
let helper = L.widthHelpers.get(key);
|
|
8704
|
+
if (!helper) {
|
|
8705
|
+
helper = `%regex.groups.${L.widthHelpers.size}`;
|
|
8706
|
+
L.widthHelpers.set(key, helper);
|
|
8707
|
+
const body = [];
|
|
8708
|
+
const locals = [{ id: "m.0", name: "m", type: recv.type, mutable: true }];
|
|
8709
|
+
let mRef = { kind: "varRef", localId: "m.0", type: recv.type, loc };
|
|
8710
|
+
if (recv.type.kind === "union") {
|
|
8711
|
+
// Node's exact TypeError per unit arm, then the checked narrow.
|
|
8712
|
+
const arms = matchSliceUnionArms(L, recv.type.unionId);
|
|
8713
|
+
for (const u of arms.units) {
|
|
8714
|
+
body.push({
|
|
8715
|
+
kind: "if",
|
|
8716
|
+
cond: { kind: "unionIsTag", unionId: recv.type.unionId, tag: u.tag, negated: false, value: mRef, type: BOOL, loc },
|
|
8717
|
+
then: [
|
|
8718
|
+
{
|
|
8719
|
+
kind: "throw",
|
|
8720
|
+
value: {
|
|
8721
|
+
kind: "libCall",
|
|
8722
|
+
fn: "error.new",
|
|
8723
|
+
args: [
|
|
8724
|
+
{ kind: "strLit", value: `Cannot read properties of ${u.unit} (reading 'groups')`, type: STRING, loc },
|
|
8725
|
+
],
|
|
8726
|
+
type: { kind: "object", className: "%TypeError" },
|
|
8727
|
+
loc,
|
|
8728
|
+
},
|
|
8729
|
+
loc,
|
|
8730
|
+
},
|
|
8731
|
+
],
|
|
8732
|
+
else_: null,
|
|
8733
|
+
loc,
|
|
8734
|
+
});
|
|
8735
|
+
}
|
|
8736
|
+
const narrowed = { kind: "unionNarrow", unionId: recv.type.unionId, tag: arms.arrTag, value: mRef, type: arrayOf(STRING), loc };
|
|
8737
|
+
locals.push({ id: "arr.0", name: "arr", type: arrayOf(STRING), mutable: false });
|
|
8738
|
+
body.push({ kind: "varDecl", localId: "arr.0", init: narrowed, loc });
|
|
8739
|
+
mRef = { kind: "varRef", localId: "arr.0", type: arrayOf(STRING), loc };
|
|
8740
|
+
}
|
|
8741
|
+
const num = (value) => ({ kind: "numLit", value, type: F64, loc });
|
|
8742
|
+
const elem = (index) => ({ kind: "arrayGet", arr: mRef, index: num(index), type: STRING, loc });
|
|
8743
|
+
const values = new Map();
|
|
8744
|
+
order.forEach((name, i) => {
|
|
8745
|
+
const idxs = indicesByName.get(name);
|
|
8746
|
+
if (idxs.length === 1) {
|
|
8747
|
+
values.set(name, elem(idxs[0]));
|
|
8748
|
+
return;
|
|
8749
|
+
}
|
|
8750
|
+
// Duplicates: the first non-"" occurrence (at most one
|
|
8751
|
+
// participates; all-"" answers "" like any nonparticipating slot).
|
|
8752
|
+
const id = `g${i}.0`;
|
|
8753
|
+
locals.push({ id, name: `g${i}`, type: STRING, mutable: true });
|
|
8754
|
+
body.push({ kind: "varDecl", localId: id, init: elem(idxs[0]), loc });
|
|
8755
|
+
const gRef = { kind: "varRef", localId: id, type: STRING, loc };
|
|
8756
|
+
for (const idx of idxs.slice(1)) {
|
|
8757
|
+
body.push({
|
|
8758
|
+
kind: "if",
|
|
8759
|
+
cond: { kind: "strEq", negated: false, left: gRef, right: { kind: "strLit", value: "", type: STRING, loc }, type: BOOL, loc },
|
|
8760
|
+
then: [{ kind: "assign", localId: id, value: elem(idx), loc }],
|
|
8761
|
+
else_: null,
|
|
8762
|
+
loc,
|
|
8763
|
+
});
|
|
8764
|
+
}
|
|
8765
|
+
values.set(name, gRef);
|
|
8766
|
+
});
|
|
8767
|
+
body.push({
|
|
8768
|
+
kind: "return",
|
|
8769
|
+
value: {
|
|
8770
|
+
kind: "recordLit",
|
|
8771
|
+
fields: order.map((name) => ({ name, value: values.get(name), overflow: true })),
|
|
8772
|
+
type: recordT,
|
|
8773
|
+
loc,
|
|
8774
|
+
},
|
|
8775
|
+
loc,
|
|
8776
|
+
});
|
|
8777
|
+
L.liftedFns.push({
|
|
8778
|
+
name: helper,
|
|
8779
|
+
params: [{ localId: "m.0", name: "m", type: recv.type }],
|
|
8780
|
+
returnType: recordT,
|
|
8781
|
+
locals,
|
|
8782
|
+
body,
|
|
8783
|
+
loc,
|
|
8784
|
+
});
|
|
8785
|
+
}
|
|
8786
|
+
return { kind: "call", callee: helper, args: [recv], type: recordT, loc };
|
|
8787
|
+
}
|
|
8283
8788
|
/** Field read `obj.f` on class-instance and record receivers, through the
|
|
8284
8789
|
* shared FieldTarget union (fieldGet / recordGet). Bound method references
|
|
8285
8790
|
* on classes are rejected specifically; func-typed record fields are
|
|
@@ -8372,8 +8877,8 @@ export function lowerUnionProperty(L, expr) {
|
|
|
8372
8877
|
// fallback's discipline.
|
|
8373
8878
|
// A checker-union receiver whose VALUE lowered checked-dynamic (a
|
|
8374
8879
|
// never-tainted JS chain — `cmd[1].length` on `const cmd = ['pwd',
|
|
8375
|
-
// []]`, where the element read stayed a
|
|
8376
|
-
//
|
|
8880
|
+
// []]`, where the element read stayed a dyn node): read through the
|
|
8881
|
+
// dyn keyed read like the unmappable-receiver path below the chain.
|
|
8377
8882
|
if (value.type.kind === "dyn") {
|
|
8378
8883
|
const key = { kind: "strLit", value: expr.name.text, type: STRING, loc: locOf(expr.name) };
|
|
8379
8884
|
return { kind: "dynKeyGet", key, value, type: DYN, loc: locOf(expr) };
|
|
@@ -8760,9 +9265,9 @@ export function symbolFieldTarget(L, expr) {
|
|
|
8760
9265
|
* tsc-clean (the property types as the setter's param), but Node yields
|
|
8761
9266
|
* undefined, which these property types cannot represent. */
|
|
8762
9267
|
export function fieldGetExpr(L, target, loc, blame) {
|
|
8763
|
-
// A record-shaped CHECKER target whose receiver VALUE lives in the
|
|
9268
|
+
// A record-shaped CHECKER target whose receiver VALUE lives in the checked-dynamic tree
|
|
8764
9269
|
// (a JS file-scope object-literal global): the checked-dynamic keyed
|
|
8765
|
-
// read — dynKeyGet (a missing key answers the
|
|
9270
|
+
// read — dynKeyGet (a missing key answers the dyn undefined, exactly
|
|
8766
9271
|
// JS); consumers validate (dynCheck) where a static type is required.
|
|
8767
9272
|
if ((target.container === "record" || target.container === "recordOvf") &&
|
|
8768
9273
|
target.obj.type.kind === "dyn") {
|
|
@@ -8843,15 +9348,15 @@ export function fieldGetExpr(L, target, loc, blame) {
|
|
|
8843
9348
|
* call). A write to a getter-only property never gets here in a clean
|
|
8844
9349
|
* program (tsc's TS2540 is the fence); the rejection is the backstop. */
|
|
8845
9350
|
export function fieldSetStmt(L, target, value, loc, blame) {
|
|
8846
|
-
// A record-shaped CHECKER target whose receiver VALUE lives in the
|
|
9351
|
+
// A record-shaped CHECKER target whose receiver VALUE lives in the checked-dynamic tree:
|
|
8847
9352
|
// the checked-dynamic keyed write — dyn.keySet (later writes win,
|
|
8848
9353
|
// insertion order; Node's TypeErrors on non-object receivers), the
|
|
8849
|
-
// value converting into the
|
|
9354
|
+
// value converting into the checked-dynamic tree.
|
|
8850
9355
|
if ((target.container === "record" || target.container === "recordOvf") &&
|
|
8851
9356
|
target.obj.type.kind === "dyn") {
|
|
8852
9357
|
const v = L.coerceToExpected(value, DYN);
|
|
8853
9358
|
if (v.type.kind !== "dyn") {
|
|
8854
|
-
L.unsupported("SC1101", blame, `storing '${L.fmt(value.type)}' values in a checked-dynamic object (the value cannot convert into the
|
|
9359
|
+
L.unsupported("SC1101", blame, `storing '${L.fmt(value.type)}' values in a checked-dynamic object (the value cannot convert into the checked-dynamic tree)`);
|
|
8855
9360
|
}
|
|
8856
9361
|
return {
|
|
8857
9362
|
kind: "exprStmt",
|
|
@@ -8939,7 +9444,7 @@ export function lowerPromiseRejectCall(L, call, access) {
|
|
|
8939
9444
|
const type = ctxT ? L.mapTypeOf(ctxT) : null;
|
|
8940
9445
|
const reasonNode = call.arguments[0];
|
|
8941
9446
|
// A CHECKED-DYNAMIC reason (`Promise.reject(value)` where value rode
|
|
8942
|
-
// an untyped binding — the tracingChannel suite's shape): the
|
|
9447
|
+
// an untyped binding — the tracingChannel suite's shape): the checked-dynamic tree
|
|
8943
9448
|
// value IS the rejection payload (the thrown-dyn representation, so
|
|
8944
9449
|
// identity survives to catch/unhandledRejection observers), and the
|
|
8945
9450
|
// result is promise<dyn> when no context names a concrete one.
|
|
@@ -9082,7 +9587,7 @@ export function lowerFieldCompound(L, access, op, rhsNode, loc) {
|
|
|
9082
9587
|
const concat = {
|
|
9083
9588
|
kind: "strConcat",
|
|
9084
9589
|
// String-context read: JS's String(unknown) over the field —
|
|
9085
|
-
// the JS-exact
|
|
9590
|
+
// the JS-exact dyn walker, never a checked cast.
|
|
9086
9591
|
left: { kind: "toString", operand: read, type: STRING, loc },
|
|
9087
9592
|
right: rhs,
|
|
9088
9593
|
type: STRING,
|