@scriptc/compiler 0.0.9 → 0.0.11
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 +54 -9
- package/dist/backend/emission/emit-exprs.js +170 -14
- package/dist/backend/emission/emit-exprs.js.map +1 -1
- package/dist/backend/emission/emit-walkers.js +29 -2
- package/dist/backend/emission/emit-walkers.js.map +1 -1
- package/dist/backend/emission/emitter.js +25 -1
- package/dist/backend/emission/emitter.js.map +1 -1
- package/dist/backend/llvm/dyn.d.ts +1 -0
- package/dist/backend/llvm/dyn.js +83 -1
- package/dist/backend/llvm/dyn.js.map +1 -1
- package/dist/backend/llvm/emitter.js +210 -13
- package/dist/backend/llvm/emitter.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 +41 -14
- package/dist/diagnostics/diagnostic.js +79 -27
- package/dist/diagnostics/diagnostic.js.map +1 -1
- package/dist/diagnostics/render.js +8 -0
- package/dist/diagnostics/render.js.map +1 -1
- package/dist/frontend/lowering/lib-boundary.js +11 -1
- package/dist/frontend/lowering/lib-boundary.js.map +1 -1
- package/dist/frontend/lowering/lower-builtins.d.ts +69 -0
- package/dist/frontend/lowering/lower-builtins.js +380 -4
- package/dist/frontend/lowering/lower-builtins.js.map +1 -1
- package/dist/frontend/lowering/lower-calls.d.ts +41 -0
- package/dist/frontend/lowering/lower-calls.js +559 -11
- 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 +51 -1
- package/dist/frontend/lowering/lower-classes.js.map +1 -1
- package/dist/frontend/lowering/lower-containers.d.ts +21 -0
- package/dist/frontend/lowering/lower-containers.js +262 -17
- package/dist/frontend/lowering/lower-containers.js.map +1 -1
- package/dist/frontend/lowering/lower-dgram.js +92 -17
- package/dist/frontend/lowering/lower-dgram.js.map +1 -1
- package/dist/frontend/lowering/lower-emitter.d.ts +70 -3
- package/dist/frontend/lowering/lower-emitter.js +436 -29
- package/dist/frontend/lowering/lower-emitter.js.map +1 -1
- package/dist/frontend/lowering/lower-exprs.d.ts +80 -5
- package/dist/frontend/lowering/lower-exprs.js +651 -23
- package/dist/frontend/lowering/lower-exprs.js.map +1 -1
- package/dist/frontend/lowering/lower-modules.d.ts +8 -5
- package/dist/frontend/lowering/lower-modules.js +144 -19
- package/dist/frontend/lowering/lower-modules.js.map +1 -1
- package/dist/frontend/lowering/lower-namespaces.d.ts +39 -11
- package/dist/frontend/lowering/lower-namespaces.js +166 -24
- package/dist/frontend/lowering/lower-namespaces.js.map +1 -1
- package/dist/frontend/lowering/lower-server.js +211 -4
- package/dist/frontend/lowering/lower-server.js.map +1 -1
- package/dist/frontend/lowering/lower-stmts.d.ts +1 -5
- package/dist/frontend/lowering/lower-stmts.js +293 -10
- package/dist/frontend/lowering/lower-stmts.js.map +1 -1
- package/dist/frontend/lowering/lower-stream.js +178 -10
- package/dist/frontend/lowering/lower-stream.js.map +1 -1
- package/dist/frontend/lowering/lowerer.d.ts +53 -1
- package/dist/frontend/lowering/lowerer.js +210 -19
- package/dist/frontend/lowering/lowerer.js.map +1 -1
- package/dist/frontend/lowering/surfaces.d.ts +44 -0
- package/dist/frontend/lowering/surfaces.js +234 -3
- 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 +144 -135
- package/dist/frontend/program.js.map +1 -1
- package/dist/frontend/shared.d.ts +1 -1
- package/dist/frontend/shared.js +1 -1
- package/dist/frontend/shared.js.map +1 -1
- package/dist/frontend/types.d.ts +13 -0
- package/dist/frontend/types.js +84 -7
- package/dist/frontend/types.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +157 -12
- package/dist/index.js.map +1 -1
- package/dist/ir/nodes.d.ts +163 -13
- package/dist/ir/nodes.js +65 -6
- package/dist/ir/nodes.js.map +1 -1
- package/dist/ir/validate.js +64 -5
- package/dist/ir/validate.js.map +1 -1
- package/dist/library/fence-eval.d.ts +77 -0
- package/dist/library/fence-eval.js +395 -0
- package/dist/library/fence-eval.js.map +1 -0
- package/dist/library/int-infer.d.ts +119 -0
- package/dist/library/int-infer.js +1301 -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 +43 -6
- package/dist/library/profile.js +179 -31
- package/dist/library/profile.js.map +1 -1
- package/dist/library/sidecar.d.ts +23 -0
- package/dist/library/sidecar.js +150 -16
- package/dist/library/sidecar.js.map +1 -1
- package/package.json +2 -2
- package/surface-manifest.json +208 -10
|
@@ -8,9 +8,9 @@ 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
|
-
import { PoisonError, dynUndefinedExpr, jsFuncNameOf, neverTaintedJsType, own } from "./lowerer.js";
|
|
13
|
+
import { PoisonError, dynUndefinedExpr, jsFuncNameOf, neverTaintedJsType, nodeThrowExpr, own } from "./lowerer.js";
|
|
14
14
|
import { lowerIndexMergeHelper, lowerNpmStaticSafeIndexRead, strCharsCall } from "./lower-containers.js";
|
|
15
15
|
import { npmStaticPackageOfPath } from "../npm-static.js";
|
|
16
16
|
import { unsupportedModuleFeatureOf } from "../shared.js";
|
|
@@ -19,7 +19,7 @@ import { ambientNsRootOf, ambientUndefReadType, ambientUndefVarRootOf, ambientUn
|
|
|
19
19
|
import { expandoMemberRead, expandoWritableTarget } from "./lower-expando.js";
|
|
20
20
|
import { lowerSocketInstanceOf } from "./lower-server.js";
|
|
21
21
|
import { findGenericMethodOn, lowerStaticFieldRead } from "./lower-classes.js";
|
|
22
|
-
import { bindingNeverReassigned, implicitMonoFile, lowerTaggedTemplate, objLitGenericFnInfoOf, objLitGenericFnNodeOf, requireObjLitGenericReceiver } from "./lower-calls.js";
|
|
22
|
+
import { bindingNeverReassigned, implicitMonoFile, lowerTaggedTemplate, nullishGenericBindingUnitOf, objLitGenericFnInfoOf, objLitGenericFnNodeOf, requireObjLitGenericReceiver } from "./lower-calls.js";
|
|
23
23
|
import { mixinFnOfCallee } from "./lower-mixins.js";
|
|
24
24
|
import { isConstAssertionTypeNode, isGenericCallableMemberType, underConstAssertion, unitOnlyUnion } from "../types.js";
|
|
25
25
|
import { lowerYield } from "./lower-generators.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 };
|
|
@@ -649,6 +664,11 @@ function lowerExprInner(L, expr) {
|
|
|
649
664
|
const c = builtinModuleConstOf(L, bi.module, bi.member);
|
|
650
665
|
if (c !== undefined)
|
|
651
666
|
return builtinConstLit(c, loc);
|
|
667
|
+
// module.builtinModules — the baked Node v24 list, a fresh
|
|
668
|
+
// string[] per read.
|
|
669
|
+
if (bi.module === "module" && bi.member === "builtinModules") {
|
|
670
|
+
return builtinModulesArrayLit(loc);
|
|
671
|
+
}
|
|
652
672
|
// JavaScript sources: a builtin member taken as a bare VALUE is
|
|
653
673
|
// the same identity-token story as stdlib globals above (the
|
|
654
674
|
// harness adds worker_threads.Worker to its identity Set).
|
|
@@ -796,6 +816,18 @@ function lowerExprInner(L, expr) {
|
|
|
796
816
|
return nsUndefRead(L, expr.text, expr, t);
|
|
797
817
|
}
|
|
798
818
|
}
|
|
819
|
+
// A read of a TRAP binding — a declaration whose own initializer
|
|
820
|
+
// provably threw (module init unwound there), so this reference can
|
|
821
|
+
// never execute: any lowering is sound, and the trap keeps the
|
|
822
|
+
// shape honest. Typed by the use site when it maps, the F64 dummy
|
|
823
|
+
// otherwise (never observed — never even reached).
|
|
824
|
+
{
|
|
825
|
+
const sym = L.resolveValueSymbol(expr);
|
|
826
|
+
if (sym !== null && L.trapBindings.has(sym)) {
|
|
827
|
+
const t = ambientUndefReadType(L, expr) ?? contextualUndefReadType(L, expr) ?? F64;
|
|
828
|
+
return nsUndefRead(L, expr.text, expr, t);
|
|
829
|
+
}
|
|
830
|
+
}
|
|
799
831
|
// A program CLASS NAME as a value: the classRef over the class's
|
|
800
832
|
// immortal class object (member accesses and construction never
|
|
801
833
|
// reach here — their hooks claim the property/new forms first).
|
|
@@ -1165,6 +1197,15 @@ function lowerExprInner(L, expr) {
|
|
|
1165
1197
|
}
|
|
1166
1198
|
return read;
|
|
1167
1199
|
}
|
|
1200
|
+
// `m.groups` on a match result whose regex is statically known:
|
|
1201
|
+
// the compile-time record projection over the honest slice (or
|
|
1202
|
+
// Node's undefined when the pattern has no named groups) — see
|
|
1203
|
+
// lowerMatchGroupsRead.
|
|
1204
|
+
{
|
|
1205
|
+
const g = lowerMatchGroupsRead(L, expr);
|
|
1206
|
+
if (g !== null)
|
|
1207
|
+
return g;
|
|
1208
|
+
}
|
|
1168
1209
|
// `m.index` on a for-of-over-matchAll binding reads the companion-
|
|
1169
1210
|
// index array at this iteration's cursor — always a number: every
|
|
1170
1211
|
// drained row matched, so the lib's `.index` is never undefined
|
|
@@ -1406,12 +1447,25 @@ function lowerExprInner(L, expr) {
|
|
|
1406
1447
|
// read (ARR answers length; everything else JS's own-property
|
|
1407
1448
|
// answer). Mapped receivers keep the fence-first order: their
|
|
1408
1449
|
// members' gaps are real surface gaps ([1,2].entries), not
|
|
1409
|
-
// representation artifacts.
|
|
1410
|
-
|
|
1450
|
+
// representation artifacts. `.length` on a receiver whose checker
|
|
1451
|
+
// ARRAY type lowers to the checked-dynamic representation —
|
|
1452
|
+
// `unknown[]`, the collapsed `(string | object)[]`, and the `any[]`
|
|
1453
|
+
// an Array.isArray guard narrows a collapsed union to — is the DOM
|
|
1454
|
+
// array's OWN length (a keyed read the ARR kind answers exactly),
|
|
1455
|
+
// never an `Array.prototype` surface gap; both source languages.
|
|
1456
|
+
// Prototype-method VALUE reads (`ps.map` unparenthesized) keep the
|
|
1457
|
+
// fence-first order: a stored-member undefined would mis-answer
|
|
1458
|
+
// them, and calls dispatch through the dyn method machinery
|
|
1459
|
+
// instead.
|
|
1460
|
+
if ((isJsSourceFile(expr.getSourceFile()) &&
|
|
1411
1461
|
(L.mapTypeOf(L.typeOf(expr.expression)) === null ||
|
|
1412
1462
|
// A never-tainted receiver type maps (never rides as f64) but
|
|
1413
1463
|
// its VALUE lowered checked-dynamic — same DOM read.
|
|
1414
|
-
neverTaintedJsType(L, expr.expression, L.typeOf(expr.expression))))
|
|
1464
|
+
neverTaintedJsType(L, expr.expression, L.typeOf(expr.expression)))) ||
|
|
1465
|
+
(expr.name.text === "length" &&
|
|
1466
|
+
(L.checkerAnyArray(expr.expression) ||
|
|
1467
|
+
(L.checker.isArrayType(L.typeOf(expr.expression)) &&
|
|
1468
|
+
L.mapTypeOf(L.typeOf(expr.expression))?.kind === "dyn")))) {
|
|
1415
1469
|
const recv = L.lowerExpr(expr.expression);
|
|
1416
1470
|
if (recv.type.kind === "dyn") {
|
|
1417
1471
|
const key = { kind: "strLit", value: expr.name.text, type: STRING, loc: locOf(expr.name) };
|
|
@@ -1479,6 +1533,17 @@ function lowerExprInner(L, expr) {
|
|
|
1479
1533
|
return nsUndefRead(L, ambientRoot.text, expr, t);
|
|
1480
1534
|
}
|
|
1481
1535
|
}
|
|
1536
|
+
// A member read through a NULLISH generic binding (`const i: I<A &
|
|
1537
|
+
// B> = null as any; const _i: I<A> = i.something` — the receiver
|
|
1538
|
+
// provably holds null/undefined forever): the read throws Node's
|
|
1539
|
+
// exact TypeError at the access.
|
|
1540
|
+
if (ts.isIdentifier(expr.expression) && expr.questionDotToken === undefined) {
|
|
1541
|
+
const unit = nullishGenericBindingUnitOf(L, L.resolveValueSymbol(expr.expression));
|
|
1542
|
+
if (unit !== null) {
|
|
1543
|
+
const t = ambientUndefReadType(L, expr) ?? contextualUndefReadType(L, expr) ?? F64;
|
|
1544
|
+
return nodeThrowExpr(1, "", `Cannot read properties of ${unit} (reading '${expr.name.text}')`, t, loc);
|
|
1545
|
+
}
|
|
1546
|
+
}
|
|
1482
1547
|
// The lib fence's PROPERTY chokepoint: a stdlib-declared member that
|
|
1483
1548
|
// no lowering above claimed ([1,2].entries, Math.SQRT2, Promise.all,
|
|
1484
1549
|
// re.exec as a value, ...) reports SC2020 here.
|
|
@@ -1516,6 +1581,31 @@ function lowerExprInner(L, expr) {
|
|
|
1516
1581
|
const opt = chainGuardedByQuestionDot(expr.expression);
|
|
1517
1582
|
return L.maybeNarrow({ kind: "dynKeyGet", key, ...(opt ? { optional: true } : {}), value: recvLowered, type: DYN, loc }, expr);
|
|
1518
1583
|
}
|
|
1584
|
+
// The lowered receiver is a RECORD the checker spelled wider —
|
|
1585
|
+
// `s.match(re).groups.key` in a JS file: the checker says
|
|
1586
|
+
// `{ [key: string]: string } | undefined`, but the groups
|
|
1587
|
+
// projection already answered the record arm (its null trap
|
|
1588
|
+
// included). Declared fields read directly; an index-signature
|
|
1589
|
+
// shape serves undeclared keys through the overflow read, exactly
|
|
1590
|
+
// the dot-access rule on checker-spelled hybrids.
|
|
1591
|
+
if (recvLowered.type.kind === "record") {
|
|
1592
|
+
const recvShape = L.shapes.get(recvLowered.type.shapeId);
|
|
1593
|
+
const field = recvShape?.fields.find((f) => f.name === expr.name.text);
|
|
1594
|
+
if (field) {
|
|
1595
|
+
return L.maybeNarrow({ kind: "recordGet", obj: recvLowered, shapeId: recvLowered.type.shapeId, field: field.name, type: field.type, loc }, expr);
|
|
1596
|
+
}
|
|
1597
|
+
if (recvShape?.indexValue !== undefined && !recvShape.tuple) {
|
|
1598
|
+
return L.maybeNarrow({
|
|
1599
|
+
kind: "recordKeyGet",
|
|
1600
|
+
obj: recvLowered,
|
|
1601
|
+
shapeId: recvLowered.type.shapeId,
|
|
1602
|
+
key: { kind: "strLit", value: expr.name.text, type: STRING, loc: locOf(expr.name) },
|
|
1603
|
+
overflowOnly: true,
|
|
1604
|
+
type: recvShape.indexValue,
|
|
1605
|
+
loc,
|
|
1606
|
+
}, expr);
|
|
1607
|
+
}
|
|
1608
|
+
}
|
|
1519
1609
|
// An ABSTRACT property through an abstract-typed receiver: the
|
|
1520
1610
|
// declaration is erased at runtime — Node defines no field for it,
|
|
1521
1611
|
// each concrete subclass declares its OWN (at its own layout
|
|
@@ -2154,7 +2244,18 @@ function pureCondExpr(e) {
|
|
|
2154
2244
|
export function lowerNullishCoalesce(L, expr, loc) {
|
|
2155
2245
|
const left = L.lowerExpr(expr.left);
|
|
2156
2246
|
if (left.type.kind === "dyn") {
|
|
2157
|
-
|
|
2247
|
+
// `a ?? b` on a CHECKED-DYNAMIC left: the deciding test is the
|
|
2248
|
+
// runtime kind (scr_dyn_is_nullish — UNDEF/NULL take the default;
|
|
2249
|
+
// a wrapped island value routes to the engine's test, defensively:
|
|
2250
|
+
// the wrap constructor scalar-normalizes engine null/undefined
|
|
2251
|
+
// away). Both sides live in the DOM — the right converts through
|
|
2252
|
+
// the usual boundary and evaluates lazily in its branch; a default
|
|
2253
|
+
// with no dyn representation keeps the fence.
|
|
2254
|
+
const right = L.coerceToExpected(L.lowerExpr(expr.right), DYN);
|
|
2255
|
+
if (right.type.kind !== "dyn") {
|
|
2256
|
+
L.unsupported("SC1100", expr, "nullish coalescing on 'unknown' values against defaults with no dynamic representation");
|
|
2257
|
+
}
|
|
2258
|
+
return { kind: "nullish", left, right, type: DYN, loc };
|
|
2158
2259
|
}
|
|
2159
2260
|
if (left.type.kind === "jsval") {
|
|
2160
2261
|
// `a ?? b` on an ISLAND value: the engine's own nullish test — the
|
|
@@ -2947,8 +3048,8 @@ export function lowerIntrinsicProperty(L, expr) {
|
|
|
2947
3048
|
// No ArrayBuffer VALUE exists — `.buffer` compiles only in the
|
|
2948
3049
|
// one composed position that consumes it, the DataView
|
|
2949
3050
|
// constructor's first argument (lowerDataViewNew peels it).
|
|
2950
|
-
L.unsupported("SC1090", expr, "'.buffer' outside new DataView(x.buffer, ...)
|
|
2951
|
-
"no free-standing ArrayBuffer value exists
|
|
3051
|
+
L.unsupported("SC1090", expr, "'.buffer' outside new DataView(x.buffer, ...) / Buffer.from(x.buffer, ...) " +
|
|
3052
|
+
"(no free-standing ArrayBuffer value exists; subarray() answers an aliasing view directly)");
|
|
2952
3053
|
}
|
|
2953
3054
|
if (name === "slice" || name === "subarray" || name === "set" || name === "toString") {
|
|
2954
3055
|
L.unsupported("SC1090", expr, `typed-array methods as values (call '${name}' directly)`);
|
|
@@ -2988,6 +3089,24 @@ export function lowerIntrinsicProperty(L, expr) {
|
|
|
2988
3089
|
return read;
|
|
2989
3090
|
return { kind: "jsExit", value: read, type: F64, loc: locOf(expr) };
|
|
2990
3091
|
}
|
|
3092
|
+
// A CHECKED-DYNAMIC value behind an array-typed checker spelling
|
|
3093
|
+
// (`Object.keys(u).length` — the dyn walk answers a DOM array while
|
|
3094
|
+
// tsc spells string[]): the runtime-world dispatch — a DOM keyed
|
|
3095
|
+
// read validated into the declared number (a lying length throws
|
|
3096
|
+
// the catchable TypeError, the dynCheck stance).
|
|
3097
|
+
if (receiver.type.kind === "dyn") {
|
|
3098
|
+
const read = {
|
|
3099
|
+
kind: "dynKeyGet",
|
|
3100
|
+
key: { kind: "strLit", value: "length", type: STRING, loc: locOf(expr) },
|
|
3101
|
+
...(expr.questionDotToken ? { optional: true } : {}),
|
|
3102
|
+
value: receiver,
|
|
3103
|
+
type: DYN,
|
|
3104
|
+
loc: locOf(expr),
|
|
3105
|
+
};
|
|
3106
|
+
if (expr.questionDotToken)
|
|
3107
|
+
return read;
|
|
3108
|
+
return { kind: "dynCheck", value: read, type: F64, loc: locOf(expr) };
|
|
3109
|
+
}
|
|
2991
3110
|
return kind === "string"
|
|
2992
3111
|
? { kind: "strIntrinsic", method: "length", receiver, args: [], type: F64, loc: locOf(expr) }
|
|
2993
3112
|
: { kind: "arrIntrinsic", method: "length", receiver, args: [], type: F64, loc: locOf(expr) };
|
|
@@ -3148,8 +3267,12 @@ export function lowerArrayLiteral(L, expr, expected) {
|
|
|
3148
3267
|
// []) builds as a DOM ARRAY — one dyn value whose elements each
|
|
3149
3268
|
// convert through the usual boundary (dynFrom's JSON-safe domain);
|
|
3150
3269
|
// an element that cannot convert fences per element. length/index
|
|
3151
|
-
// reads and dynamic consumers ride the keyed-DOM paths.
|
|
3152
|
-
|
|
3270
|
+
// reads and dynamic consumers ride the keyed-DOM paths. TS literals
|
|
3271
|
+
// take the same build when the slot ITSELF is checked-dynamic — an
|
|
3272
|
+
// `unknown[]` annotation or a collapsed `(string | object)[]` maps
|
|
3273
|
+
// to DYN wholesale now (mapType's dyn-element array rule), so the
|
|
3274
|
+
// literal IS the DOM array.
|
|
3275
|
+
if (isJsSourceFile(expr.getSourceFile()) || mapped?.kind === "dyn") {
|
|
3153
3276
|
const elems = expr.elements.map((el) => {
|
|
3154
3277
|
if (ts.isSpreadElement(el)) {
|
|
3155
3278
|
L.unsupported("SC1090", el, "spread elements in a dynamic (unknown[]) array literal");
|
|
@@ -5053,7 +5176,24 @@ export function lowerElementAccess(L, expr) {
|
|
|
5053
5176
|
? null
|
|
5054
5177
|
: L.mapTypeOf(L.typeOf(expr.expression));
|
|
5055
5178
|
if (receiverIr?.kind === "jsval") {
|
|
5056
|
-
|
|
5179
|
+
// Dispatch follows the RUNTIME world (383(d)): a checker-'any'
|
|
5180
|
+
// receiver whose value LOWERED checked-dynamic (`bag.list[0]` where
|
|
5181
|
+
// bag.list is a routed keyed read off a wrapped island value) takes
|
|
5182
|
+
// the DOM keyed read below — the routed JSVAL arm reads the real
|
|
5183
|
+
// engine element; a jsval-lowered receiver keeps the island read.
|
|
5184
|
+
const recv = L.lowerExpr(expr.expression);
|
|
5185
|
+
if (recv.type.kind !== "dyn")
|
|
5186
|
+
return islandElementRead(L, expr, recv);
|
|
5187
|
+
const rawKey = L.lowerExpr(expr.argumentExpression);
|
|
5188
|
+
const key = rawKey.type.kind === "string"
|
|
5189
|
+
? rawKey
|
|
5190
|
+
: rawKey.type.kind === "f64"
|
|
5191
|
+
? { kind: "toString", operand: rawKey, type: STRING, loc: rawKey.loc }
|
|
5192
|
+
: null;
|
|
5193
|
+
if (key) {
|
|
5194
|
+
const opt = chainGuardedByQuestionDot(expr.expression);
|
|
5195
|
+
return L.maybeNarrow({ kind: "dynKeyGet", key, ...(opt ? { optional: true } : {}), value: recv, type: DYN, loc: locOf(expr) }, expr);
|
|
5196
|
+
}
|
|
5057
5197
|
}
|
|
5058
5198
|
// Typed-array element read `b[i]` — like arrayGet with a scalar
|
|
5059
5199
|
// result; any invalid index traps (the array discipline; JS would
|
|
@@ -5131,9 +5271,12 @@ export function lowerElementAccess(L, expr) {
|
|
|
5131
5271
|
const obj = L.lowerExpr(expr.expression);
|
|
5132
5272
|
if (obj.type.kind === "dyn") {
|
|
5133
5273
|
const rawKey = L.lowerExpr(expr.argumentExpression);
|
|
5274
|
+
// Number, bool, and DYN keys stringify (ToPropertyKey) — the
|
|
5275
|
+
// dyn-keyed read `catchWarning[warning.name]` where the property
|
|
5276
|
+
// chain itself lowered dyn.
|
|
5134
5277
|
const key = rawKey.type.kind === "string"
|
|
5135
5278
|
? rawKey
|
|
5136
|
-
: rawKey.type.kind === "f64"
|
|
5279
|
+
: rawKey.type.kind === "f64" || rawKey.type.kind === "bool" || rawKey.type.kind === "dyn"
|
|
5137
5280
|
? { kind: "toString", operand: rawKey, type: STRING, loc: rawKey.loc }
|
|
5138
5281
|
: null;
|
|
5139
5282
|
if (key) {
|
|
@@ -5499,11 +5642,58 @@ export function lowerElementWrite(L, expr) {
|
|
|
5499
5642
|
const receiverIr = L.mapTypeOf(L.typeOf(target.expression));
|
|
5500
5643
|
if (receiverIr?.kind === "jsval") {
|
|
5501
5644
|
const obj = L.lowerExpr(target.expression);
|
|
5645
|
+
// Dispatch follows the RUNTIME world (383(d)): a checker-'any'
|
|
5646
|
+
// receiver whose value LOWERED checked-dynamic takes the DOM keyed
|
|
5647
|
+
// write — the routed JSVAL arm lands it on the real engine object.
|
|
5648
|
+
if (obj.type.kind === "dyn") {
|
|
5649
|
+
const loc = locOf(expr);
|
|
5650
|
+
const rawKey = L.lowerExpr(target.argumentExpression);
|
|
5651
|
+
const key = rawKey.type.kind === "f64" || rawKey.type.kind === "bool" || rawKey.type.kind === "dyn"
|
|
5652
|
+
? { kind: "toString", operand: rawKey, type: STRING, loc: rawKey.loc }
|
|
5653
|
+
: rawKey;
|
|
5654
|
+
if (key.type.kind !== "string") {
|
|
5655
|
+
L.unsupported("SC1090", target.argumentExpression, `'${L.fmt(key.type)}'-typed keys in keyed writes through 'unknown' values`);
|
|
5656
|
+
}
|
|
5657
|
+
const value = L.coerceToExpected(L.lowerExpr(expr.right), DYN);
|
|
5658
|
+
if (value.type.kind !== "dyn") {
|
|
5659
|
+
L.unsupported("SC1100", expr.right, `assigning '${L.fmt(value.type)}' values through 'unknown' receivers`);
|
|
5660
|
+
}
|
|
5661
|
+
return {
|
|
5662
|
+
kind: "exprStmt",
|
|
5663
|
+
expr: { kind: "libCall", fn: "dyn.keySet", args: [obj, key, value], type: VOID, loc },
|
|
5664
|
+
loc,
|
|
5665
|
+
};
|
|
5666
|
+
}
|
|
5502
5667
|
const key = L.jsvalIn(L.lowerExpr(target.argumentExpression), target.argumentExpression);
|
|
5503
5668
|
const value = L.jsvalIn(L.lowerExpr(expr.right), expr.right);
|
|
5504
5669
|
const loc = locOf(expr);
|
|
5505
5670
|
return { kind: "exprStmt", expr: { kind: "jsOp", op: "setIdx", args: [obj, key, value], type: VOID, loc }, loc };
|
|
5506
5671
|
}
|
|
5672
|
+
// A checked-dynamic receiver TYPE (`unknown[]` and the collapsed
|
|
5673
|
+
// `(string | object)[]` map to DYN wholesale now): the DOM keyed
|
|
5674
|
+
// write — dyn.keySet. Number keys canonicalize through the JS-exact
|
|
5675
|
+
// formatter; ARR receivers take canonical index keys as element
|
|
5676
|
+
// set/extend (undefined-hole padding, JS's length growth exactly),
|
|
5677
|
+
// OBJ receivers set the member, and the runtime throws Node's
|
|
5678
|
+
// TypeErrors on every other kind. Values convert into the DOM;
|
|
5679
|
+
// unconvertible ones fence per site (the dynFrom stance).
|
|
5680
|
+
if (receiverIr?.kind === "dyn") {
|
|
5681
|
+
const obj = L.lowerExpr(target.expression);
|
|
5682
|
+
if (obj.type.kind === "dyn") {
|
|
5683
|
+
const loc = locOf(expr);
|
|
5684
|
+
let key = L.lowerExpr(target.argumentExpression);
|
|
5685
|
+
if (key.type.kind === "f64")
|
|
5686
|
+
key = L.ensureString(key, target.argumentExpression);
|
|
5687
|
+
if (key.type.kind !== "string") {
|
|
5688
|
+
L.unsupported("SC1090", target.argumentExpression, "indexing with non-string or non-number keys");
|
|
5689
|
+
}
|
|
5690
|
+
const value = L.coerceToExpected(L.lowerExpr(expr.right), DYN);
|
|
5691
|
+
if (value.type.kind !== "dyn") {
|
|
5692
|
+
L.unsupported("SC1101", expr.right, `storing '${L.fmt(value.type)}' values in a checked-dynamic array (the value cannot convert into the DOM)`);
|
|
5693
|
+
}
|
|
5694
|
+
return { kind: "exprStmt", expr: { kind: "libCall", fn: "dyn.keySet", args: [obj, key, value], type: VOID, loc }, loc };
|
|
5695
|
+
}
|
|
5696
|
+
}
|
|
5507
5697
|
// Typed-array element write `b[i] = v` — bytesSet: the value is an f64
|
|
5508
5698
|
// the runtime coerces JS-exactly; invalid indices trap (no appends —
|
|
5509
5699
|
// typed arrays are fixed-length; JS would ignore the write).
|
|
@@ -5680,6 +5870,39 @@ export function lowerElementWrite(L, expr) {
|
|
|
5680
5870
|
};
|
|
5681
5871
|
}
|
|
5682
5872
|
}
|
|
5873
|
+
// Keyed write `d[k] = v` on a CHECKED-DYNAMIC receiver (a JS `any`
|
|
5874
|
+
// dictionary — the Object.create(null) memo-table idiom writes
|
|
5875
|
+
// `result[key] = value`): dyn.keySet, exactly the record-mapped dyn
|
|
5876
|
+
// arm above — later writes win in insertion order, number keys
|
|
5877
|
+
// canonicalize through the JS-exact formatter (ToPropertyKey's string
|
|
5878
|
+
// side), index keys on DOM arrays set/extend elements, and non-object
|
|
5879
|
+
// receivers throw Node's TypeErrors at runtime. An UNMAPPED checker
|
|
5880
|
+
// type (static `any` — mapTypeOf answers null without --dynamic)
|
|
5881
|
+
// probes the receiver's own lowered world: a dyn value takes the same
|
|
5882
|
+
// write, anything else falls through to the fences.
|
|
5883
|
+
if (receiverIr?.kind === "dyn" || receiverIr === null) {
|
|
5884
|
+
const obj = receiverIr !== null ? L.lowerExpr(target.expression) : probeLower(L, target.expression);
|
|
5885
|
+
if (obj !== null && obj.type.kind === "dyn") {
|
|
5886
|
+
const loc = locOf(expr);
|
|
5887
|
+
const litKey = recordKeyLiteralText(target.argumentExpression);
|
|
5888
|
+
let key = litKey !== null
|
|
5889
|
+
? { kind: "strLit", value: litKey, type: STRING, loc: locOf(target.argumentExpression) }
|
|
5890
|
+
: L.lowerExpr(target.argumentExpression);
|
|
5891
|
+
if (key.type.kind === "f64")
|
|
5892
|
+
key = L.ensureString(key, target.argumentExpression);
|
|
5893
|
+
if (key.type.kind === "bool" || key.type.kind === "dyn") {
|
|
5894
|
+
key = { kind: "toString", operand: key, type: STRING, loc: locOf(target.argumentExpression) };
|
|
5895
|
+
}
|
|
5896
|
+
if (key.type.kind !== "string") {
|
|
5897
|
+
L.unsupported("SC1090", target.argumentExpression, "indexing checked-dynamic values with non-string or non-number keys");
|
|
5898
|
+
}
|
|
5899
|
+
const value = L.coerceToExpected(L.lowerExpr(expr.right), DYN);
|
|
5900
|
+
if (value.type.kind !== "dyn") {
|
|
5901
|
+
L.unsupported("SC1101", expr.right, `storing '${L.fmt(value.type)}' values in a checked-dynamic object (the value cannot convert into the DOM)`);
|
|
5902
|
+
}
|
|
5903
|
+
return { kind: "exprStmt", expr: { kind: "libCall", fn: "dyn.keySet", args: [obj, key, value], type: VOID, loc }, loc };
|
|
5904
|
+
}
|
|
5905
|
+
}
|
|
5683
5906
|
if (receiverIr?.kind !== "array") {
|
|
5684
5907
|
L.unsupported("SC1090", target, "assignment to non-array elements");
|
|
5685
5908
|
}
|
|
@@ -8086,11 +8309,14 @@ function lowerRuntimeKeyIn(L, expr, loc) {
|
|
|
8086
8309
|
}
|
|
8087
8310
|
/** A regex literal `/ab+c/gi` → regexLit (interned per (pattern, flags)
|
|
8088
8311
|
* by the backend). The TS parser has already syntax-checked the literal;
|
|
8089
|
-
* what remains here
|
|
8090
|
-
* declared-valid TS flags outside this slice)
|
|
8091
|
-
* (
|
|
8092
|
-
*
|
|
8093
|
-
*
|
|
8312
|
+
* what remains here is the flag-alphabet fence (d and v are
|
|
8313
|
+
* declared-valid TS flags outside this slice). Named capture groups
|
|
8314
|
+
* `(?<name>...)` and `\k<name>` backreferences compile — libregexp
|
|
8315
|
+
* executes them natively, replace templates resolve `$<name>` at
|
|
8316
|
+
* runtime, and `.groups` reads desugar at their access sites
|
|
8317
|
+
* (matchResultNamedGroupsOf). The engine validates the pattern itself
|
|
8318
|
+
* lazily at first use (SEMANTICS.md documents the divergence from
|
|
8319
|
+
* Node's parse-time SyntaxError). */
|
|
8094
8320
|
export function lowerRegexLiteral(L, expr) {
|
|
8095
8321
|
const text = expr.text;
|
|
8096
8322
|
const lastSlash = text.lastIndexOf("/");
|
|
@@ -8105,12 +8331,414 @@ export function lowerRegexLiteral(L, expr) {
|
|
|
8105
8331
|
: `the regex '${f}' flag`);
|
|
8106
8332
|
}
|
|
8107
8333
|
}
|
|
8108
|
-
// (?<name>...) — named groups; (?<= / (?<! are lookbehinds and fine.
|
|
8109
|
-
if (/\(\?<(?![=!])/.test(pattern)) {
|
|
8110
|
-
L.unsupported("SC1120", expr, "named capture groups in regex patterns");
|
|
8111
|
-
}
|
|
8112
8334
|
return { kind: "regexLit", pattern, flags, type: REGEX, loc: locOf(expr) };
|
|
8113
8335
|
}
|
|
8336
|
+
/** The pattern's named capture groups, in source order: each `(?<name>`
|
|
8337
|
+
* with its 1-based capture index (numbered and named groups share the
|
|
8338
|
+
* numbering — the index IS the match slice's element position). Null
|
|
8339
|
+
* when the scan can't answer confidently (an unterminated construct, a
|
|
8340
|
+
* `\u`-escaped group name) — callers fence rather than guess. Duplicate
|
|
8341
|
+
* names (ES2025 — valid across alternatives) appear once per
|
|
8342
|
+
* declaration; consumers pick the participating occurrence. The scan
|
|
8343
|
+
* only needs to track escapes, character classes, and `(` kinds; tsc
|
|
8344
|
+
* has already syntax-checked the literal, so a malformed pattern here
|
|
8345
|
+
* answers null and the engine's lazy compile reports it. */
|
|
8346
|
+
export function namedCaptureGroupsOfPattern(pattern) {
|
|
8347
|
+
const groups = [];
|
|
8348
|
+
let captureIndex = 0;
|
|
8349
|
+
let inClass = false;
|
|
8350
|
+
let i = 0;
|
|
8351
|
+
while (i < pattern.length) {
|
|
8352
|
+
const c = pattern[i];
|
|
8353
|
+
if (c === "\\") {
|
|
8354
|
+
i += 2;
|
|
8355
|
+
continue;
|
|
8356
|
+
}
|
|
8357
|
+
if (inClass) {
|
|
8358
|
+
if (c === "]")
|
|
8359
|
+
inClass = false;
|
|
8360
|
+
i++;
|
|
8361
|
+
continue;
|
|
8362
|
+
}
|
|
8363
|
+
if (c === "[") {
|
|
8364
|
+
inClass = true;
|
|
8365
|
+
i++;
|
|
8366
|
+
continue;
|
|
8367
|
+
}
|
|
8368
|
+
if (c !== "(") {
|
|
8369
|
+
i++;
|
|
8370
|
+
continue;
|
|
8371
|
+
}
|
|
8372
|
+
if (pattern[i + 1] !== "?") {
|
|
8373
|
+
captureIndex++;
|
|
8374
|
+
i++;
|
|
8375
|
+
continue;
|
|
8376
|
+
}
|
|
8377
|
+
// (?<name> captures; (?<= and (?<! are lookbehinds; every other
|
|
8378
|
+
// (?… form — (?:, (?=, (?!, modifier groups (?i: — is non-capturing.
|
|
8379
|
+
if (pattern[i + 2] === "<" && pattern[i + 3] !== "=" && pattern[i + 3] !== "!") {
|
|
8380
|
+
const gt = pattern.indexOf(">", i + 3);
|
|
8381
|
+
if (gt < 0)
|
|
8382
|
+
return null;
|
|
8383
|
+
const name = pattern.slice(i + 3, gt);
|
|
8384
|
+
if (name.includes("\\"))
|
|
8385
|
+
return null; // \u-escaped names: bytecode spells them decoded
|
|
8386
|
+
captureIndex++;
|
|
8387
|
+
groups.push({ name, index: captureIndex });
|
|
8388
|
+
i = gt + 1;
|
|
8389
|
+
continue;
|
|
8390
|
+
}
|
|
8391
|
+
i += 2;
|
|
8392
|
+
}
|
|
8393
|
+
return groups;
|
|
8394
|
+
}
|
|
8395
|
+
/** The statically-known regex PATTERN behind an expression: a regex
|
|
8396
|
+
* literal, a const local initialized with one (the crypto.js
|
|
8397
|
+
* `const regexp = /(?<m>\d+)/` shape), or `new RegExp("...")` over a
|
|
8398
|
+
* string literal (the cooked text IS the pattern). Null when the regex
|
|
8399
|
+
* only exists at runtime — .groups consumers fence there. */
|
|
8400
|
+
export function staticRegexPatternOf(L, e) {
|
|
8401
|
+
let expr = e;
|
|
8402
|
+
for (;;) {
|
|
8403
|
+
if (ts.isParenthesizedExpression(expr) || ts.isNonNullExpression(expr)) {
|
|
8404
|
+
expr = expr.expression;
|
|
8405
|
+
continue;
|
|
8406
|
+
}
|
|
8407
|
+
break;
|
|
8408
|
+
}
|
|
8409
|
+
if (ts.isRegularExpressionLiteral(expr)) {
|
|
8410
|
+
const text = expr.text;
|
|
8411
|
+
return text.slice(1, text.lastIndexOf("/"));
|
|
8412
|
+
}
|
|
8413
|
+
if (ts.isNewExpression(expr) &&
|
|
8414
|
+
ts.isIdentifier(expr.expression) &&
|
|
8415
|
+
expr.expression.text === "RegExp" &&
|
|
8416
|
+
L.isStdlibGlobal(expr.expression, "RegExp") &&
|
|
8417
|
+
expr.arguments !== undefined &&
|
|
8418
|
+
expr.arguments.length >= 1 &&
|
|
8419
|
+
(ts.isStringLiteral(expr.arguments[0]) || ts.isNoSubstitutionTemplateLiteral(expr.arguments[0]))) {
|
|
8420
|
+
return expr.arguments[0].text;
|
|
8421
|
+
}
|
|
8422
|
+
if (ts.isIdentifier(expr)) {
|
|
8423
|
+
const init = constInitializerOf(L, expr);
|
|
8424
|
+
if (init !== null)
|
|
8425
|
+
return staticRegexPatternOf(L, init);
|
|
8426
|
+
}
|
|
8427
|
+
return null;
|
|
8428
|
+
}
|
|
8429
|
+
/** The initializer behind a CONST identifier binding (a plain
|
|
8430
|
+
* VariableDeclaration under a const list) — the one-hop provenance step
|
|
8431
|
+
* the regex traces walk. Null for let/var (reassignable), params,
|
|
8432
|
+
* destructured bindings, and unresolvable names. */
|
|
8433
|
+
function constInitializerOf(L, ident) {
|
|
8434
|
+
const sym = L.resolveValueSymbol(ident);
|
|
8435
|
+
const decl = sym ? L.checker.valueDeclarationOf(sym) : undefined;
|
|
8436
|
+
if (decl === undefined || !ts.isVariableDeclaration(decl) || decl.initializer === undefined)
|
|
8437
|
+
return null;
|
|
8438
|
+
if (!ts.isVariableDeclarationList(decl.parent) || (decl.parent.flags & ts.NodeFlags.Const) === 0)
|
|
8439
|
+
return null;
|
|
8440
|
+
return decl.initializer;
|
|
8441
|
+
}
|
|
8442
|
+
/** The named-group table of the regex that PRODUCED a match-result
|
|
8443
|
+
* expression — the `.groups` desugar's provenance question. Traces
|
|
8444
|
+
* (through parens, `!`, and const bindings):
|
|
8445
|
+
* - `re.exec(s)` / `s.match(re)` → re's static pattern
|
|
8446
|
+
* - a matchAll ROW: `rows[i]`, the for-of binding over a direct
|
|
8447
|
+
* `s.matchAll(re)` or a stored const drain → re's static pattern
|
|
8448
|
+
* Answers null when the producing regex isn't statically known (the
|
|
8449
|
+
* caller keeps its fence), or the groups list (possibly empty — a
|
|
8450
|
+
* traced regex WITHOUT named groups is the Node-undefined case). */
|
|
8451
|
+
export function matchResultNamedGroupsOf(L, e) {
|
|
8452
|
+
const reExpr = matchProducerRegexOf(L, e);
|
|
8453
|
+
if (reExpr === null)
|
|
8454
|
+
return null;
|
|
8455
|
+
const pattern = staticRegexPatternOf(L, reExpr);
|
|
8456
|
+
if (pattern === null)
|
|
8457
|
+
return null;
|
|
8458
|
+
return namedCaptureGroupsOfPattern(pattern);
|
|
8459
|
+
}
|
|
8460
|
+
/** The regex EXPRESSION whose match produced `e` (see
|
|
8461
|
+
* matchResultNamedGroupsOf for the traced shapes). */
|
|
8462
|
+
function matchProducerRegexOf(L, e) {
|
|
8463
|
+
let expr = e;
|
|
8464
|
+
for (;;) {
|
|
8465
|
+
if (ts.isParenthesizedExpression(expr) || ts.isNonNullExpression(expr)) {
|
|
8466
|
+
expr = expr.expression;
|
|
8467
|
+
continue;
|
|
8468
|
+
}
|
|
8469
|
+
break;
|
|
8470
|
+
}
|
|
8471
|
+
// The direct producers: re.exec(s) / s.match(re) — the receiver's
|
|
8472
|
+
// mapped type pins the STDLIB operation (a regex for exec, a string
|
|
8473
|
+
// or its nullable spelling for match — the claim lower-containers
|
|
8474
|
+
// makes), so a user method that happens to be named `match` with a
|
|
8475
|
+
// regex argument never traces.
|
|
8476
|
+
if (ts.isCallExpression(expr) && ts.isPropertyAccessExpression(expr.expression)) {
|
|
8477
|
+
const name = expr.expression.name.text;
|
|
8478
|
+
const recvT = L.mapTypeOf(L.typeOf(expr.expression.expression));
|
|
8479
|
+
if (name === "exec" && expr.arguments.length === 1 && recvT?.kind === "regex") {
|
|
8480
|
+
return expr.expression.expression;
|
|
8481
|
+
}
|
|
8482
|
+
if (name === "match" && expr.arguments.length === 1 &&
|
|
8483
|
+
(recvT?.kind === "string" || (recvT !== null && recvT !== undefined && nullableStringType(L, recvT))) &&
|
|
8484
|
+
L.mapTypeOf(L.typeOf(expr.arguments[0]))?.kind === "regex") {
|
|
8485
|
+
return expr.arguments[0];
|
|
8486
|
+
}
|
|
8487
|
+
}
|
|
8488
|
+
// A matchAll ROW by element access: rows[i] with rows tracing to the
|
|
8489
|
+
// drain call — or the direct s.matchAll(re)[i] spelling.
|
|
8490
|
+
if (ts.isElementAccessExpression(expr)) {
|
|
8491
|
+
return matchAllRegexOf(L, expr.expression);
|
|
8492
|
+
}
|
|
8493
|
+
if (ts.isIdentifier(expr)) {
|
|
8494
|
+
// The for-of binding over a matchAll drain: `for (const m of
|
|
8495
|
+
// s.matchAll(re))` (or over a stored const rows).
|
|
8496
|
+
const sym = L.resolveValueSymbol(expr);
|
|
8497
|
+
const decl = sym ? L.checker.valueDeclarationOf(sym) : undefined;
|
|
8498
|
+
if (decl !== undefined && ts.isVariableDeclaration(decl) &&
|
|
8499
|
+
ts.isVariableDeclarationList(decl.parent) && ts.isForOfStatement(decl.parent.parent)) {
|
|
8500
|
+
return matchAllRegexOf(L, decl.parent.parent.expression);
|
|
8501
|
+
}
|
|
8502
|
+
// A stored const match result: `const m = re.exec(s)`.
|
|
8503
|
+
const init = constInitializerOf(L, expr);
|
|
8504
|
+
if (init !== null)
|
|
8505
|
+
return matchProducerRegexOf(L, init);
|
|
8506
|
+
}
|
|
8507
|
+
return null;
|
|
8508
|
+
}
|
|
8509
|
+
/** The regex argument of a (possibly const-stored) `s.matchAll(re)`. */
|
|
8510
|
+
function matchAllRegexOf(L, e) {
|
|
8511
|
+
let expr = e;
|
|
8512
|
+
for (;;) {
|
|
8513
|
+
if (ts.isParenthesizedExpression(expr) || ts.isNonNullExpression(expr)) {
|
|
8514
|
+
expr = expr.expression;
|
|
8515
|
+
continue;
|
|
8516
|
+
}
|
|
8517
|
+
break;
|
|
8518
|
+
}
|
|
8519
|
+
if (ts.isCallExpression(expr) && ts.isPropertyAccessExpression(expr.expression) &&
|
|
8520
|
+
expr.expression.name.text === "matchAll" && expr.arguments.length === 1 &&
|
|
8521
|
+
L.mapTypeOf(L.typeOf(expr.expression.expression))?.kind === "string" &&
|
|
8522
|
+
L.mapTypeOf(L.typeOf(expr.arguments[0]))?.kind === "regex") {
|
|
8523
|
+
return expr.arguments[0];
|
|
8524
|
+
}
|
|
8525
|
+
// The eager-spread spelling: `[...s.matchAll(re)]`.
|
|
8526
|
+
if (ts.isArrayLiteralExpression(expr) && expr.elements.length === 1) {
|
|
8527
|
+
const only = expr.elements[0];
|
|
8528
|
+
if (ts.isSpreadElement(only))
|
|
8529
|
+
return matchAllRegexOf(L, only.expression);
|
|
8530
|
+
}
|
|
8531
|
+
if (ts.isIdentifier(expr)) {
|
|
8532
|
+
const init = constInitializerOf(L, expr);
|
|
8533
|
+
if (init !== null)
|
|
8534
|
+
return matchAllRegexOf(L, init);
|
|
8535
|
+
}
|
|
8536
|
+
return null;
|
|
8537
|
+
}
|
|
8538
|
+
/** `m.groups` on a match result whose producing regex is statically
|
|
8539
|
+
* known: the honest slice already HOLDS every named group's value at
|
|
8540
|
+
* its capture index, so the groups object is a compile-time record
|
|
8541
|
+
* projection — `{ year: m[1], month: m[2] }` — built by one interned
|
|
8542
|
+
* helper per (shape, index list, receiver type). Node's exact shape
|
|
8543
|
+
* decisions:
|
|
8544
|
+
* - no named groups → `undefined` (the identifier-receiver shapes —
|
|
8545
|
+
* a call receiver would lose its own null trap, so those keep the
|
|
8546
|
+
* member fence);
|
|
8547
|
+
* - a nullable receiver (the JS `s.match(re).groups` idiom) throws
|
|
8548
|
+
* Node's exact TypeError on the unit arms;
|
|
8549
|
+
* - every declared name is a key (nonparticipating groups hold "" —
|
|
8550
|
+
* divergence 51's rule, exactly the slice elements they project);
|
|
8551
|
+
* - ES2025 duplicate names (distinct alternatives) project the first
|
|
8552
|
+
* participating occurrence: at most one participates, and a
|
|
8553
|
+
* participating-empty "" falls through to slots that are "" anyway;
|
|
8554
|
+
* - key order is group declaration order (declaredOrder), Node's own.
|
|
8555
|
+
* The record has no prototype — Node's groups object is null-prototype,
|
|
8556
|
+
* observable only through util.inspect's "[Object: null prototype]"
|
|
8557
|
+
* prefix (the Object.groupBy stance, ledgered). Null when the receiver
|
|
8558
|
+
* isn't a match slice or the regex isn't traceable — the caller's
|
|
8559
|
+
* member fence (with the groups hint) names the gap. */
|
|
8560
|
+
export function lowerMatchGroupsRead(L, expr) {
|
|
8561
|
+
if (expr.name.text !== "groups" || expr.questionDotToken !== undefined)
|
|
8562
|
+
return null;
|
|
8563
|
+
const loc = locOf(expr);
|
|
8564
|
+
const strArr = arrayOf(STRING);
|
|
8565
|
+
const recvT = L.mapTypeOf(L.typeOf(expr.expression));
|
|
8566
|
+
if (!recvT || !isMatchSliceType(L, recvT) || !L.isStdlibMember(expr))
|
|
8567
|
+
return null;
|
|
8568
|
+
const groups = matchResultNamedGroupsOf(L, expr.expression);
|
|
8569
|
+
if (groups === null)
|
|
8570
|
+
return null;
|
|
8571
|
+
if (groups.length === 0) {
|
|
8572
|
+
// Node: a match result of a group-less regex answers undefined —
|
|
8573
|
+
// the unit literal, exactly the `undefined` identifier's lowering.
|
|
8574
|
+
// Identifier receivers only (evaluation-free): a CALL receiver's
|
|
8575
|
+
// exec/match must still run and null-trap, which a folded unit
|
|
8576
|
+
// cannot carry — those keep the fence.
|
|
8577
|
+
let r = expr.expression;
|
|
8578
|
+
while (ts.isParenthesizedExpression(r) || ts.isNonNullExpression(r))
|
|
8579
|
+
r = r.expression;
|
|
8580
|
+
if (ts.isIdentifier(r) && typeEquals(recvT, strArr)) {
|
|
8581
|
+
return { kind: "unitLit", unit: "undefined", type: UNDEFINED_T, loc };
|
|
8582
|
+
}
|
|
8583
|
+
return null;
|
|
8584
|
+
}
|
|
8585
|
+
const recv = L.lowerExpr(expr.expression);
|
|
8586
|
+
if (!isMatchSliceType(L, recv.type))
|
|
8587
|
+
return null;
|
|
8588
|
+
return lowerGroupsProjection(L, recv, groups, loc);
|
|
8589
|
+
}
|
|
8590
|
+
/** True when the IR type is a string-or-units union — the Dict<string>
|
|
8591
|
+
* member spelling (`process.versions.openssl`), match's nullable
|
|
8592
|
+
* receiver claim. */
|
|
8593
|
+
function nullableStringType(L, t) {
|
|
8594
|
+
if (t.kind !== "union")
|
|
8595
|
+
return false;
|
|
8596
|
+
const arms = L.unions.get(t.unionId)?.arms ?? [];
|
|
8597
|
+
return arms.some((a) => a.kind === "string") && arms.every((a) => a.kind === "string" || isUnitType(a));
|
|
8598
|
+
}
|
|
8599
|
+
/** True when the IR type is the honest match slice — `string[]`, or a
|
|
8600
|
+
* union of it with unit arms only (`string[] | null`). The groups
|
|
8601
|
+
* projection's receiver gate, shared with the destructure interception. */
|
|
8602
|
+
export function isMatchSliceType(L, t) {
|
|
8603
|
+
if (typeEquals(t, arrayOf(STRING)))
|
|
8604
|
+
return true;
|
|
8605
|
+
return t.kind === "union" && matchSliceUnionArms(L, t.unionId) !== null;
|
|
8606
|
+
}
|
|
8607
|
+
/** The union-arm layout of a nullable match slice (`string[] | null`,
|
|
8608
|
+
* matchAll's checker spellings with undefined arms included): the
|
|
8609
|
+
* string[] arm's tag plus each unit arm's tag and spelling. Null when
|
|
8610
|
+
* the union holds anything else. */
|
|
8611
|
+
function matchSliceUnionArms(L, unionId) {
|
|
8612
|
+
const def = L.unions.get(unionId);
|
|
8613
|
+
if (!def)
|
|
8614
|
+
return null;
|
|
8615
|
+
const strArr = arrayOf(STRING);
|
|
8616
|
+
const arrTag = def.arms.findIndex((a) => typeEquals(a, strArr));
|
|
8617
|
+
if (arrTag < 0)
|
|
8618
|
+
return null;
|
|
8619
|
+
const units = [];
|
|
8620
|
+
for (let i = 0; i < def.arms.length; i++) {
|
|
8621
|
+
if (i === arrTag)
|
|
8622
|
+
continue;
|
|
8623
|
+
const a = def.arms[i];
|
|
8624
|
+
if (!isUnitType(a))
|
|
8625
|
+
return null;
|
|
8626
|
+
units.push({ tag: i, unit: a.kind === "nullT" ? "null" : "undefined" });
|
|
8627
|
+
}
|
|
8628
|
+
return { arrTag, units };
|
|
8629
|
+
}
|
|
8630
|
+
/** The groups-record projection call: the result is the CHECKER'S OWN
|
|
8631
|
+
* shape — `{ [key: string]: string }`, the hybrid index-signature
|
|
8632
|
+
* record with no declared fields — holding one OVERFLOW entry per
|
|
8633
|
+
* group name in declaration order, so every downstream consumer
|
|
8634
|
+
* (recordKeyGet reads, Object.keys, JSON, the `| undefined` union
|
|
8635
|
+
* wrap) meets exactly the type tsc reported. One lifted helper per
|
|
8636
|
+
* (index list, receiver representation) builds it; see
|
|
8637
|
+
* lowerMatchGroupsRead for the semantics the body encodes. */
|
|
8638
|
+
export function lowerGroupsProjection(L, recv, groups, loc) {
|
|
8639
|
+
// Name → its capture indices, in declaration order (duplicates keep
|
|
8640
|
+
// every occurrence — the projection picks the participating one).
|
|
8641
|
+
const order = [];
|
|
8642
|
+
const indicesByName = new Map();
|
|
8643
|
+
for (const g of groups) {
|
|
8644
|
+
const got = indicesByName.get(g.name);
|
|
8645
|
+
if (got) {
|
|
8646
|
+
got.push(g.index);
|
|
8647
|
+
}
|
|
8648
|
+
else {
|
|
8649
|
+
indicesByName.set(g.name, [g.index]);
|
|
8650
|
+
order.push(g.name);
|
|
8651
|
+
}
|
|
8652
|
+
}
|
|
8653
|
+
const shapeId = L.shapes.intern([], false, STRING);
|
|
8654
|
+
const recordT = { kind: "record", shapeId };
|
|
8655
|
+
const recvKey = recv.type.kind === "union" ? recv.type.unionId : "arr";
|
|
8656
|
+
const key = `regexgroups:${shapeId}:${groups.map((g) => `${g.name}=${g.index}`).join(",")}:${recvKey}`;
|
|
8657
|
+
let helper = L.widthHelpers.get(key);
|
|
8658
|
+
if (!helper) {
|
|
8659
|
+
helper = `%regex.groups.${L.widthHelpers.size}`;
|
|
8660
|
+
L.widthHelpers.set(key, helper);
|
|
8661
|
+
const body = [];
|
|
8662
|
+
const locals = [{ id: "m.0", name: "m", type: recv.type, mutable: true }];
|
|
8663
|
+
let mRef = { kind: "varRef", localId: "m.0", type: recv.type, loc };
|
|
8664
|
+
if (recv.type.kind === "union") {
|
|
8665
|
+
// Node's exact TypeError per unit arm, then the checked narrow.
|
|
8666
|
+
const arms = matchSliceUnionArms(L, recv.type.unionId);
|
|
8667
|
+
for (const u of arms.units) {
|
|
8668
|
+
body.push({
|
|
8669
|
+
kind: "if",
|
|
8670
|
+
cond: { kind: "unionIsTag", unionId: recv.type.unionId, tag: u.tag, negated: false, value: mRef, type: BOOL, loc },
|
|
8671
|
+
then: [
|
|
8672
|
+
{
|
|
8673
|
+
kind: "throw",
|
|
8674
|
+
value: {
|
|
8675
|
+
kind: "libCall",
|
|
8676
|
+
fn: "error.new",
|
|
8677
|
+
args: [
|
|
8678
|
+
{ kind: "strLit", value: `Cannot read properties of ${u.unit} (reading 'groups')`, type: STRING, loc },
|
|
8679
|
+
],
|
|
8680
|
+
type: { kind: "object", className: "%TypeError" },
|
|
8681
|
+
loc,
|
|
8682
|
+
},
|
|
8683
|
+
loc,
|
|
8684
|
+
},
|
|
8685
|
+
],
|
|
8686
|
+
else_: null,
|
|
8687
|
+
loc,
|
|
8688
|
+
});
|
|
8689
|
+
}
|
|
8690
|
+
const narrowed = { kind: "unionNarrow", unionId: recv.type.unionId, tag: arms.arrTag, value: mRef, type: arrayOf(STRING), loc };
|
|
8691
|
+
locals.push({ id: "arr.0", name: "arr", type: arrayOf(STRING), mutable: false });
|
|
8692
|
+
body.push({ kind: "varDecl", localId: "arr.0", init: narrowed, loc });
|
|
8693
|
+
mRef = { kind: "varRef", localId: "arr.0", type: arrayOf(STRING), loc };
|
|
8694
|
+
}
|
|
8695
|
+
const num = (value) => ({ kind: "numLit", value, type: F64, loc });
|
|
8696
|
+
const elem = (index) => ({ kind: "arrayGet", arr: mRef, index: num(index), type: STRING, loc });
|
|
8697
|
+
const values = new Map();
|
|
8698
|
+
order.forEach((name, i) => {
|
|
8699
|
+
const idxs = indicesByName.get(name);
|
|
8700
|
+
if (idxs.length === 1) {
|
|
8701
|
+
values.set(name, elem(idxs[0]));
|
|
8702
|
+
return;
|
|
8703
|
+
}
|
|
8704
|
+
// Duplicates: the first non-"" occurrence (at most one
|
|
8705
|
+
// participates; all-"" answers "" like any nonparticipating slot).
|
|
8706
|
+
const id = `g${i}.0`;
|
|
8707
|
+
locals.push({ id, name: `g${i}`, type: STRING, mutable: true });
|
|
8708
|
+
body.push({ kind: "varDecl", localId: id, init: elem(idxs[0]), loc });
|
|
8709
|
+
const gRef = { kind: "varRef", localId: id, type: STRING, loc };
|
|
8710
|
+
for (const idx of idxs.slice(1)) {
|
|
8711
|
+
body.push({
|
|
8712
|
+
kind: "if",
|
|
8713
|
+
cond: { kind: "strEq", negated: false, left: gRef, right: { kind: "strLit", value: "", type: STRING, loc }, type: BOOL, loc },
|
|
8714
|
+
then: [{ kind: "assign", localId: id, value: elem(idx), loc }],
|
|
8715
|
+
else_: null,
|
|
8716
|
+
loc,
|
|
8717
|
+
});
|
|
8718
|
+
}
|
|
8719
|
+
values.set(name, gRef);
|
|
8720
|
+
});
|
|
8721
|
+
body.push({
|
|
8722
|
+
kind: "return",
|
|
8723
|
+
value: {
|
|
8724
|
+
kind: "recordLit",
|
|
8725
|
+
fields: order.map((name) => ({ name, value: values.get(name), overflow: true })),
|
|
8726
|
+
type: recordT,
|
|
8727
|
+
loc,
|
|
8728
|
+
},
|
|
8729
|
+
loc,
|
|
8730
|
+
});
|
|
8731
|
+
L.liftedFns.push({
|
|
8732
|
+
name: helper,
|
|
8733
|
+
params: [{ localId: "m.0", name: "m", type: recv.type }],
|
|
8734
|
+
returnType: recordT,
|
|
8735
|
+
locals,
|
|
8736
|
+
body,
|
|
8737
|
+
loc,
|
|
8738
|
+
});
|
|
8739
|
+
}
|
|
8740
|
+
return { kind: "call", callee: helper, args: [recv], type: recordT, loc };
|
|
8741
|
+
}
|
|
8114
8742
|
/** Field read `obj.f` on class-instance and record receivers, through the
|
|
8115
8743
|
* shared FieldTarget union (fieldGet / recordGet). Bound method references
|
|
8116
8744
|
* on classes are rejected specifically; func-typed record fields are
|