@scriptc/compiler 0.0.7 → 0.0.9
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 +13 -0
- package/dist/backend/cc.js +14 -0
- package/dist/backend/cc.js.map +1 -1
- package/dist/backend/emission/emit-exprs.js +155 -6
- package/dist/backend/emission/emit-exprs.js.map +1 -1
- package/dist/backend/emission/emit-island.js +9 -1
- package/dist/backend/emission/emit-island.js.map +1 -1
- package/dist/backend/emission/emit-stmts.js +3 -1
- package/dist/backend/emission/emit-stmts.js.map +1 -1
- package/dist/backend/emission/emit-types.d.ts +3 -0
- package/dist/backend/emission/emit-types.js +12 -0
- package/dist/backend/emission/emit-types.js.map +1 -1
- package/dist/backend/emission/emit-walkers.d.ts +7 -5
- package/dist/backend/emission/emit-walkers.js +45 -18
- package/dist/backend/emission/emit-walkers.js.map +1 -1
- package/dist/backend/emission/emitter.d.ts +1 -1
- package/dist/backend/emission/emitter.js +35 -10
- package/dist/backend/emission/emitter.js.map +1 -1
- package/dist/backend/emission/may-throw.js +4 -3
- package/dist/backend/emission/may-throw.js.map +1 -1
- package/dist/backend/llvm/emitter.js +300 -18
- package/dist/backend/llvm/emitter.js.map +1 -1
- package/dist/diagnostics/diagnostic.d.ts +98 -11
- package/dist/diagnostics/diagnostic.js +185 -25
- package/dist/diagnostics/diagnostic.js.map +1 -1
- package/dist/frontend/lib-contract.d.ts +24 -6
- package/dist/frontend/lib-contract.js +125 -44
- package/dist/frontend/lib-contract.js.map +1 -1
- package/dist/frontend/lowering/lower-assert.js +16 -0
- package/dist/frontend/lowering/lower-assert.js.map +1 -1
- package/dist/frontend/lowering/lower-builtins.d.ts +11 -0
- package/dist/frontend/lowering/lower-builtins.js +127 -4
- package/dist/frontend/lowering/lower-builtins.js.map +1 -1
- package/dist/frontend/lowering/lower-calls.d.ts +77 -18
- package/dist/frontend/lowering/lower-calls.js +978 -44
- package/dist/frontend/lowering/lower-calls.js.map +1 -1
- package/dist/frontend/lowering/lower-classes.d.ts +14 -1
- package/dist/frontend/lowering/lower-classes.js +112 -16
- package/dist/frontend/lowering/lower-classes.js.map +1 -1
- package/dist/frontend/lowering/lower-containers.d.ts +4 -2
- package/dist/frontend/lowering/lower-containers.js +236 -44
- package/dist/frontend/lowering/lower-containers.js.map +1 -1
- package/dist/frontend/lowering/lower-emitter.js +55 -1
- package/dist/frontend/lowering/lower-emitter.js.map +1 -1
- package/dist/frontend/lowering/lower-exprs.d.ts +26 -3
- package/dist/frontend/lowering/lower-exprs.js +507 -47
- package/dist/frontend/lowering/lower-exprs.js.map +1 -1
- package/dist/frontend/lowering/lower-island.d.ts +7 -0
- package/dist/frontend/lowering/lower-island.js +71 -10
- package/dist/frontend/lowering/lower-island.js.map +1 -1
- package/dist/frontend/lowering/lower-mixins.d.ts +1 -1
- package/dist/frontend/lowering/lower-mixins.js +1 -1
- package/dist/frontend/lowering/lower-modules.js +95 -4
- package/dist/frontend/lowering/lower-modules.js.map +1 -1
- package/dist/frontend/lowering/lower-stmts.js +866 -56
- package/dist/frontend/lowering/lower-stmts.js.map +1 -1
- package/dist/frontend/lowering/lower-stream.js +28 -2
- package/dist/frontend/lowering/lower-stream.js.map +1 -1
- package/dist/frontend/lowering/lowerer.d.ts +85 -17
- package/dist/frontend/lowering/lowerer.js +459 -37
- package/dist/frontend/lowering/lowerer.js.map +1 -1
- package/dist/frontend/lowering/surfaces.js +20 -3
- package/dist/frontend/lowering/surfaces.js.map +1 -1
- package/dist/frontend/npm-static-rewrite.d.ts +7 -2
- package/dist/frontend/npm-static-rewrite.js +566 -138
- package/dist/frontend/npm-static-rewrite.js.map +1 -1
- package/dist/frontend/npm-static.js +11 -2
- package/dist/frontend/npm-static.js.map +1 -1
- package/dist/frontend/program.js +5 -5
- package/dist/frontend/program.js.map +1 -1
- package/dist/frontend/shared.d.ts +6 -1
- package/dist/frontend/shared.js +46 -1
- package/dist/frontend/shared.js.map +1 -1
- package/dist/frontend/types.d.ts +28 -1
- package/dist/frontend/types.js +304 -10
- package/dist/frontend/types.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +181 -26
- package/dist/index.js.map +1 -1
- package/dist/ir/nodes.d.ts +159 -9
- package/dist/ir/nodes.js +76 -6
- package/dist/ir/nodes.js.map +1 -1
- package/dist/ir/validate.js +93 -14
- package/dist/ir/validate.js.map +1 -1
- package/dist/library/profile.d.ts +17 -3
- package/dist/library/profile.js +38 -9
- package/dist/library/profile.js.map +1 -1
- package/dist/library/sidecar.js +162 -43
- package/dist/library/sidecar.js.map +1 -1
- package/dist/library/trap-teaching.d.ts +11 -0
- package/dist/library/trap-teaching.js +49 -0
- package/dist/library/trap-teaching.js.map +1 -0
- package/package.json +2 -2
- package/surface-manifest.json +46 -4
|
@@ -8,13 +8,14 @@ import { BOOL, BYTES_U8, CAUGHT, DYN, F64, JSVAL, STRING, UNDEFINED_T, VOID, arr
|
|
|
8
8
|
import { PoisonError, boundIdentifiersOf, dynFallbackType, dynUndefinedExpr, importCallHandleType, neverTaintedJsType, stmtUsesIsland, uncheckedOverloadHandleCall } from "./lowerer.js";
|
|
9
9
|
import { enforceLibBoundary } from "./lib-boundary.js";
|
|
10
10
|
import { cjsExportAssignmentOf, cjsExportDiscardReason, cjsExportTargetLiteral, isCjsJsFile, isJsSourceFile, locOf, requireSpecOf } from "../program.js";
|
|
11
|
-
import { COMPOUND_ASSIGN_OPS, UNSUPPORTED_STMT, isStdlibMember, sideEffectFreeOptionValue, stdlibGlobalAliasDecl, stdlibGlobalNameOf } from "./surfaces.js";
|
|
11
|
+
import { COMPOUND_ASSIGN_OPS, STR_METHODS, UNSUPPORTED_STMT, isStdlibMember, sideEffectFreeOptionValue, stdlibGlobalAliasDecl, stdlibGlobalNameOf } from "./surfaces.js";
|
|
12
12
|
import { isProvenanceSourceFile } from "../provenance-registry.js";
|
|
13
13
|
import { lowerImportEquals, nsWritableTarget } from "./lower-namespaces.js";
|
|
14
14
|
import { expandoWritableTarget, lowerExpandoAssignStmt } from "./lower-expando.js";
|
|
15
|
-
import { lowerForOfMap, lowerForOfSearchParams, lowerForOfSet, objectIterOverIndexShape } from "./lower-containers.js";
|
|
16
|
-
import { bindingGenericFnInfoOf, bindingGenericFnNodeOf, implicitLocalFnInfoOf, implicitLocalFnNodeOf, recordKeysArrayCall } from "./lower-calls.js";
|
|
15
|
+
import { lowerForOfMap, lowerForOfSearchParams, lowerForOfSet, objectIterOverIndexShape, strCharsCall } from "./lower-containers.js";
|
|
16
|
+
import { bindingContextualGenericFnNodeOf, bindingGenericFnAliasInfoOf, bindingGenericFnInfoOf, bindingGenericFnNodeOf, implicitLocalFnInfoOf, implicitLocalFnNodeOf, recordKeysArrayCall } from "./lower-calls.js";
|
|
17
17
|
import { isMixinFnBinding, mixinResultBindingClassOf } from "./lower-mixins.js";
|
|
18
|
+
import { genericIfaceBindingKeepsClass } from "./lower-classes.js";
|
|
18
19
|
import { lowerStreamUnderscoreAssign, streamClassAliasDecl, streamSidesOf } from "./lower-stream.js";
|
|
19
20
|
import { lowerHttpResPropertyAssignment, lowerServerCloseOverrideAssignment } from "./lower-server.js";
|
|
20
21
|
import { builtinMemberRequireDecl, builtinNamespaceDestructureModuleOf } from "./lower-builtins.js";
|
|
@@ -1001,6 +1002,19 @@ export function lowerDestructuringDecl(L, decl, isLet) {
|
|
|
1001
1002
|
// tsc already rejects this (TS1182); defensive.
|
|
1002
1003
|
L.unsupported("SC1031", decl);
|
|
1003
1004
|
}
|
|
1005
|
+
// A STDLIB-GLOBAL source in a JavaScript file (`const { subtle } =
|
|
1006
|
+
// globalThis.crypto`, `const { Console } = console` — the suite's
|
|
1007
|
+
// webcrypto/console prologues): each element binds a member IDENTITY
|
|
1008
|
+
// TOKEN, the identifier chokepoint's rule one member deep — see
|
|
1009
|
+
// stdlibGlobalTokenDestructure. Checked before the initializer
|
|
1010
|
+
// lowers: the whole-global spelling would answer the global's own
|
|
1011
|
+
// token (a string), and the pattern would meet the string fences
|
|
1012
|
+
// below blaming the token's carrier type instead of the global.
|
|
1013
|
+
{
|
|
1014
|
+
const tokenBound = stdlibGlobalTokenDestructure(L, decl, isLet);
|
|
1015
|
+
if (tokenBound !== null)
|
|
1016
|
+
return tokenBound;
|
|
1017
|
+
}
|
|
1004
1018
|
let init = L.lowerExpr(decl.initializer);
|
|
1005
1019
|
// A TS `any`-origin source whose lowered value is NOT a destructurable
|
|
1006
1020
|
// shape (`var { x } = <any>0` — the cast erases to the f64; a dyn
|
|
@@ -1015,6 +1029,20 @@ export function lowerDestructuringDecl(L, decl, isLet) {
|
|
|
1015
1029
|
init.type.kind !== "record" && init.type.kind !== "array" && init.type.kind !== "jsval") {
|
|
1016
1030
|
L.anyOpFence("destructuring", decl);
|
|
1017
1031
|
}
|
|
1032
|
+
// An IR-string source the CHECKER does not type as a string is a
|
|
1033
|
+
// builtin identity token that leaked through a JS local (`const c =
|
|
1034
|
+
// globalThis.crypto; const { x } = c` — the local adopted the token's
|
|
1035
|
+
// carrier type): the value is a token, not a string — the string
|
|
1036
|
+
// lowerings below would read the CARRIER (its length, its code
|
|
1037
|
+
// points), numbers and characters Node never sees. The fence names
|
|
1038
|
+
// the checker's own type instead of the carrier.
|
|
1039
|
+
if (init.type.kind === "string" &&
|
|
1040
|
+
isJsSourceFile(decl.getSourceFile()) &&
|
|
1041
|
+
!checkerStringSource(L, decl.initializer)) {
|
|
1042
|
+
L.unsupported("SC1031", decl.name, ts.isArrayBindingPattern(decl.name)
|
|
1043
|
+
? `array destructuring of non-array values (the source is '${L.checker.typeToString(L.typeOf(decl.initializer))}'-typed)`
|
|
1044
|
+
: `object destructuring of non-record values (the source is '${L.checker.typeToString(L.typeOf(decl.initializer))}'-typed)`);
|
|
1045
|
+
}
|
|
1018
1046
|
// PRIMITIVE and unit sources (`let { toString } = 1`, `const [c] =
|
|
1019
1047
|
// "xy"`, `var [] = null`): JS destructures any value by reading
|
|
1020
1048
|
// through its wrapper object — prototype members included — or throws
|
|
@@ -1022,9 +1050,18 @@ export function lowerDestructuringDecl(L, decl, isLet) {
|
|
|
1022
1050
|
// pattern has an engine form, --dynamic marshals the value in and
|
|
1023
1051
|
// runs the REAL pattern (lowerJsvalBindingPattern); a static build
|
|
1024
1052
|
// reports the dynamic-family choice instead of the dead-end type
|
|
1025
|
-
// recitation.
|
|
1053
|
+
// recitation. STRING sources whose pattern the static path claims
|
|
1054
|
+
// whole (staticStringPattern — code-point positions, `length`
|
|
1055
|
+
// bindings) skip the gate in STATIC builds: lowerBindingPattern's
|
|
1056
|
+
// string branches are exact there, and the diagnostic would report a
|
|
1057
|
+
// dynamic-engine dependency the program does not have. --dynamic
|
|
1058
|
+
// keeps the engine route for every string pattern — the engine binds
|
|
1059
|
+
// undefined past the last code point where the static chars array
|
|
1060
|
+
// inherits the array divergence's trap.
|
|
1026
1061
|
if ((init.type.kind === "f64" || init.type.kind === "bool" || init.type.kind === "string" || isUnitType(init.type)) &&
|
|
1027
1062
|
!isJsSourceFile(decl.getSourceFile()) &&
|
|
1063
|
+
!(!L.dynamic && init.type.kind === "string" &&
|
|
1064
|
+
staticStringPattern(L, decl.name)) &&
|
|
1028
1065
|
enginePatternSpec(L, decl.name) !== null) {
|
|
1029
1066
|
if (!L.dynamic) {
|
|
1030
1067
|
L.pushDiag(requiresDynamicDiag(`destructuring '${L.fmt(init.type)}' values (the engine reads through JS's wrapper coercion)`, locOf(decl)));
|
|
@@ -1172,6 +1209,28 @@ export function lowerBindingPattern(L, pattern, srcRef, srcType, isLet, out) {
|
|
|
1172
1209
|
return;
|
|
1173
1210
|
}
|
|
1174
1211
|
}
|
|
1212
|
+
// STRING sources (`const [a, b] = str`): array destructuring walks
|
|
1213
|
+
// the STRING ITERATOR — code points, astral characters whole —
|
|
1214
|
+
// which is Array.from(string)'s own machinery (%str.chars), so the
|
|
1215
|
+
// source splits once into a hidden chars array and the pattern
|
|
1216
|
+
// lowers as an array pattern over string[]. Positions, holes,
|
|
1217
|
+
// defaults (with the bounds test), rest (the remaining code points
|
|
1218
|
+
// pack fresh — JS's surplus packing builds a new array too), and
|
|
1219
|
+
// nested patterns all ride the array machinery below — which also
|
|
1220
|
+
// means a position past the LAST code point inherits the array
|
|
1221
|
+
// divergence (the read traps where JS binds undefined; a DEFAULTED
|
|
1222
|
+
// position carries its bounds test and fires exactly there).
|
|
1223
|
+
// Callers vetted the source as a checker-typed string: builtin
|
|
1224
|
+
// identity tokens (IR strings the checker types otherwise) fence
|
|
1225
|
+
// before reaching here.
|
|
1226
|
+
if (srcType.kind === "string") {
|
|
1227
|
+
const loc = locOf(pattern);
|
|
1228
|
+
const charsT = arrayOf(STRING);
|
|
1229
|
+
const chars = L.declareHiddenLocal("%dchars", charsT);
|
|
1230
|
+
out.push({ kind: "varDecl", localId: chars.id, init: strCharsCall(L, srcRef(), loc), loc });
|
|
1231
|
+
L.lowerBindingPattern(pattern, () => ({ kind: "varRef", localId: chars.id, type: charsT, loc }), charsT, isLet, out);
|
|
1232
|
+
return;
|
|
1233
|
+
}
|
|
1175
1234
|
if (srcType.kind !== "array") {
|
|
1176
1235
|
L.unsupported("SC1031", pattern, `array destructuring of non-array values (the source is ${srcType.kind}-typed)`);
|
|
1177
1236
|
}
|
|
@@ -1295,7 +1354,25 @@ export function lowerBindingPattern(L, pattern, srcRef, srcType, isLet, out) {
|
|
|
1295
1354
|
continue;
|
|
1296
1355
|
const loc = locOf(el);
|
|
1297
1356
|
if (el.dotDotDotToken) {
|
|
1298
|
-
|
|
1357
|
+
// `{ a, ...rest }` over a class instance: the remaining
|
|
1358
|
+
// instance FIELDS pack fresh (classInstanceRestValue — JS's
|
|
1359
|
+
// CopyDataProperties copies own enumerable properties, which
|
|
1360
|
+
// for a class instance are exactly the fields).
|
|
1361
|
+
const consumed = new Set();
|
|
1362
|
+
for (const sib of pattern.elements) {
|
|
1363
|
+
if (sib === el || sib.name === undefined || sib.dotDotDotToken)
|
|
1364
|
+
continue;
|
|
1365
|
+
const keyNode = sib.propertyName ?? (ts.isIdentifier(sib.name) ? sib.name : null);
|
|
1366
|
+
if (keyNode === null)
|
|
1367
|
+
continue; // defensive: a pattern target always carries a propertyName
|
|
1368
|
+
const folded = patternKeyNameOf(L, keyNode);
|
|
1369
|
+
if (folded === null) {
|
|
1370
|
+
L.unsupported("SC1031", el, "rest bindings beside computed keys (the consumed set is a runtime fact)");
|
|
1371
|
+
}
|
|
1372
|
+
consumed.add(folded);
|
|
1373
|
+
}
|
|
1374
|
+
L.bindPatternTarget(el.name, classInstanceRestValue(L, el, srcRef, srcType, info, consumed, patternBindingType(L, el.name)), isLet, out);
|
|
1375
|
+
continue;
|
|
1299
1376
|
}
|
|
1300
1377
|
const prop = el.propertyName ?? el.name;
|
|
1301
1378
|
const propName = ts.isIdentifier(prop) || ts.isComputedPropertyName(prop) || ts.isStringLiteralLike(prop) || ts.isNumericLiteral(prop)
|
|
@@ -1319,6 +1396,14 @@ export function lowerBindingPattern(L, pattern, srcRef, srcType, isLet, out) {
|
|
|
1319
1396
|
field: propName,
|
|
1320
1397
|
fieldType: getF ? getF.sig.ret : setF.sig.params[0].type,
|
|
1321
1398
|
}, loc, el);
|
|
1399
|
+
// A defaulted GETTER result lands in a hidden temp first: the
|
|
1400
|
+
// default's ternary mentions its operand twice (test + present
|
|
1401
|
+
// arm), and JS calls the getter once per element.
|
|
1402
|
+
if (el.initializer) {
|
|
1403
|
+
const got = L.declareHiddenLocal("%dget", value.type);
|
|
1404
|
+
out.push({ kind: "varDecl", localId: got.id, init: value, loc });
|
|
1405
|
+
value = { kind: "varRef", localId: got.id, type: value.type, loc };
|
|
1406
|
+
}
|
|
1322
1407
|
}
|
|
1323
1408
|
else {
|
|
1324
1409
|
L.unsupported("SC1031", el, L.findMethodOn(info, propName)
|
|
@@ -1332,6 +1417,44 @@ export function lowerBindingPattern(L, pattern, srcRef, srcType, isLet, out) {
|
|
|
1332
1417
|
return;
|
|
1333
1418
|
}
|
|
1334
1419
|
}
|
|
1420
|
+
// A STRING source under an OBJECT pattern (`const { length } = str`):
|
|
1421
|
+
// JS reads through the wrapper object, whose one own DATA property is
|
|
1422
|
+
// `length` — the strLen intrinsic, exact, renames and (dead) defaults
|
|
1423
|
+
// included (length always exists and is never undefined, so a default
|
|
1424
|
+
// never evaluates — JS's rule). Everything else on the wrapper is a
|
|
1425
|
+
// prototype METHOD (detached, it loses its receiver) or absent (the
|
|
1426
|
+
// read is undefined, which the binding's type cannot hold) — named
|
|
1427
|
+
// fences either way; rest packs the wrapper's own INDICES (JS's
|
|
1428
|
+
// CopyDataProperties copies one property per code UNIT) and fences
|
|
1429
|
+
// naming that. Callers vetted the source as a checker-typed string —
|
|
1430
|
+
// builtin identity tokens fence before reaching here.
|
|
1431
|
+
if (srcType.kind === "string") {
|
|
1432
|
+
for (const el of pattern.elements) {
|
|
1433
|
+
if (el.name === undefined)
|
|
1434
|
+
continue;
|
|
1435
|
+
const loc = locOf(el);
|
|
1436
|
+
if (el.dotDotDotToken) {
|
|
1437
|
+
L.unsupported("SC1031", el, "rest bindings over string sources (JS packs the wrapper's per-code-unit indices — split with [...s] instead)");
|
|
1438
|
+
}
|
|
1439
|
+
const prop = el.propertyName ?? el.name;
|
|
1440
|
+
const propName = ts.isIdentifier(prop) || ts.isComputedPropertyName(prop) || ts.isStringLiteralLike(prop) || ts.isNumericLiteral(prop)
|
|
1441
|
+
? patternKeyNameOf(L, prop)
|
|
1442
|
+
: null;
|
|
1443
|
+
if (propName === null) {
|
|
1444
|
+
L.unsupported("SC1031", el, "destructuring with computed keys that do not fold to one property name");
|
|
1445
|
+
}
|
|
1446
|
+
if (propName !== "length") {
|
|
1447
|
+
L.unsupported("SC1031", el, Object.hasOwn(STR_METHODS, propName)
|
|
1448
|
+
? `destructuring the method '${propName}' of a string (a detached method loses its receiver — call it through the value)`
|
|
1449
|
+
: `destructuring the property '${propName}' strings do not carry as data ('length' is the one own data property)`);
|
|
1450
|
+
}
|
|
1451
|
+
let value = { kind: "strIntrinsic", method: "length", receiver: srcRef(), args: [], type: F64, loc };
|
|
1452
|
+
if (el.initializer)
|
|
1453
|
+
value = applyBindingDefault(L, el, value);
|
|
1454
|
+
L.bindPatternTarget(el.name, value, isLet, out);
|
|
1455
|
+
}
|
|
1456
|
+
return;
|
|
1457
|
+
}
|
|
1335
1458
|
if (srcType.kind !== "record") {
|
|
1336
1459
|
L.unsupported("SC1031", pattern, srcType.kind === "object"
|
|
1337
1460
|
? "object destructuring of class instances (read the fields directly — accessors make the desugar observable)"
|
|
@@ -1372,12 +1495,20 @@ export function lowerBindingPattern(L, pattern, srcRef, srcType, isLet, out) {
|
|
|
1372
1495
|
if (getSlotT?.kind !== "func") {
|
|
1373
1496
|
L.unsupported("SC1031", el, `destructuring the setter-only property '${propName}' (Node would bind undefined)`);
|
|
1374
1497
|
}
|
|
1375
|
-
if (el.initializer) {
|
|
1376
|
-
L.unsupported("SC1031", el, "defaults on destructured get/set accessor properties");
|
|
1377
|
-
}
|
|
1378
1498
|
const loc = locOf(el);
|
|
1379
1499
|
const closure = { kind: "recordGet", obj: srcRef(), shapeId: srcType.shapeId, field: `%get:${propName}`, type: getSlotT, loc };
|
|
1380
|
-
|
|
1500
|
+
// The getter call IS the element's read; a default applies to its
|
|
1501
|
+
// RESULT exactly like a data field's (undefined-arm test, lazy).
|
|
1502
|
+
// The result lands in a hidden temp first: the default's ternary
|
|
1503
|
+
// mentions its operand twice (test + present arm), and the getter
|
|
1504
|
+
// must run ONCE (JS calls it once per element).
|
|
1505
|
+
let accessorValue = { kind: "callValue", callee: closure, args: [], type: getSlotT.ret, loc };
|
|
1506
|
+
if (el.initializer) {
|
|
1507
|
+
const got = L.declareHiddenLocal("%dget", accessorValue.type);
|
|
1508
|
+
out.push({ kind: "varDecl", localId: got.id, init: accessorValue, loc });
|
|
1509
|
+
accessorValue = applyBindingDefault(L, el, { kind: "varRef", localId: got.id, type: accessorValue.type, loc });
|
|
1510
|
+
}
|
|
1511
|
+
L.bindPatternTarget(el.name, accessorValue, isLet, out);
|
|
1381
1512
|
continue;
|
|
1382
1513
|
}
|
|
1383
1514
|
const fieldType = shape.fields.find((f) => f.name === propName)?.type;
|
|
@@ -1440,11 +1571,199 @@ export function patternKeyNameOf(L, prop) {
|
|
|
1440
1571
|
return prop.text;
|
|
1441
1572
|
if (ts.isComputedPropertyName(prop))
|
|
1442
1573
|
return L.foldedStringKeyOf(prop.expression);
|
|
1443
|
-
|
|
1574
|
+
// A numeric-literal key spells JS's canonical ToPropertyKey form
|
|
1575
|
+
// directly (`{ 2: x }` names the field "2") — a pattern position is
|
|
1576
|
+
// not an expression, so the checker-type fold below does not apply.
|
|
1577
|
+
if (ts.isNumericLiteral(prop))
|
|
1578
|
+
return String(Number(prop.text));
|
|
1579
|
+
if (ts.isStringLiteral(prop) || ts.isNoSubstitutionTemplateLiteral(prop)) {
|
|
1444
1580
|
return L.foldedStringKeyOf(prop);
|
|
1445
1581
|
}
|
|
1446
1582
|
return null;
|
|
1447
1583
|
}
|
|
1584
|
+
/** True when the CHECKER types this expression as a string in every arm
|
|
1585
|
+
* (unions of string-likes included). The IR carries builtin IDENTITY
|
|
1586
|
+
* TOKENS as strings too (`globalThis.crypto` taken as a value in a JS
|
|
1587
|
+
* file, or a local that adopted such a value's carrier type), and those
|
|
1588
|
+
* must never reach the string-source destructuring lowerings — the
|
|
1589
|
+
* callers fence with the checker's own type name instead. */
|
|
1590
|
+
function checkerStringSource(L, e) {
|
|
1591
|
+
const stringLike = (t) => {
|
|
1592
|
+
if ((t.flags & ts.TypeFlags.StringLike) !== 0)
|
|
1593
|
+
return true;
|
|
1594
|
+
if (t.isUnionType())
|
|
1595
|
+
return t.getTypes().every(stringLike);
|
|
1596
|
+
return false;
|
|
1597
|
+
};
|
|
1598
|
+
return stringLike(L.typeOf(e));
|
|
1599
|
+
}
|
|
1600
|
+
/** True when a pattern over a STRING source lowers statically whole —
|
|
1601
|
+
* every element a form the chars-array/wrapper-read desugars carry.
|
|
1602
|
+
* Array patterns take positions, holes, defaults, rest (a nested rest
|
|
1603
|
+
* pattern must be an ARRAY pattern — it destructures the packed
|
|
1604
|
+
* string[]), and nested patterns, whose elements are single code
|
|
1605
|
+
* points and recurse through this same test. Object patterns take
|
|
1606
|
+
* `length` bindings — shorthand, renamed, or (dead-)defaulted.
|
|
1607
|
+
* Anything else — wrapper methods, computed keys, rest over the
|
|
1608
|
+
* wrapper — keeps the engine gate (--dynamic runs the real pattern;
|
|
1609
|
+
* a static build reports the dynamic-family choice). */
|
|
1610
|
+
function staticStringPattern(L, pattern) {
|
|
1611
|
+
if (ts.isArrayBindingPattern(pattern)) {
|
|
1612
|
+
return pattern.elements.every((el) => {
|
|
1613
|
+
if (ts.isOmittedExpression(el) || el.name === undefined)
|
|
1614
|
+
return true;
|
|
1615
|
+
if (el.dotDotDotToken) {
|
|
1616
|
+
// The rest binds string[]: identifier targets and nested ARRAY
|
|
1617
|
+
// patterns ride the array machinery; an object pattern over the
|
|
1618
|
+
// packed array keeps its fence.
|
|
1619
|
+
return ts.isIdentifier(el.name) || ts.isArrayBindingPattern(el.name);
|
|
1620
|
+
}
|
|
1621
|
+
if (ts.isIdentifier(el.name))
|
|
1622
|
+
return true;
|
|
1623
|
+
return staticStringPattern(L, el.name);
|
|
1624
|
+
});
|
|
1625
|
+
}
|
|
1626
|
+
return pattern.elements.every((el) => {
|
|
1627
|
+
if (el.name === undefined)
|
|
1628
|
+
return true;
|
|
1629
|
+
if (el.dotDotDotToken || !ts.isIdentifier(el.name))
|
|
1630
|
+
return false;
|
|
1631
|
+
const prop = el.propertyName ?? el.name;
|
|
1632
|
+
const propName = ts.isIdentifier(prop) || ts.isComputedPropertyName(prop) || ts.isStringLiteralLike(prop) || ts.isNumericLiteral(prop)
|
|
1633
|
+
? patternKeyNameOf(L, prop)
|
|
1634
|
+
: null;
|
|
1635
|
+
return propName === "length";
|
|
1636
|
+
});
|
|
1637
|
+
}
|
|
1638
|
+
/** The stdlib globals whose members destructure to MEMBER identity
|
|
1639
|
+
* tokens: the OPAQUE globals — no member of theirs has a lowered
|
|
1640
|
+
* surface, so a token binding trades the eager pattern fence for the
|
|
1641
|
+
* per-site token rules and loses nothing. Globals with REAL member
|
|
1642
|
+
* surfaces (Math.max, JSON.stringify, process.env, console.log) stay
|
|
1643
|
+
* OUT: their detached members would ride the token's quiet
|
|
1644
|
+
* own-property-undefined reads where the property spelling works, so
|
|
1645
|
+
* those patterns keep an eager fence naming the global. `console` is
|
|
1646
|
+
* the one split surface: its five CALL members fence by name, while
|
|
1647
|
+
* the rest (`Console` — the suite's constructor-identity probe) have
|
|
1648
|
+
* no surface to lose and bind tokens. */
|
|
1649
|
+
const TOKEN_OPAQUE_GLOBALS = new Set(["crypto"]);
|
|
1650
|
+
const CONSOLE_CALL_MEMBERS = new Set(["log", "info", "debug", "error", "warn"]);
|
|
1651
|
+
/** `const { subtle } = globalThis.crypto`, `const { Console } = console`,
|
|
1652
|
+
* `const { crypto } = globalThis` — an object pattern over a STDLIB
|
|
1653
|
+
* GLOBAL in a JavaScript file (the suite's webcrypto/console
|
|
1654
|
+
* prologues): the identifier chokepoint's identity-token rule, one
|
|
1655
|
+
* member deep. The global taken as a value is an opaque token, and a
|
|
1656
|
+
* member taken off an OPAQUE global is the same kind of value — one
|
|
1657
|
+
* global, one member, one interned string — so plain elements bind
|
|
1658
|
+
* MEMBER tokens (`[builtin crypto.subtle]`), and identity flows agree
|
|
1659
|
+
* across destructures of the same member. A member of GLOBALTHIS that
|
|
1660
|
+
* is itself a canonical global (`const { crypto } = globalThis` —
|
|
1661
|
+
* `declare var` globals are properties of `typeof globalThis`,
|
|
1662
|
+
* stdlib-provenance-checked so user script globals stay out) binds the
|
|
1663
|
+
* global's OWN token, byte-equal to the bare and property spellings'
|
|
1664
|
+
* answers, AND registers in stdlibGlobalAliases — the destructured
|
|
1665
|
+
* twin of stdlibGlobalAliasDecl — so receiver-position uses resolve
|
|
1666
|
+
* through every surface exactly like the bare global (`const { Math }
|
|
1667
|
+
* = globalThis; Math.max(...)` is Math.max). What a token cannot do
|
|
1668
|
+
* meets the per-site rules lazily at each USE — member reads answer
|
|
1669
|
+
* the wrapper's own-property undefined, calls throw Node-shaped
|
|
1670
|
+
* is-not-a-function TypeErrors — at the use's own line, where the
|
|
1671
|
+
* pattern-level fence killed the program at the destructure.
|
|
1672
|
+
*
|
|
1673
|
+
* Everything this rule does NOT claim over a stdlib-global source
|
|
1674
|
+
* fences HERE, naming the global — never the token's carrier string
|
|
1675
|
+
* type: rest (the member set is not statically enumerable), defaults
|
|
1676
|
+
* (member presence is not statically testable), nested patterns,
|
|
1677
|
+
* computed keys, members of the SURFACED globals (detached members
|
|
1678
|
+
* lose their receiver-keyed lowerings), and `var`/TDZ-predeclared/
|
|
1679
|
+
* pre-registered bindings whose slots no token can inhabit. Null only
|
|
1680
|
+
* when the source is not a stdlib global at all, or in TypeScript
|
|
1681
|
+
* files (the identifier chokepoint fences the global there first). */
|
|
1682
|
+
function stdlibGlobalTokenDestructure(L, decl, isLet) {
|
|
1683
|
+
if (decl.initializer === undefined || !isJsSourceFile(decl.getSourceFile()))
|
|
1684
|
+
return null;
|
|
1685
|
+
const globalName = stdlibGlobalNameOf(L, decl.initializer);
|
|
1686
|
+
if (globalName === null)
|
|
1687
|
+
return null;
|
|
1688
|
+
if (!ts.isObjectBindingPattern(decl.name)) {
|
|
1689
|
+
L.unsupported("SC1031", decl.name, `array destructuring of the builtin global '${globalName}' (builtin globals are not iterable)`);
|
|
1690
|
+
}
|
|
1691
|
+
if (isVarDeclared(decl)) {
|
|
1692
|
+
L.unsupported("SC1031", decl.name, `var-declared patterns over the builtin global '${globalName}'`);
|
|
1693
|
+
}
|
|
1694
|
+
const srcT = L.typeOf(decl.initializer);
|
|
1695
|
+
const binds = [];
|
|
1696
|
+
for (const el of decl.name.elements) {
|
|
1697
|
+
if (el.name === undefined)
|
|
1698
|
+
continue;
|
|
1699
|
+
if (el.dotDotDotToken) {
|
|
1700
|
+
L.unsupported("SC1031", el, `rest bindings over the builtin global '${globalName}' (the member set is not statically enumerable)`);
|
|
1701
|
+
}
|
|
1702
|
+
if (el.initializer !== undefined) {
|
|
1703
|
+
L.unsupported("SC1031", el, `binding defaults over the builtin global '${globalName}' (member presence is not statically testable)`);
|
|
1704
|
+
}
|
|
1705
|
+
if (!ts.isIdentifier(el.name)) {
|
|
1706
|
+
L.unsupported("SC1031", el, `nested patterns over the builtin global '${globalName}'`);
|
|
1707
|
+
}
|
|
1708
|
+
const prop = el.propertyName ?? el.name;
|
|
1709
|
+
const propName = ts.isIdentifier(prop) || ts.isComputedPropertyName(prop) || ts.isStringLiteralLike(prop) || ts.isNumericLiteral(prop)
|
|
1710
|
+
? patternKeyNameOf(L, prop)
|
|
1711
|
+
: null;
|
|
1712
|
+
if (propName === null) {
|
|
1713
|
+
L.unsupported("SC1031", el, "destructuring with computed keys that do not fold to one property name");
|
|
1714
|
+
}
|
|
1715
|
+
let token;
|
|
1716
|
+
let alias = null;
|
|
1717
|
+
if (globalName === "globalThis") {
|
|
1718
|
+
// The member must BE a stdlib global (user script vars are
|
|
1719
|
+
// properties of `typeof globalThis` too — their values are real
|
|
1720
|
+
// and this rule has none to give).
|
|
1721
|
+
const member = L.checker.getPropertyOfType(srcT, propName);
|
|
1722
|
+
if (member === undefined || !L.isStdlibSymbol(member)) {
|
|
1723
|
+
L.unsupported("SC1031", el, `destructuring the non-builtin member '${propName}' of 'globalThis'`);
|
|
1724
|
+
}
|
|
1725
|
+
const canonical = member.name === "global" ? "globalThis" : member.name;
|
|
1726
|
+
token = `[builtin ${canonical}]`;
|
|
1727
|
+
alias = canonical;
|
|
1728
|
+
}
|
|
1729
|
+
else if (globalName === "console" && !CONSOLE_CALL_MEMBERS.has(propName)) {
|
|
1730
|
+
token = `[builtin console.${propName}]`;
|
|
1731
|
+
}
|
|
1732
|
+
else if (TOKEN_OPAQUE_GLOBALS.has(globalName)) {
|
|
1733
|
+
token = `[builtin ${globalName}.${propName}]`;
|
|
1734
|
+
}
|
|
1735
|
+
else {
|
|
1736
|
+
L.unsupported("SC1031", el, `destructuring the member '${propName}' of the builtin global '${globalName}' (a detached member loses its receiver-keyed lowering — call it through the global)`);
|
|
1737
|
+
}
|
|
1738
|
+
const symbol = L.checker.getSymbolAtLocation(el.name);
|
|
1739
|
+
if (!symbol || L.tdzPredeclared.has(symbol)) {
|
|
1740
|
+
L.unsupported("SC1031", el, `bindings with predeclared slots over the builtin global '${globalName}'`);
|
|
1741
|
+
}
|
|
1742
|
+
const g = L.globalsBySymbol.get(symbol);
|
|
1743
|
+
if (g !== undefined && g.type.kind !== "string" && g.type.kind !== "dyn") {
|
|
1744
|
+
L.unsupported("SC1031", el, `bindings with '${L.fmt(g.type)}' storage over the builtin global '${globalName}'`);
|
|
1745
|
+
}
|
|
1746
|
+
binds.push({ name: el.name, token, alias, g });
|
|
1747
|
+
}
|
|
1748
|
+
const out = [];
|
|
1749
|
+
for (const b of binds) {
|
|
1750
|
+
const loc = locOf(b.name);
|
|
1751
|
+
const token = { kind: "strLit", value: b.token, type: STRING, loc };
|
|
1752
|
+
if (b.alias !== null) {
|
|
1753
|
+
const symbol = L.checker.getSymbolAtLocation(b.name);
|
|
1754
|
+
if (symbol)
|
|
1755
|
+
L.stdlibGlobalAliases.set(symbol, b.alias);
|
|
1756
|
+
}
|
|
1757
|
+
if (b.g !== undefined) {
|
|
1758
|
+
out.push({ kind: "assign", localId: b.g.id, value: L.coerceInto(b.name, token, b.g.type), loc });
|
|
1759
|
+
}
|
|
1760
|
+
else {
|
|
1761
|
+
const local = L.declareLocal(b.name, b.name.text, STRING, isLet);
|
|
1762
|
+
out.push({ kind: "varDecl", localId: local.id, init: token, loc });
|
|
1763
|
+
}
|
|
1764
|
+
}
|
|
1765
|
+
return out;
|
|
1766
|
+
}
|
|
1448
1767
|
/** `x = dflt` on a pattern position whose source value always EXISTS
|
|
1449
1768
|
* (tuple positions, record fields): JS evaluates the default exactly
|
|
1450
1769
|
* when the value is undefined — the undefined-arm ternary, lazy, so
|
|
@@ -1681,6 +2000,63 @@ function objectRestValue(L, el, srcRef, srcType, shape) {
|
|
|
1681
2000
|
});
|
|
1682
2001
|
return { kind: "recordLit", fields, type: restT, loc };
|
|
1683
2002
|
}
|
|
2003
|
+
/** The fresh record for `{ a, ...rest }` over a CLASS-INSTANCE source:
|
|
2004
|
+
* JS's CopyDataProperties copies the OWN ENUMERABLE properties — the
|
|
2005
|
+
* instance FIELDS, base chain included, in property-creation order
|
|
2006
|
+
* (base constructors assign theirs first) — and never the prototype
|
|
2007
|
+
* members (methods and accessors stay behind; the checker's rest type
|
|
2008
|
+
* excludes them too, so the two sides agree). The packed record must
|
|
2009
|
+
* agree with the checker's rest type field-for-field — a non-public
|
|
2010
|
+
* field IS copied by JS but the rest type cannot name it (silent
|
|
2011
|
+
* divergence; fence) — and key-for-key in ORDER: the packed shape's
|
|
2012
|
+
* JSON/Object.keys order is first-seen global metadata, and a checker
|
|
2013
|
+
* rest type that reorders (own-before-inherited) would make the
|
|
2014
|
+
* enumeration surfaces diverge from Node, so it fences instead of
|
|
2015
|
+
* silently reordering. Runtime-provided chains (Error/EventEmitter/
|
|
2016
|
+
* stream roots) carry runtime-internal state no record copy can
|
|
2017
|
+
* reproduce — fenced. ES-#private fields are own but non-enumerable:
|
|
2018
|
+
* JS skips them and so does the packing. */
|
|
2019
|
+
function classInstanceRestValue(L, blame, srcRef, srcType, info, consumed, restT) {
|
|
2020
|
+
const loc = locOf(blame);
|
|
2021
|
+
for (let c = info; c; c = c.base) {
|
|
2022
|
+
if (c.builtinError || c.builtinEmitter || c.builtinStream) {
|
|
2023
|
+
L.unsupported("SC1031", blame, "rest bindings over runtime-provided class instances (Error/EventEmitter/stream internals have no record form)");
|
|
2024
|
+
}
|
|
2025
|
+
}
|
|
2026
|
+
if (restT?.kind !== "record") {
|
|
2027
|
+
L.unsupported("SC1031", blame, restT
|
|
2028
|
+
? `rest bindings packing the remaining properties as '${L.fmt(restT)}' (only plain record types pack)`
|
|
2029
|
+
: "rest bindings whose packed type has no static mapping (only plain record types pack)");
|
|
2030
|
+
}
|
|
2031
|
+
const restShape = L.shapes.get(restT.shapeId);
|
|
2032
|
+
if (!restShape)
|
|
2033
|
+
throw new Error(`lowerer bug: rest binding over unknown shape ${restT.shapeId}`);
|
|
2034
|
+
if (restShape.indexValue || restShape.tuple) {
|
|
2035
|
+
L.unsupported("SC1031", blame, "rest bindings over index-signature shapes (the undeclared entries would need overflow packing)");
|
|
2036
|
+
}
|
|
2037
|
+
const remaining = info.def.fields.filter((f) => !f.name.startsWith("#") && !f.name.startsWith("%") && !consumed.has(f.name));
|
|
2038
|
+
for (const f of remaining) {
|
|
2039
|
+
if (!restShape.fields.some((rf) => rf.name === f.name)) {
|
|
2040
|
+
L.unsupported("SC1031", blame, `rest bindings over instances of '${info.def.jsName ?? info.def.name}' (JS copies the non-public field '${f.name}' into the rest object, which the rest type cannot name)`);
|
|
2041
|
+
}
|
|
2042
|
+
}
|
|
2043
|
+
for (const rf of restShape.fields) {
|
|
2044
|
+
if (!remaining.some((f) => f.name === rf.name)) {
|
|
2045
|
+
L.unsupported("SC1031", blame, `the rest field '${rf.name}' is not an instance field of '${info.def.jsName ?? info.def.name}'`);
|
|
2046
|
+
}
|
|
2047
|
+
}
|
|
2048
|
+
const emitOrder = restShape.declaredOrder ?? restShape.fields.map((f) => f.name);
|
|
2049
|
+
if (emitOrder.length !== remaining.length || emitOrder.some((n, i) => n !== remaining[i].name)) {
|
|
2050
|
+
L.unsupported("SC1031", blame, "rest bindings over class instances whose packed key order cannot match Node's (Object.keys/JSON.stringify would enumerate the copied fields in a different order)");
|
|
2051
|
+
}
|
|
2052
|
+
const fields = remaining.map((f) => {
|
|
2053
|
+
const fieldType = info.fields.get(f.name) ?? f.type;
|
|
2054
|
+
const read = L.fieldGetExpr({ container: "class", obj: srcRef(), className: srcType.className, field: f.name, fieldType }, loc, blame);
|
|
2055
|
+
const restFT = restShape.fields.find((rf) => rf.name === f.name).type;
|
|
2056
|
+
return { name: f.name, value: L.coerceInto(blame, read, restFT) };
|
|
2057
|
+
});
|
|
2058
|
+
return { kind: "recordLit", fields, type: restT, loc };
|
|
2059
|
+
}
|
|
1684
2060
|
/** The fences the island's element-wise object path shares: no rest
|
|
1685
2061
|
* (surplus packing over a handle) and no defaults (an engine undefined
|
|
1686
2062
|
* test) — the static positions now take both through their own
|
|
@@ -2144,12 +2520,19 @@ export function lowerVarDecl(L, decl, isLet) {
|
|
|
2144
2520
|
// shapes (reassigned bindings, declarations inside functions) fence
|
|
2145
2521
|
// by name inside; a collection-time report dedupes.
|
|
2146
2522
|
{
|
|
2147
|
-
const gfnNode = bindingGenericFnNodeOf(decl);
|
|
2523
|
+
const gfnNode = bindingGenericFnNodeOf(decl) ?? bindingContextualGenericFnNodeOf(L, decl);
|
|
2148
2524
|
if (gfnNode) {
|
|
2149
2525
|
bindingGenericFnInfoOf(L, decl, gfnNode);
|
|
2150
2526
|
return null;
|
|
2151
2527
|
}
|
|
2152
2528
|
}
|
|
2529
|
+
// `const h = id` — an ALIAS of a generic function: the alias's symbol
|
|
2530
|
+
// registers the target's info (calls and pinned values resolve like
|
|
2531
|
+
// the target's own name), the binding has no runtime value, and the
|
|
2532
|
+
// statement emits nothing (collectGlobals skipped module-scope
|
|
2533
|
+
// globals by the same test; block-scoped aliases register here).
|
|
2534
|
+
if (bindingGenericFnAliasInfoOf(L, decl))
|
|
2535
|
+
return null;
|
|
2153
2536
|
// `const knownBy = (cmd) => ...` — an IMPLICIT-ANY function-value
|
|
2154
2537
|
// binding in npm-static JS (function-scope bindings register here, in
|
|
2155
2538
|
// source order before any use can lower; file-scope ones registered at
|
|
@@ -2403,6 +2786,20 @@ export function lowerVarDecl(L, decl, isLet) {
|
|
|
2403
2786
|
// bridge is an independent observer of the same settlement).
|
|
2404
2787
|
if (init.type.kind === "jsval" && type.kind === "promise")
|
|
2405
2788
|
type = JSVAL;
|
|
2789
|
+
// A STATIC array of island HANDLES under an unannotated declared type
|
|
2790
|
+
// spelling evolved elements (`const kept = fns.filter(...)` over an
|
|
2791
|
+
// evolving-`any` receiver — the handle-element lowering keeps the
|
|
2792
|
+
// elements jsval, while tsc's evolving-array analysis types the
|
|
2793
|
+
// result by the pushed elements): the value's element type is the
|
|
2794
|
+
// truth, so the binding keeps the handle-element array (the
|
|
2795
|
+
// island-HANDLE local story, one level down) and later method calls
|
|
2796
|
+
// ride the handle-element lowering. Explicit annotations keep the
|
|
2797
|
+
// validated-boundary fence.
|
|
2798
|
+
if (!decl.type &&
|
|
2799
|
+
init.type.kind === "array" && init.type.elem.kind === "jsval" &&
|
|
2800
|
+
type.kind === "array" && type.elem.kind !== "jsval") {
|
|
2801
|
+
type = init.type;
|
|
2802
|
+
}
|
|
2406
2803
|
// `const runner = options.runner || defaultRunner`: the checker types
|
|
2407
2804
|
// the || as the union/merge of two structurally-compatible function
|
|
2408
2805
|
// types — a signature whose RETURN is the arms' union, or the
|
|
@@ -2434,6 +2831,14 @@ export function lowerVarDecl(L, decl, isLet) {
|
|
|
2434
2831
|
type = init.type;
|
|
2435
2832
|
}
|
|
2436
2833
|
}
|
|
2834
|
+
// `const r: Repo = new MemRepo()` over an all-generic-method
|
|
2835
|
+
// interface: the binding keeps the initializer's CLASS representation
|
|
2836
|
+
// (the record shape maps empty and the width copy would drop the
|
|
2837
|
+
// class the generic-method calls monomorphize against) — see
|
|
2838
|
+
// genericIfaceBindingKeepsClass.
|
|
2839
|
+
if (type.kind === "record" && init.type.kind === "object" && genericIfaceBindingKeepsClass(L, decl, type)) {
|
|
2840
|
+
type = init.type;
|
|
2841
|
+
}
|
|
2437
2842
|
// A TDZ box minted DURING this very initializer (a callback inside it
|
|
2438
2843
|
// captured this const — predeclareForwardCapture's current-statement
|
|
2439
2844
|
// case): the binding and its scope-entry varDecl already exist, so
|
|
@@ -2586,6 +2991,7 @@ export function lowerUnionSwitch(L, stmt, disc) {
|
|
|
2586
2991
|
kind: "unionEq",
|
|
2587
2992
|
unionId: unionType.unionId,
|
|
2588
2993
|
negated: false,
|
|
2994
|
+
sameValue: false,
|
|
2589
2995
|
left: disc,
|
|
2590
2996
|
right: L.coerceInto(clause.expression, test, unionType),
|
|
2591
2997
|
type: BOOL,
|
|
@@ -3289,6 +3695,31 @@ export function lowerExprStatement(L, expr) {
|
|
|
3289
3695
|
};
|
|
3290
3696
|
}
|
|
3291
3697
|
}
|
|
3698
|
+
// `events.defaultMaxListeners = v` in STATEMENT position — the
|
|
3699
|
+
// module-property write Node validates (the lowerBinary
|
|
3700
|
+
// expression twin): the value crosses into the DOM, the runtime
|
|
3701
|
+
// ladder throws Node's exact errors, valid numbers apply.
|
|
3702
|
+
if (!expr.left.questionDotToken && expr.left.name.text === "defaultMaxListeners") {
|
|
3703
|
+
const bi = L.builtinMemberOf(expr.left);
|
|
3704
|
+
if (bi && bi.module === "events" && bi.member === "defaultMaxListeners") {
|
|
3705
|
+
const loc = locOf(expr);
|
|
3706
|
+
const rhs = L.lowerExpr(expr.right);
|
|
3707
|
+
if (rhs.type.kind === "dyn" || rhs.kind === "unitLit" || L.dynConvertible(rhs.type)) {
|
|
3708
|
+
const dynVal = rhs.type.kind === "dyn" ? rhs : { kind: "dynFrom", value: rhs, type: DYN, loc };
|
|
3709
|
+
return {
|
|
3710
|
+
kind: "exprStmt",
|
|
3711
|
+
expr: {
|
|
3712
|
+
kind: "libCall",
|
|
3713
|
+
fn: "emitter.setDefaultMaxChk",
|
|
3714
|
+
args: [dynVal, { kind: "strLit", value: "defaultMaxListeners", type: STRING, loc }],
|
|
3715
|
+
type: VOID,
|
|
3716
|
+
loc,
|
|
3717
|
+
},
|
|
3718
|
+
loc,
|
|
3719
|
+
};
|
|
3720
|
+
}
|
|
3721
|
+
}
|
|
3722
|
+
}
|
|
3292
3723
|
// `r._read = fn` / `w._write = fn` (and the other underscore
|
|
3293
3724
|
// methods) on a runtime-stream-rooted receiver: Node's
|
|
3294
3725
|
// own-property shadow of the prototype method — the runtime
|
|
@@ -3811,6 +4242,31 @@ export function lowerDestructuringAssignParts(L, target, rhs, loc) {
|
|
|
3811
4242
|
* source-spelling TypeError machinery cannot apply), `blame` the node
|
|
3812
4243
|
* fences point at. */
|
|
3813
4244
|
function destructuringAssignInto(L, target, init, rhs, blame, loc) {
|
|
4245
|
+
// IR-string sources in JS files split two ways the carrier type
|
|
4246
|
+
// cannot: a STDLIB GLOBAL (`({ subtle } = globalThis.crypto)`) — the
|
|
4247
|
+
// declaration path binds member identity tokens into FRESH bindings,
|
|
4248
|
+
// but assignment targets carry their own checker-typed slots no token
|
|
4249
|
+
// can honestly inhabit — and a LEAKED token (a local that adopted a
|
|
4250
|
+
// global's carrier type), where the string lowerings below would read
|
|
4251
|
+
// the carrier itself. Both keep fences naming the truth; empty
|
|
4252
|
+
// patterns stay pure (RequireObjectCoercible/GetIterator hold for
|
|
4253
|
+
// globals and strings alike).
|
|
4254
|
+
{
|
|
4255
|
+
const patternEmpty = ts.isObjectLiteralExpression(target)
|
|
4256
|
+
? target.properties.length === 0
|
|
4257
|
+
: target.elements.length === 0;
|
|
4258
|
+
if (!patternEmpty && rhs !== null && isJsSourceFile(rhs.getSourceFile())) {
|
|
4259
|
+
const globalName = stdlibGlobalNameOf(L, rhs);
|
|
4260
|
+
if (globalName !== null) {
|
|
4261
|
+
L.unsupported("SC1031", blame, `destructuring assignment from the builtin global '${globalName}' (bind the members with a const destructuring declaration instead)`);
|
|
4262
|
+
}
|
|
4263
|
+
if (init.type.kind === "string" && !checkerStringSource(L, rhs)) {
|
|
4264
|
+
L.unsupported("SC1031", blame, ts.isArrayLiteralExpression(target)
|
|
4265
|
+
? `array destructuring assignment from non-array values (the source is '${L.checker.typeToString(L.typeOf(rhs))}'-typed)`
|
|
4266
|
+
: `destructuring assignment from non-record values (the source is '${L.checker.typeToString(L.typeOf(rhs))}'-typed)`);
|
|
4267
|
+
}
|
|
4268
|
+
}
|
|
4269
|
+
}
|
|
3814
4270
|
const tmp = L.declareHiddenLocal("%destr", init.type);
|
|
3815
4271
|
const out = [{ kind: "varDecl", localId: tmp.id, init, loc }];
|
|
3816
4272
|
const tmpRef = () => ({ kind: "varRef", localId: tmp.id, type: init.type, loc });
|
|
@@ -3891,6 +4347,200 @@ function destructuringAssignInto(L, target, init, rhs, blame, loc) {
|
|
|
3891
4347
|
}
|
|
3892
4348
|
return { stmts: out, value };
|
|
3893
4349
|
}
|
|
4350
|
+
// A CLASS-INSTANCE source (`({ a, b } = inst)`): the declaration
|
|
4351
|
+
// path's desugar in assignment position — one member read per
|
|
4352
|
+
// element, left to right (declared fields read their slots, accessor
|
|
4353
|
+
// properties call their getters through the same fieldGetExpr
|
|
4354
|
+
// dispatch), defaults against the TARGET's own type, rest packing
|
|
4355
|
+
// the remaining instance fields (classInstanceRestValue). Methods
|
|
4356
|
+
// and names no class on the chain declares keep the declaration
|
|
4357
|
+
// path's named fences.
|
|
4358
|
+
if (init.type.kind === "object") {
|
|
4359
|
+
const objT = init.type;
|
|
4360
|
+
if (!L.classes.get(objT.className))
|
|
4361
|
+
L.flushDeferredClass(objT.className);
|
|
4362
|
+
const info = L.classes.get(objT.className);
|
|
4363
|
+
if (info) {
|
|
4364
|
+
for (const prop of target.properties) {
|
|
4365
|
+
const propLoc = locOf(prop);
|
|
4366
|
+
if (ts.isSpreadAssignment(prop)) {
|
|
4367
|
+
let restTo = prop.expression;
|
|
4368
|
+
while (ts.isParenthesizedExpression(restTo))
|
|
4369
|
+
restTo = restTo.expression;
|
|
4370
|
+
const consumed = new Set();
|
|
4371
|
+
for (const p of target.properties) {
|
|
4372
|
+
if (ts.isShorthandPropertyAssignment(p))
|
|
4373
|
+
consumed.add(p.name.text);
|
|
4374
|
+
else if (ts.isPropertyAssignment(p)) {
|
|
4375
|
+
const n = patternKeyNameOf(L, p.name);
|
|
4376
|
+
if (n !== null)
|
|
4377
|
+
consumed.add(n);
|
|
4378
|
+
}
|
|
4379
|
+
}
|
|
4380
|
+
if (!ts.isIdentifier(restTo)) {
|
|
4381
|
+
lowerAssignTargetInto(L, out, restTo, prop, null, (targetT) => classInstanceRestValue(L, prop, tmpRef, objT, info, consumed, targetT));
|
|
4382
|
+
continue;
|
|
4383
|
+
}
|
|
4384
|
+
const targetBinding = L.resolveWritable(restTo);
|
|
4385
|
+
if (!targetBinding) {
|
|
4386
|
+
L.rejectUnresolved(restTo, `assignment to '${restTo.text}' (not a writable local or module global)`);
|
|
4387
|
+
}
|
|
4388
|
+
const packed = classInstanceRestValue(L, prop, tmpRef, objT, info, consumed, targetBinding.type);
|
|
4389
|
+
out.push({ kind: "assign", localId: targetBinding.id, value: L.coerceInto(prop, packed, targetBinding.type), loc: propLoc });
|
|
4390
|
+
continue;
|
|
4391
|
+
}
|
|
4392
|
+
let fieldName;
|
|
4393
|
+
let bindTo;
|
|
4394
|
+
let dfltInit = null;
|
|
4395
|
+
if (ts.isShorthandPropertyAssignment(prop)) {
|
|
4396
|
+
fieldName = prop.name.text;
|
|
4397
|
+
bindTo = prop.name;
|
|
4398
|
+
dfltInit = prop.objectAssignmentInitializer ?? null;
|
|
4399
|
+
}
|
|
4400
|
+
else if (ts.isPropertyAssignment(prop)) {
|
|
4401
|
+
const folded = patternKeyNameOf(L, prop.name);
|
|
4402
|
+
if (folded === null) {
|
|
4403
|
+
L.unsupported("SC1031", prop, "destructuring assignment with computed keys that do not fold to one property name");
|
|
4404
|
+
}
|
|
4405
|
+
fieldName = folded;
|
|
4406
|
+
bindTo = prop.initializer;
|
|
4407
|
+
if (ts.isBinaryExpression(bindTo) && bindTo.operatorToken.kind === ts.SyntaxKind.EqualsToken) {
|
|
4408
|
+
dfltInit = bindTo.right;
|
|
4409
|
+
bindTo = bindTo.left;
|
|
4410
|
+
}
|
|
4411
|
+
}
|
|
4412
|
+
else {
|
|
4413
|
+
L.unsupported("SC1031", prop, "destructuring assignment with getter/setter or method properties");
|
|
4414
|
+
}
|
|
4415
|
+
const dflt = dfltInit;
|
|
4416
|
+
const readOf = (targetT) => {
|
|
4417
|
+
const fieldType = info.fields.get(fieldName);
|
|
4418
|
+
const getF = fieldType === undefined ? L.findMethodOn(info, `get:${fieldName}`) : null;
|
|
4419
|
+
const setF = fieldType === undefined ? L.findMethodOn(info, `set:${fieldName}`) : null;
|
|
4420
|
+
let v;
|
|
4421
|
+
if (fieldType !== undefined) {
|
|
4422
|
+
v = L.fieldGetExpr({ container: "class", obj: tmpRef(), className: objT.className, field: fieldName, fieldType }, propLoc, prop);
|
|
4423
|
+
}
|
|
4424
|
+
else if (getF || setF) {
|
|
4425
|
+
v = L.fieldGetExpr({ container: "accessor", obj: tmpRef(), className: objT.className, field: fieldName, fieldType: getF ? getF.sig.ret : setF.sig.params[0].type }, propLoc, prop);
|
|
4426
|
+
// A defaulted getter result temps first — the default's
|
|
4427
|
+
// ternary mentions its operand twice, and JS calls the
|
|
4428
|
+
// getter once per element.
|
|
4429
|
+
if (dflt && targetT) {
|
|
4430
|
+
const got = L.declareHiddenLocal("%dget", v.type);
|
|
4431
|
+
out.push({ kind: "varDecl", localId: got.id, init: v, loc: propLoc });
|
|
4432
|
+
v = { kind: "varRef", localId: got.id, type: v.type, loc: propLoc };
|
|
4433
|
+
}
|
|
4434
|
+
}
|
|
4435
|
+
else {
|
|
4436
|
+
L.unsupported("SC1031", prop, L.findMethodOn(info, fieldName)
|
|
4437
|
+
? `destructuring the method '${fieldName}' (a detached method loses its receiver — call it through the instance)`
|
|
4438
|
+
: `destructuring the property '${fieldName}' the class '${info.def.jsName ?? info.def.name}' does not declare`);
|
|
4439
|
+
}
|
|
4440
|
+
if (dflt && targetT)
|
|
4441
|
+
v = undefArmDefault(L, prop, dflt, v, targetT);
|
|
4442
|
+
return v;
|
|
4443
|
+
};
|
|
4444
|
+
while (ts.isParenthesizedExpression(bindTo))
|
|
4445
|
+
bindTo = bindTo.expression;
|
|
4446
|
+
if (!ts.isIdentifier(bindTo)) {
|
|
4447
|
+
lowerAssignTargetInto(L, out, bindTo, prop, dfltInit, readOf);
|
|
4448
|
+
continue;
|
|
4449
|
+
}
|
|
4450
|
+
let targetBinding;
|
|
4451
|
+
if (ts.isShorthandPropertyAssignment(prop)) {
|
|
4452
|
+
const valueSymbol = L.checker.getShorthandAssignmentValueSymbol(prop) ?? null;
|
|
4453
|
+
const local = valueSymbol ? L.resolveKey(valueSymbol, bindTo) : null;
|
|
4454
|
+
const g = valueSymbol ? L.globalsBySymbol.get(valueSymbol) : undefined;
|
|
4455
|
+
targetBinding = local ? { id: local.id, type: local.type } : g ? { id: g.id, type: g.type } : null;
|
|
4456
|
+
}
|
|
4457
|
+
else {
|
|
4458
|
+
targetBinding = L.resolveWritable(bindTo);
|
|
4459
|
+
}
|
|
4460
|
+
if (!targetBinding) {
|
|
4461
|
+
L.rejectUnresolved(bindTo, `assignment to '${bindTo.text}' (not a writable local or module global)`);
|
|
4462
|
+
}
|
|
4463
|
+
out.push({
|
|
4464
|
+
kind: "assign",
|
|
4465
|
+
localId: targetBinding.id,
|
|
4466
|
+
value: L.coerceInto(prop, readOf(targetBinding.type), targetBinding.type),
|
|
4467
|
+
loc: propLoc,
|
|
4468
|
+
});
|
|
4469
|
+
}
|
|
4470
|
+
return { stmts: out, value };
|
|
4471
|
+
}
|
|
4472
|
+
}
|
|
4473
|
+
// A STRING source under an object ASSIGNMENT pattern (`({ length: n }
|
|
4474
|
+
// = s)`): the declaration path's wrapper rule in assignment position —
|
|
4475
|
+
// `length` is the wrapper's one own data property (the strLen
|
|
4476
|
+
// intrinsic; a default is dead code — length always exists and is
|
|
4477
|
+
// never undefined, so JS never evaluates it either); identifier,
|
|
4478
|
+
// property, and element targets ride the shared target plumbing.
|
|
4479
|
+
// Methods, unknown names, computed keys, spreads, and accessor
|
|
4480
|
+
// properties keep the declaration path's named fences. Callers vetted
|
|
4481
|
+
// the source as a checker-typed string.
|
|
4482
|
+
if (init.type.kind === "string") {
|
|
4483
|
+
for (const prop of target.properties) {
|
|
4484
|
+
const propLoc = locOf(prop);
|
|
4485
|
+
if (ts.isSpreadAssignment(prop)) {
|
|
4486
|
+
L.unsupported("SC1031", prop, "rest bindings over string sources (JS packs the wrapper's per-code-unit indices — split with [...s] instead)");
|
|
4487
|
+
}
|
|
4488
|
+
let fieldName;
|
|
4489
|
+
let bindTo;
|
|
4490
|
+
if (ts.isShorthandPropertyAssignment(prop)) {
|
|
4491
|
+
fieldName = prop.name.text;
|
|
4492
|
+
bindTo = prop.name;
|
|
4493
|
+
}
|
|
4494
|
+
else if (ts.isPropertyAssignment(prop)) {
|
|
4495
|
+
const folded = patternKeyNameOf(L, prop.name);
|
|
4496
|
+
if (folded === null) {
|
|
4497
|
+
L.unsupported("SC1031", prop, "destructuring assignment with computed keys that do not fold to one property name");
|
|
4498
|
+
}
|
|
4499
|
+
fieldName = folded;
|
|
4500
|
+
bindTo = prop.initializer;
|
|
4501
|
+
// `({ length: n = 3 } = s)`: the default is dead (length always
|
|
4502
|
+
// exists) — strip it, exactly the evaluation JS skips.
|
|
4503
|
+
if (ts.isBinaryExpression(bindTo) && bindTo.operatorToken.kind === ts.SyntaxKind.EqualsToken) {
|
|
4504
|
+
bindTo = bindTo.left;
|
|
4505
|
+
}
|
|
4506
|
+
}
|
|
4507
|
+
else {
|
|
4508
|
+
L.unsupported("SC1031", prop, "destructuring assignment with getter/setter or method properties");
|
|
4509
|
+
}
|
|
4510
|
+
if (fieldName !== "length") {
|
|
4511
|
+
L.unsupported("SC1031", prop, Object.hasOwn(STR_METHODS, fieldName)
|
|
4512
|
+
? `destructuring the method '${fieldName}' of a string (a detached method loses its receiver — call it through the value)`
|
|
4513
|
+
: `destructuring the property '${fieldName}' strings do not carry as data ('length' is the one own data property)`);
|
|
4514
|
+
}
|
|
4515
|
+
const readOf = () => ({ kind: "strIntrinsic", method: "length", receiver: tmpRef(), args: [], type: F64, loc: propLoc });
|
|
4516
|
+
while (ts.isParenthesizedExpression(bindTo))
|
|
4517
|
+
bindTo = bindTo.expression;
|
|
4518
|
+
if (!ts.isIdentifier(bindTo)) {
|
|
4519
|
+
lowerAssignTargetInto(L, out, bindTo, prop, null, readOf);
|
|
4520
|
+
continue;
|
|
4521
|
+
}
|
|
4522
|
+
let targetBinding;
|
|
4523
|
+
if (ts.isShorthandPropertyAssignment(prop)) {
|
|
4524
|
+
const valueSymbol = L.checker.getShorthandAssignmentValueSymbol(prop) ?? null;
|
|
4525
|
+
const local = valueSymbol ? L.resolveKey(valueSymbol, bindTo) : null;
|
|
4526
|
+
const g = valueSymbol ? L.globalsBySymbol.get(valueSymbol) : undefined;
|
|
4527
|
+
targetBinding = local ? { id: local.id, type: local.type } : g ? { id: g.id, type: g.type } : null;
|
|
4528
|
+
}
|
|
4529
|
+
else {
|
|
4530
|
+
targetBinding = L.resolveWritable(bindTo);
|
|
4531
|
+
}
|
|
4532
|
+
if (!targetBinding) {
|
|
4533
|
+
L.rejectUnresolved(bindTo, `assignment to '${bindTo.text}' (not a writable local or module global)`);
|
|
4534
|
+
}
|
|
4535
|
+
out.push({
|
|
4536
|
+
kind: "assign",
|
|
4537
|
+
localId: targetBinding.id,
|
|
4538
|
+
value: L.coerceInto(prop, readOf(), targetBinding.type),
|
|
4539
|
+
loc: propLoc,
|
|
4540
|
+
});
|
|
4541
|
+
}
|
|
4542
|
+
return { stmts: out, value };
|
|
4543
|
+
}
|
|
3894
4544
|
if (init.type.kind !== "record") {
|
|
3895
4545
|
L.unsupported("SC1031", blame, init.type.kind === "object"
|
|
3896
4546
|
? "destructuring assignment from class instances (read the fields directly — accessors make the desugar observable)"
|
|
@@ -3914,13 +4564,6 @@ function destructuringAssignInto(L, target, init, rhs, blame, loc) {
|
|
|
3914
4564
|
let restTo = prop.expression;
|
|
3915
4565
|
while (ts.isParenthesizedExpression(restTo))
|
|
3916
4566
|
restTo = restTo.expression;
|
|
3917
|
-
if (!ts.isIdentifier(restTo)) {
|
|
3918
|
-
L.unsupported("SC1031", prop, "destructuring assignment of rest into non-variable targets");
|
|
3919
|
-
}
|
|
3920
|
-
const targetBinding = L.resolveWritable(restTo);
|
|
3921
|
-
if (!targetBinding) {
|
|
3922
|
-
L.rejectUnresolved(restTo, `assignment to '${restTo.text}' (not a writable local or module global)`);
|
|
3923
|
-
}
|
|
3924
4567
|
if (shape.indexValue) {
|
|
3925
4568
|
L.unsupported("SC1031", prop, "rest bindings over index-signature shapes (the undeclared entries would need overflow packing)");
|
|
3926
4569
|
}
|
|
@@ -3945,6 +4588,16 @@ function destructuringAssignInto(L, target, init, rhs, blame, loc) {
|
|
|
3945
4588
|
type: { kind: "record", shapeId: restShapeId },
|
|
3946
4589
|
loc: locOf(prop),
|
|
3947
4590
|
};
|
|
4591
|
+
if (!ts.isIdentifier(restTo)) {
|
|
4592
|
+
// `({ a, ...box.rest } = src)` — the rest packs identically and
|
|
4593
|
+
// lands through the property/element write machinery.
|
|
4594
|
+
lowerAssignTargetInto(L, out, restTo, prop, null, () => packed);
|
|
4595
|
+
continue;
|
|
4596
|
+
}
|
|
4597
|
+
const targetBinding = L.resolveWritable(restTo);
|
|
4598
|
+
if (!targetBinding) {
|
|
4599
|
+
L.rejectUnresolved(restTo, `assignment to '${restTo.text}' (not a writable local or module global)`);
|
|
4600
|
+
}
|
|
3948
4601
|
out.push({
|
|
3949
4602
|
kind: "assign",
|
|
3950
4603
|
localId: targetBinding.id,
|
|
@@ -3981,7 +4634,33 @@ function destructuringAssignInto(L, target, init, rhs, blame, loc) {
|
|
|
3981
4634
|
while (ts.isParenthesizedExpression(bindTo))
|
|
3982
4635
|
bindTo = bindTo.expression;
|
|
3983
4636
|
if (!ts.isIdentifier(bindTo)) {
|
|
3984
|
-
|
|
4637
|
+
// Nested patterns and property/element targets: the element's
|
|
4638
|
+
// value (default applied at the target's own type) lands through
|
|
4639
|
+
// the shared non-variable machinery.
|
|
4640
|
+
const fieldT = shape.fields.find((f) => f.name === fieldName)?.type;
|
|
4641
|
+
const dflt = dfltInit;
|
|
4642
|
+
lowerAssignTargetInto(L, out, bindTo, prop, dflt, (targetT) => {
|
|
4643
|
+
if (!fieldT) {
|
|
4644
|
+
// The defaulted read of a field the shape does not carry is
|
|
4645
|
+
// always undefined — the default IS the value (the identifier
|
|
4646
|
+
// path's rule below).
|
|
4647
|
+
if (dflt && targetT)
|
|
4648
|
+
return L.lowerExprExpecting(dflt, targetT);
|
|
4649
|
+
L.unsupported("SC1031", prop, `destructuring the field '${fieldName}' the source shape does not carry`);
|
|
4650
|
+
}
|
|
4651
|
+
let v = {
|
|
4652
|
+
kind: "recordGet",
|
|
4653
|
+
obj: { kind: "varRef", localId: tmp.id, type: srcType, loc: locOf(prop) },
|
|
4654
|
+
shapeId: srcType.shapeId,
|
|
4655
|
+
field: fieldName,
|
|
4656
|
+
type: fieldT,
|
|
4657
|
+
loc: locOf(prop),
|
|
4658
|
+
};
|
|
4659
|
+
if (dflt && targetT)
|
|
4660
|
+
v = undefArmDefault(L, prop, dflt, v, targetT);
|
|
4661
|
+
return v;
|
|
4662
|
+
});
|
|
4663
|
+
continue;
|
|
3985
4664
|
}
|
|
3986
4665
|
// Shorthand names resolve to the PROPERTY symbol at their location;
|
|
3987
4666
|
// the VALUE symbol (the binding being assigned) comes from the
|
|
@@ -4037,6 +4716,102 @@ function destructuringAssignInto(L, target, init, rhs, blame, loc) {
|
|
|
4037
4716
|
}
|
|
4038
4717
|
return { stmts: out, value };
|
|
4039
4718
|
}
|
|
4719
|
+
/** A destructuring-ASSIGNMENT target that is not a plain variable:
|
|
4720
|
+
* PROPERTY targets (`({ a: this.x } = o)`, `[c.x, c.y] = t` — the
|
|
4721
|
+
* receiver evaluates into a hidden temp at the element's pattern
|
|
4722
|
+
* position, BEFORE the element's value read, JS's get-the-reference-
|
|
4723
|
+
* then-GetV order), ELEMENT targets over arrays (runtime f64 index)
|
|
4724
|
+
* and tuples/records (literal keys) with the same receiver/key temp
|
|
4725
|
+
* discipline, and NESTED patterns (`({ p: { q } } = o)`, `[[a], b] = t`
|
|
4726
|
+
* — the element's value destructures through the same assignment
|
|
4727
|
+
* machinery, its own hidden temp included). `valueOf` builds the
|
|
4728
|
+
* (defaulted) source value at the target's own type; it receives null
|
|
4729
|
+
* exactly for nested patterns, which have no single binding type —
|
|
4730
|
+
* defaults there keep a fence (JS would need the pattern's implied
|
|
4731
|
+
* type to test against). Optional-chain targets are not assignment
|
|
4732
|
+
* targets in JS at all; index-signature runtime keys and island/dyn
|
|
4733
|
+
* receivers keep the fence below. */
|
|
4734
|
+
function lowerAssignTargetInto(L, out, targetNode, blame, dflt, valueOf) {
|
|
4735
|
+
let t = targetNode;
|
|
4736
|
+
while (ts.isParenthesizedExpression(t))
|
|
4737
|
+
t = t.expression;
|
|
4738
|
+
const loc = locOf(t);
|
|
4739
|
+
if (ts.isObjectLiteralExpression(t) || ts.isArrayLiteralExpression(t)) {
|
|
4740
|
+
if (dflt) {
|
|
4741
|
+
L.unsupported("SC1031", blame, "defaults on nested patterns in destructuring assignment (bind a variable with the default, then destructure it)");
|
|
4742
|
+
}
|
|
4743
|
+
const sub = destructuringAssignInto(L, t, valueOf(null), null, blame, loc);
|
|
4744
|
+
out.push(...sub.stmts);
|
|
4745
|
+
return;
|
|
4746
|
+
}
|
|
4747
|
+
if (ts.isPropertyAccessExpression(t) && !t.questionDotToken) {
|
|
4748
|
+
const ft = L.fieldTarget(t);
|
|
4749
|
+
if (ft) {
|
|
4750
|
+
// The receiver temps FIRST: JS evaluates the target reference
|
|
4751
|
+
// before reading the element's value, so a side-effecting
|
|
4752
|
+
// receiver (`f().x`) must run before the source read the
|
|
4753
|
+
// defaulted value machinery may emit.
|
|
4754
|
+
const recv = L.declareHiddenLocal("%dtRecv", ft.obj.type);
|
|
4755
|
+
out.push({ kind: "varDecl", localId: recv.id, init: ft.obj, loc });
|
|
4756
|
+
const value = L.coerceInto(blame, valueOf(ft.fieldType), ft.fieldType);
|
|
4757
|
+
out.push(L.fieldSetStmt({ ...ft, obj: { kind: "varRef", localId: recv.id, type: ft.obj.type, loc } }, value, loc, t));
|
|
4758
|
+
return;
|
|
4759
|
+
}
|
|
4760
|
+
}
|
|
4761
|
+
if (ts.isElementAccessExpression(t) && !t.questionDotToken) {
|
|
4762
|
+
const recvT = L.mapTypeOf(L.typeOf(t.expression));
|
|
4763
|
+
if (recvT?.kind === "array") {
|
|
4764
|
+
const arr = L.lowerExpr(t.expression);
|
|
4765
|
+
if (arr.type.kind === "array") {
|
|
4766
|
+
const recv = L.declareHiddenLocal("%dtRecv", arr.type);
|
|
4767
|
+
out.push({ kind: "varDecl", localId: recv.id, init: arr, loc });
|
|
4768
|
+
const index = L.lowerExpr(t.argumentExpression);
|
|
4769
|
+
if (index.type.kind !== "f64") {
|
|
4770
|
+
L.unsupported("SC1090", t.argumentExpression, "indexing with non-number keys");
|
|
4771
|
+
}
|
|
4772
|
+
const idx = L.declareHiddenLocal("%dtIdx", F64);
|
|
4773
|
+
out.push({ kind: "varDecl", localId: idx.id, init: index, loc });
|
|
4774
|
+
const value = L.coerceInto(blame, valueOf(recvT.elem), recvT.elem);
|
|
4775
|
+
out.push({
|
|
4776
|
+
kind: "arraySet",
|
|
4777
|
+
arr: { kind: "varRef", localId: recv.id, type: arr.type, loc },
|
|
4778
|
+
index: { kind: "varRef", localId: idx.id, type: F64, loc },
|
|
4779
|
+
value,
|
|
4780
|
+
loc,
|
|
4781
|
+
});
|
|
4782
|
+
return;
|
|
4783
|
+
}
|
|
4784
|
+
}
|
|
4785
|
+
else if (recvT?.kind === "record") {
|
|
4786
|
+
// Tuple positions and LITERAL declared record keys are field
|
|
4787
|
+
// writes (the element-write lowering's own rule); runtime keys
|
|
4788
|
+
// keep the fence below.
|
|
4789
|
+
const shape = L.shapes.get(recvT.shapeId);
|
|
4790
|
+
const litKey = ts.isStringLiteralLike(t.argumentExpression) || ts.isNumericLiteral(t.argumentExpression)
|
|
4791
|
+
? L.foldedStringKeyOf(t.argumentExpression)
|
|
4792
|
+
: null;
|
|
4793
|
+
const field = litKey !== null ? shape?.fields.find((f) => f.name === litKey) : undefined;
|
|
4794
|
+
if (field) {
|
|
4795
|
+
const obj = L.lowerExpr(t.expression);
|
|
4796
|
+
if (obj.type.kind === "record") {
|
|
4797
|
+
const recv = L.declareHiddenLocal("%dtRecv", obj.type);
|
|
4798
|
+
out.push({ kind: "varDecl", localId: recv.id, init: obj, loc });
|
|
4799
|
+
const value = L.coerceInto(blame, valueOf(field.type), field.type);
|
|
4800
|
+
out.push({
|
|
4801
|
+
kind: "recordSet",
|
|
4802
|
+
obj: { kind: "varRef", localId: recv.id, type: obj.type, loc },
|
|
4803
|
+
shapeId: recvT.shapeId,
|
|
4804
|
+
field: field.name,
|
|
4805
|
+
value,
|
|
4806
|
+
loc,
|
|
4807
|
+
});
|
|
4808
|
+
return;
|
|
4809
|
+
}
|
|
4810
|
+
}
|
|
4811
|
+
}
|
|
4812
|
+
}
|
|
4813
|
+
L.unsupported("SC1031", targetNode, "destructuring assignment to targets with no static write form (assign a variable, then write the parts out)");
|
|
4814
|
+
}
|
|
4040
4815
|
/** One object-pattern property's pieces for the checked-dynamic path:
|
|
4041
4816
|
* the source FIELD name (or, over ISLAND sources, a runtime KEY
|
|
4042
4817
|
* expression the engine indexes with) and the resolved writable target.
|
|
@@ -4099,7 +4874,23 @@ function destructAssignPiece(L, prop, islandSource) {
|
|
|
4099
4874
|
* bounds test (JS reads undefined past the end), rest packs the tail
|
|
4100
4875
|
* fresh (array slices, tuple tails). */
|
|
4101
4876
|
function lowerArrayAssignInto(L, out, target, tmpRef, blame, loc) {
|
|
4102
|
-
|
|
4877
|
+
let srcType = tmpRef().type;
|
|
4878
|
+
// A STRING source (`[a, b] = s`): the declaration path's rule in
|
|
4879
|
+
// assignment position — the string iterator's code-point split
|
|
4880
|
+
// (%str.chars, astral characters whole) into a hidden chars array,
|
|
4881
|
+
// then the pattern proceeds as an array pattern over string[]
|
|
4882
|
+
// (defaults with the bounds test, rest packing the remaining code
|
|
4883
|
+
// points, elisions; positions past the last code point inherit the
|
|
4884
|
+
// array divergence). The EMPTY pattern skips the split — GetIterator
|
|
4885
|
+
// + close, nothing observable — through the iterable early-return
|
|
4886
|
+
// below. Callers vetted the source as a checker-typed string.
|
|
4887
|
+
if (srcType.kind === "string" && target.elements.length > 0) {
|
|
4888
|
+
const charsT = arrayOf(STRING);
|
|
4889
|
+
const chars = L.declareHiddenLocal("%dchars", charsT);
|
|
4890
|
+
out.push({ kind: "varDecl", localId: chars.id, init: strCharsCall(L, tmpRef(), loc), loc });
|
|
4891
|
+
tmpRef = () => ({ kind: "varRef", localId: chars.id, type: charsT, loc });
|
|
4892
|
+
srcType = charsT;
|
|
4893
|
+
}
|
|
4103
4894
|
// A sole leading rest whose operand is ITSELF an array pattern
|
|
4104
4895
|
// (`[...[a, b = 0]] = t`): the rest collects every element, then the
|
|
4105
4896
|
// inner pattern destructures the collection — over arrays/tuples that
|
|
@@ -4184,32 +4975,28 @@ function lowerArrayAssignInto(L, out, target, tmpRef, blame, loc) {
|
|
|
4184
4975
|
}
|
|
4185
4976
|
while (ts.isParenthesizedExpression(targetNode))
|
|
4186
4977
|
targetNode = targetNode.expression;
|
|
4187
|
-
|
|
4188
|
-
|
|
4189
|
-
|
|
4190
|
-
|
|
4191
|
-
|
|
4192
|
-
|
|
4193
|
-
|
|
4194
|
-
|
|
4195
|
-
|
|
4196
|
-
|
|
4197
|
-
|
|
4198
|
-
|
|
4199
|
-
|
|
4200
|
-
|
|
4201
|
-
|
|
4202
|
-
// IS the assignment (JS evaluates it unconditionally there).
|
|
4203
|
-
read = L.lowerExprExpecting(defaultNode, targetBinding.type);
|
|
4204
|
-
}
|
|
4205
|
-
else {
|
|
4978
|
+
// The element's (defaulted) value at the target's own type — shared
|
|
4979
|
+
// by variable, property/element, and nested-pattern targets (the
|
|
4980
|
+
// latter receive null: no single binding type exists there, and
|
|
4981
|
+
// defaults on nested targets fence in the shared machinery).
|
|
4982
|
+
const readOf = (targetT) => {
|
|
4983
|
+
if (isRest) {
|
|
4984
|
+
return isTuple
|
|
4985
|
+
? tupleTailValue(L, el, tmpRef, srcType, shape, i, targetT)
|
|
4986
|
+
: { kind: "arrIntrinsic", method: "slice", receiver: tmpRef(), args: [{ kind: "numLit", value: i, type: F64, loc: locOf(el) }], type: srcType, loc: locOf(el) };
|
|
4987
|
+
}
|
|
4988
|
+
if (isTuple && defaultNode !== null && targetT !== null && !shape.fields.some((f) => f.name === String(i))) {
|
|
4989
|
+
// Past the tuple's end the read is always undefined — the default
|
|
4990
|
+
// IS the assignment (JS evaluates it unconditionally there).
|
|
4991
|
+
return L.lowerExprExpecting(defaultNode, targetT);
|
|
4992
|
+
}
|
|
4206
4993
|
// Defaults: JS applies the default ONLY when the element reads
|
|
4207
4994
|
// undefined. A checked-dynamic element tests at runtime; a tuple
|
|
4208
4995
|
// position tests its undefined arm (a never-undefined field makes
|
|
4209
4996
|
// the default dead — JS would not evaluate it either); an array
|
|
4210
4997
|
// position carries the bounds test (past the end JS reads
|
|
4211
4998
|
// undefined where the plain read would trap).
|
|
4212
|
-
read = elemRead(i, el);
|
|
4999
|
+
let read = elemRead(i, el);
|
|
4213
5000
|
if (defaultNode !== null) {
|
|
4214
5001
|
if (read.type.kind === "dyn" || read.type.kind === "jsval") {
|
|
4215
5002
|
const boundary = read.type;
|
|
@@ -4229,18 +5016,26 @@ function lowerArrayAssignInto(L, out, target, tmpRef, blame, loc) {
|
|
|
4229
5016
|
loc: locOf(el),
|
|
4230
5017
|
};
|
|
4231
5018
|
}
|
|
4232
|
-
else if (
|
|
4233
|
-
read =
|
|
4234
|
-
|
|
4235
|
-
|
|
4236
|
-
read = arrayPositionDefaultValue(L, el, defaultNode, read, tmpRef, i, srcType.elem, targetBinding.type, out);
|
|
5019
|
+
else if (targetT !== null) {
|
|
5020
|
+
read = isTuple
|
|
5021
|
+
? undefArmDefault(L, el, defaultNode, read, targetT)
|
|
5022
|
+
: arrayPositionDefaultValue(L, el, defaultNode, read, tmpRef, i, srcType.elem, targetT, out);
|
|
4237
5023
|
}
|
|
4238
5024
|
}
|
|
5025
|
+
return read;
|
|
5026
|
+
};
|
|
5027
|
+
if (!ts.isIdentifier(targetNode)) {
|
|
5028
|
+
lowerAssignTargetInto(L, out, targetNode, el, defaultNode, readOf);
|
|
5029
|
+
return;
|
|
5030
|
+
}
|
|
5031
|
+
const targetBinding = L.resolveWritable(targetNode);
|
|
5032
|
+
if (!targetBinding) {
|
|
5033
|
+
L.rejectUnresolved(targetNode, `assignment to '${targetNode.text}' (not a writable local or module global)`);
|
|
4239
5034
|
}
|
|
4240
5035
|
out.push({
|
|
4241
5036
|
kind: "assign",
|
|
4242
5037
|
localId: targetBinding.id,
|
|
4243
|
-
value: L.coerceInto(el,
|
|
5038
|
+
value: L.coerceInto(el, readOf(targetBinding.type), targetBinding.type),
|
|
4244
5039
|
loc: locOf(el),
|
|
4245
5040
|
});
|
|
4246
5041
|
});
|
|
@@ -4371,7 +5166,7 @@ export function lowerForOf(L, stmt) {
|
|
|
4371
5166
|
if (iterable.type.kind !== "array") {
|
|
4372
5167
|
// The lib types strings as iterable too, so those for-ofs typecheck;
|
|
4373
5168
|
// only arrays, Maps, and Sets have a lowering. Named specifically —
|
|
4374
|
-
// the
|
|
5169
|
+
// the blanket type fence would blame the TYPE, which is itself supported.
|
|
4375
5170
|
if (iterable.type.kind === "string") {
|
|
4376
5171
|
// JS's string iterator walks code POINTS, not units: each pass
|
|
4377
5172
|
// yields the whole character (two UTF-16 units for astral chars,
|
|
@@ -4918,11 +5713,13 @@ function lowerForOfIsland(L, stmt, iterable, labels) {
|
|
|
4918
5713
|
function lowerForOfString(L, stmt, iterable, labels) {
|
|
4919
5714
|
let exprTarget = null;
|
|
4920
5715
|
let exprTargetNode = null;
|
|
5716
|
+
let exprPattern = null;
|
|
4921
5717
|
if (!ts.isVariableDeclarationList(stmt.initializer)) {
|
|
4922
5718
|
// `for (v of "hello")` over a PRE-DECLARED identifier: assign the
|
|
4923
|
-
// existing binding per pass (the array head's rule).
|
|
4924
|
-
//
|
|
4925
|
-
//
|
|
5719
|
+
// existing binding per pass (the array head's rule). PATTERN heads
|
|
5720
|
+
// (`for ([a] of "xy")`) destructure the per-iteration code point
|
|
5721
|
+
// through the string-source assignment lowerings; member targets
|
|
5722
|
+
// keep the fence.
|
|
4926
5723
|
let target = stmt.initializer;
|
|
4927
5724
|
while (ts.isParenthesizedExpression(target))
|
|
4928
5725
|
target = target.expression;
|
|
@@ -4930,7 +5727,10 @@ function lowerForOfString(L, stmt, iterable, labels) {
|
|
|
4930
5727
|
exprTarget = L.resolveWritable(target);
|
|
4931
5728
|
exprTargetNode = target;
|
|
4932
5729
|
}
|
|
4933
|
-
if (
|
|
5730
|
+
else if (ts.isObjectLiteralExpression(target) || ts.isArrayLiteralExpression(target)) {
|
|
5731
|
+
exprPattern = target;
|
|
5732
|
+
}
|
|
5733
|
+
if (!exprTarget && !exprPattern) {
|
|
4934
5734
|
L.unsupported("SC1090", stmt.initializer, "for-of over a pre-declared variable (declare the loop variable in the loop: for (const x of ...))");
|
|
4935
5735
|
}
|
|
4936
5736
|
}
|
|
@@ -4940,8 +5740,6 @@ function lowerForOfString(L, stmt, iterable, labels) {
|
|
|
4940
5740
|
}
|
|
4941
5741
|
const isLet = list !== null && (list.flags & ts.NodeFlags.Let) !== 0;
|
|
4942
5742
|
const decl = list ? list.declarations[0] : null; // the grammar allows exactly one
|
|
4943
|
-
if (decl && !ts.isIdentifier(decl.name))
|
|
4944
|
-
L.unsupported("SC1031", decl.name);
|
|
4945
5743
|
const loc = locOf(stmt);
|
|
4946
5744
|
L.scopes.push(new Map());
|
|
4947
5745
|
try {
|
|
@@ -4953,9 +5751,15 @@ function lowerForOfString(L, stmt, iterable, labels) {
|
|
|
4953
5751
|
// `for (var c of str)`: the character mechanics stay on a hidden
|
|
4954
5752
|
// per-iteration local; the body opens by assigning it into the ONE
|
|
4955
5753
|
// hoisted var binding (see forOfVarTarget). Pre-declared expression
|
|
4956
|
-
// heads assign their existing binding the same way.
|
|
4957
|
-
|
|
4958
|
-
|
|
5754
|
+
// heads assign their existing binding the same way. PATTERN heads
|
|
5755
|
+
// (`for (const [half] of "😀x")`) destructure the per-iteration
|
|
5756
|
+
// code point through the string-source pattern lowerings — each
|
|
5757
|
+
// element is a genuine one-code-point string.
|
|
5758
|
+
const declPattern = decl !== null && !ts.isIdentifier(decl.name)
|
|
5759
|
+
? decl.name
|
|
5760
|
+
: null;
|
|
5761
|
+
const varTarget = exprTarget ?? (decl && !declPattern ? forOfVarTarget(L, decl) : null);
|
|
5762
|
+
const ch = varTarget || declPattern || exprPattern
|
|
4959
5763
|
? L.declareHiddenLocal("%vof", STRING)
|
|
4960
5764
|
: L.declareLocal(decl.name, decl.name.text, STRING, isLet);
|
|
4961
5765
|
const chRef = { kind: "varRef", localId: ch.id, type: STRING, loc };
|
|
@@ -4983,6 +5787,12 @@ function lowerForOfString(L, stmt, iterable, labels) {
|
|
|
4983
5787
|
loc,
|
|
4984
5788
|
},
|
|
4985
5789
|
];
|
|
5790
|
+
if (declPattern) {
|
|
5791
|
+
L.lowerBindingPattern(declPattern, () => chRef, STRING, isLet, head);
|
|
5792
|
+
}
|
|
5793
|
+
if (exprPattern) {
|
|
5794
|
+
head.push(lowerDestructuringAssign(L, exprPattern, chRef, exprPattern, loc));
|
|
5795
|
+
}
|
|
4986
5796
|
const body = L.inCtl("loop", () => L.lowerScopedBlock(stmt.statement), labels);
|
|
4987
5797
|
return {
|
|
4988
5798
|
kind: "block",
|