@scriptc/compiler 0.0.10 → 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 +34 -2
- package/dist/backend/emission/emit-exprs.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/emitter.js +59 -4
- 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 +20 -7
- package/dist/diagnostics/diagnostic.js +44 -11
- package/dist/diagnostics/diagnostic.js.map +1 -1
- package/dist/frontend/lowering/lower-builtins.d.ts +56 -0
- package/dist/frontend/lowering/lower-builtins.js +235 -4
- package/dist/frontend/lowering/lower-builtins.js.map +1 -1
- package/dist/frontend/lowering/lower-calls.js +38 -4
- 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 +4 -0
- package/dist/frontend/lowering/lower-containers.js +69 -7
- 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 +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 +472 -13
- 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 +141 -47
- package/dist/frontend/lowering/lower-modules.js.map +1 -1
- package/dist/frontend/lowering/lower-namespaces.d.ts +14 -0
- package/dist/frontend/lowering/lower-namespaces.js +122 -15
- package/dist/frontend/lowering/lower-namespaces.js.map +1 -1
- package/dist/frontend/lowering/lower-stmts.js +80 -6
- package/dist/frontend/lowering/lower-stmts.js.map +1 -1
- package/dist/frontend/lowering/lower-stream.js +113 -6
- package/dist/frontend/lowering/lower-stream.js.map +1 -1
- package/dist/frontend/lowering/lowerer.d.ts +17 -0
- package/dist/frontend/lowering/lowerer.js +105 -17
- 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/index.d.ts +2 -1
- package/dist/index.js +137 -4
- package/dist/index.js.map +1 -1
- package/dist/ir/nodes.d.ts +64 -8
- package/dist/ir/nodes.js +21 -3
- package/dist/ir/nodes.js.map +1 -1
- package/dist/ir/validate.js +11 -2
- 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 +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 +32 -5
- package/dist/library/profile.js +77 -13
- 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,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 };
|
|
@@ -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).
|
|
@@ -1177,6 +1197,15 @@ function lowerExprInner(L, expr) {
|
|
|
1177
1197
|
}
|
|
1178
1198
|
return read;
|
|
1179
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
|
+
}
|
|
1180
1209
|
// `m.index` on a for-of-over-matchAll binding reads the companion-
|
|
1181
1210
|
// index array at this iteration's cursor — always a number: every
|
|
1182
1211
|
// drained row matched, so the lib's `.index` is never undefined
|
|
@@ -1552,6 +1581,31 @@ function lowerExprInner(L, expr) {
|
|
|
1552
1581
|
const opt = chainGuardedByQuestionDot(expr.expression);
|
|
1553
1582
|
return L.maybeNarrow({ kind: "dynKeyGet", key, ...(opt ? { optional: true } : {}), value: recvLowered, type: DYN, loc }, expr);
|
|
1554
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
|
+
}
|
|
1555
1609
|
// An ABSTRACT property through an abstract-typed receiver: the
|
|
1556
1610
|
// declaration is erased at runtime — Node defines no field for it,
|
|
1557
1611
|
// each concrete subclass declares its OWN (at its own layout
|
|
@@ -2994,8 +3048,8 @@ export function lowerIntrinsicProperty(L, expr) {
|
|
|
2994
3048
|
// No ArrayBuffer VALUE exists — `.buffer` compiles only in the
|
|
2995
3049
|
// one composed position that consumes it, the DataView
|
|
2996
3050
|
// 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
|
|
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)");
|
|
2999
3053
|
}
|
|
3000
3054
|
if (name === "slice" || name === "subarray" || name === "set" || name === "toString") {
|
|
3001
3055
|
L.unsupported("SC1090", expr, `typed-array methods as values (call '${name}' directly)`);
|
|
@@ -8255,11 +8309,14 @@ function lowerRuntimeKeyIn(L, expr, loc) {
|
|
|
8255
8309
|
}
|
|
8256
8310
|
/** A regex literal `/ab+c/gi` → regexLit (interned per (pattern, flags)
|
|
8257
8311
|
* 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
|
-
*
|
|
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). */
|
|
8263
8320
|
export function lowerRegexLiteral(L, expr) {
|
|
8264
8321
|
const text = expr.text;
|
|
8265
8322
|
const lastSlash = text.lastIndexOf("/");
|
|
@@ -8274,12 +8331,414 @@ export function lowerRegexLiteral(L, expr) {
|
|
|
8274
8331
|
: `the regex '${f}' flag`);
|
|
8275
8332
|
}
|
|
8276
8333
|
}
|
|
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
8334
|
return { kind: "regexLit", pattern, flags, type: REGEX, loc: locOf(expr) };
|
|
8282
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
|
+
}
|
|
8283
8742
|
/** Field read `obj.f` on class-instance and record receivers, through the
|
|
8284
8743
|
* shared FieldTarget union (fieldGet / recordGet). Bound method references
|
|
8285
8744
|
* on classes are rejected specifically; func-typed record fields are
|