@scriptc/compiler 0.0.36 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ambient/scriptc-node-fallback.d.ts +7 -5
- package/ambient/scriptc-overrides.d.ts +0 -7
- package/ambient/scriptc.d.ts +1 -1
- package/dist/backend/c/exprs.js +69 -45
- package/dist/backend/c/exprs.js.map +1 -1
- package/dist/backend/c/may-throw.js +3 -0
- package/dist/backend/c/may-throw.js.map +1 -1
- package/dist/backend/c/stmts.js +18 -0
- package/dist/backend/c/stmts.js.map +1 -1
- package/dist/backend/c/walkers.js +5 -7
- package/dist/backend/c/walkers.js.map +1 -1
- package/dist/backend/external-c.d.ts +33 -0
- package/dist/backend/external-c.js +51 -0
- package/dist/backend/external-c.js.map +1 -0
- package/dist/backend/link-plan.d.ts +37 -0
- package/dist/backend/link-plan.js +26 -0
- package/dist/backend/link-plan.js.map +1 -0
- package/dist/backend/linker.d.ts +22 -0
- package/dist/backend/linker.js +236 -0
- package/dist/backend/linker.js.map +1 -0
- package/dist/backend/llvm/dyn.js +7 -0
- package/dist/backend/llvm/dyn.js.map +1 -1
- package/dist/backend/llvm/emitter.js +34 -7
- package/dist/backend/llvm/emitter.js.map +1 -1
- package/dist/backend/llvm/expr-containers.js +68 -54
- package/dist/backend/llvm/expr-containers.js.map +1 -1
- package/dist/backend/llvm/expr-context.d.ts +1 -1
- package/dist/backend/llvm/expr-dispatch.js +2 -0
- package/dist/backend/llvm/expr-dispatch.js.map +1 -1
- package/dist/backend/llvm/expr-primitives.d.ts +1 -1
- package/dist/backend/llvm/expr-primitives.js +20 -0
- package/dist/backend/llvm/expr-primitives.js.map +1 -1
- package/dist/backend/llvm/expr-stream-bridges.js +25 -1
- package/dist/backend/llvm/expr-stream-bridges.js.map +1 -1
- package/dist/backend/llvm/walkers.js +31 -4
- package/dist/backend/llvm/walkers.js.map +1 -1
- package/dist/backend/native-codegen.d.ts +8 -1
- package/dist/backend/native-codegen.js +39 -25
- package/dist/backend/native-codegen.js.map +1 -1
- package/dist/backend/native-codegen.test.js +42 -4
- package/dist/backend/native-codegen.test.js.map +1 -1
- package/dist/backend/native-link-info.d.ts +1 -1
- package/dist/backend/native-link-info.js +42 -18
- package/dist/backend/native-link-info.js.map +1 -1
- package/dist/backend/native-toolchain.test.js +5 -1
- package/dist/backend/native-toolchain.test.js.map +1 -1
- package/dist/backend/runtime-pack-targets.test.d.ts +1 -0
- package/dist/backend/runtime-pack-targets.test.js +54 -0
- package/dist/backend/runtime-pack-targets.test.js.map +1 -0
- package/dist/backend/runtime-pack.d.ts +5 -30
- package/dist/backend/runtime-pack.js +46 -141
- package/dist/backend/runtime-pack.js.map +1 -1
- package/dist/backend/runtime-pack.test.js +49 -14
- package/dist/backend/runtime-pack.test.js.map +1 -1
- package/dist/backend/targets.d.ts +63 -17
- package/dist/backend/targets.js +251 -35
- package/dist/backend/targets.js.map +1 -1
- package/dist/backend/targets.test.js +27 -5
- package/dist/backend/targets.test.js.map +1 -1
- package/dist/coverage/surface-manifest.js +10 -1
- package/dist/coverage/surface-manifest.js.map +1 -1
- package/dist/frontend/lowering/array-values.d.ts +8 -0
- package/dist/frontend/lowering/array-values.js +60 -0
- package/dist/frontend/lowering/array-values.js.map +1 -0
- package/dist/frontend/lowering/lower-array-sort.d.ts +22 -0
- package/dist/frontend/lowering/lower-array-sort.js +619 -0
- package/dist/frontend/lowering/lower-array-sort.js.map +1 -0
- package/dist/frontend/lowering/lower-builtins.js +164 -4
- package/dist/frontend/lowering/lower-builtins.js.map +1 -1
- package/dist/frontend/lowering/lower-calls.d.ts +3 -4
- package/dist/frontend/lowering/lower-calls.js +260 -27
- package/dist/frontend/lowering/lower-calls.js.map +1 -1
- package/dist/frontend/lowering/lower-containers.js +728 -655
- package/dist/frontend/lowering/lower-containers.js.map +1 -1
- package/dist/frontend/lowering/lower-exprs.d.ts +4 -0
- package/dist/frontend/lowering/lower-exprs.js +769 -101
- package/dist/frontend/lowering/lower-exprs.js.map +1 -1
- package/dist/frontend/lowering/lower-inspect.js +47 -33
- package/dist/frontend/lowering/lower-inspect.js.map +1 -1
- package/dist/frontend/lowering/lower-island.d.ts +5 -4
- package/dist/frontend/lowering/lower-island.js +10 -5
- package/dist/frontend/lowering/lower-island.js.map +1 -1
- package/dist/frontend/lowering/lower-server.js +10 -0
- package/dist/frontend/lowering/lower-server.js.map +1 -1
- package/dist/frontend/lowering/lower-stmts.js +302 -71
- package/dist/frontend/lowering/lower-stmts.js.map +1 -1
- package/dist/frontend/lowering/lowerer.d.ts +74 -2
- package/dist/frontend/lowering/lowerer.js +1087 -10
- package/dist/frontend/lowering/lowerer.js.map +1 -1
- package/dist/frontend/lowering/surfaces.d.ts +10 -2
- package/dist/frontend/lowering/surfaces.js +15 -3
- package/dist/frontend/lowering/surfaces.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +45 -23
- package/dist/index.js.map +1 -1
- package/dist/ir/ir.d.ts +65 -11
- package/dist/ir/ir.js +25 -25
- package/dist/ir/ir.js.map +1 -1
- package/dist/ir/serialize.d.ts +1 -1
- package/dist/ir/serialize.js +1 -1
- package/dist/ir/validate.js +84 -37
- package/dist/ir/validate.js.map +1 -1
- package/dist/library/fence-eval.js +3 -1
- package/dist/library/fence-eval.js.map +1 -1
- package/dist/library/int-infer.js +12 -1
- package/dist/library/int-infer.js.map +1 -1
- package/dist/library/int-infer.test.js +4 -4
- package/dist/library/library-cache.test.js +5 -5
- package/dist/startup-cache.d.ts +9 -1
- package/dist/startup-cache.js +16 -1
- package/dist/startup-cache.js.map +1 -1
- package/package.json +17 -4
- package/surface-manifest.json +5 -5
|
@@ -7,6 +7,7 @@ import { InternalCompilerError } from "../../errors.js";
|
|
|
7
7
|
* (FieldTarget). */
|
|
8
8
|
import * as ts from "../ts7/adapter.js";
|
|
9
9
|
import { dirname, posix } from "node:path";
|
|
10
|
+
import { pathToFileURL } from "node:url";
|
|
10
11
|
import { wasiGuestPath } from "../../wasi-paths.js";
|
|
11
12
|
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/ir.js";
|
|
12
13
|
import { cjsClassExprWholeExportOf, cjsExportAssignmentOf, cjsExportDiscardReason, isCjsExportTableLiteral, isCjsJsFile, isJsSourceFile, isModuleExportsAccess, isNodeEsmFile, locOf } from "../program.js";
|
|
@@ -14,6 +15,7 @@ import { ARRAY_METHODS, builtinConstLit, builtinFenceHintOf, builtinModuleConstO
|
|
|
14
15
|
import { blockedBindingUseDiag, recordShapeMismatchDiag, requiresDynamicPackageDiag, unsupportedDiag } from "../../diagnostics/diagnostic.js";
|
|
15
16
|
import { PoisonError, dynUndefinedExpr, jsFuncNameOf, neverTaintedJsType, nodeThrowExpr, own } from "./lowerer.js";
|
|
16
17
|
import { lowerIndexMergeHelper, lowerNpmStaticSafeIndexRead, lowerSafeIndexRead, strCharsCall } from "./lower-containers.js";
|
|
18
|
+
import { arrayValueStore } from "./array-values.js";
|
|
17
19
|
import { npmStaticPackageOfPath } from "../npm-static.js";
|
|
18
20
|
import { unsupportedModuleFeatureOf } from "../builtin-modules.js";
|
|
19
21
|
import { fenceEnumObjectValue, lowerEnumAccess } from "./lower-enums.js";
|
|
@@ -640,9 +642,13 @@ function lowerExprInner(lowerer, expr) {
|
|
|
640
642
|
const symbol = lowerer.resolveValueSymbol(expr);
|
|
641
643
|
const runtimeOptionalRoot = lowerer.runtimeOptionalRootOf(local);
|
|
642
644
|
const active = lowerer.runtimeOptionalLocals.has(runtimeOptionalRoot);
|
|
645
|
+
const arithmetic = lowerer.runtimeOptionalArithmeticLocals.has(runtimeOptionalRoot);
|
|
643
646
|
const captured = !!symbol &&
|
|
644
647
|
lowerer.runtimeOptionalStorageLocals.has(runtimeOptionalRoot) &&
|
|
645
|
-
lowerer.ctx.captureBySymbol.get(symbol) === local;
|
|
648
|
+
(lowerer.ctx.captureBySymbol.get(symbol) === local || runtimeOptionalRoot !== local);
|
|
649
|
+
if (arithmetic && !runtimeOptionalUseOf(expr)) {
|
|
650
|
+
return { kind: "varRef", localId: local.id, type: local.type, loc };
|
|
651
|
+
}
|
|
646
652
|
if (active || captured) {
|
|
647
653
|
const use = runtimeOptionalUseOf(expr);
|
|
648
654
|
if (use?.complex) {
|
|
@@ -665,8 +671,20 @@ function lowerExprInner(lowerer, expr) {
|
|
|
665
671
|
}
|
|
666
672
|
return runtimeOptionalReceiverRead(lowerer, expr, local, null, loc);
|
|
667
673
|
}
|
|
668
|
-
if (
|
|
669
|
-
|
|
674
|
+
if (runtimeOptionalObjectWalkerArg(lowerer, expr)) {
|
|
675
|
+
const narrowed = lowerer.mapTypeOf(lowerer.typeOf(expr));
|
|
676
|
+
if (local.type.kind === "union" && narrowed) {
|
|
677
|
+
const helper = narrowed.kind === "union"
|
|
678
|
+
? lowerer.narrowedRetagHelper(expr, local.type.unionId, narrowed.unionId, loc)
|
|
679
|
+
: !isUnitType(narrowed)
|
|
680
|
+
? lowerer.narrowedArmHelper(local.type.unionId, narrowed, loc)
|
|
681
|
+
: null;
|
|
682
|
+
if (helper) {
|
|
683
|
+
return { kind: "call", callee: helper, args: [varRef(local.id, local.type, loc)], type: narrowed, loc };
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
return { kind: "varRef", localId: local.id, type: local.type, loc };
|
|
670
688
|
}
|
|
671
689
|
return lowerer.maybeNarrow({ kind: "varRef", localId: local.id, type: local.type, loc }, expr);
|
|
672
690
|
}
|
|
@@ -686,7 +704,53 @@ function lowerExprInner(lowerer, expr) {
|
|
|
686
704
|
lowerer.pushDiag(blockedBindingUseDiag(expr.text, loc));
|
|
687
705
|
throw new PoisonError();
|
|
688
706
|
}
|
|
689
|
-
|
|
707
|
+
const ref = { kind: "varRef", localId: g.id, type: g.type, loc };
|
|
708
|
+
if (lowerer.isRuntimeOptionalArithmeticGlobal(g) && !runtimeOptionalUseOf(expr)) {
|
|
709
|
+
return ref;
|
|
710
|
+
}
|
|
711
|
+
const storageHasUndefined = g.type.kind === "union" &&
|
|
712
|
+
lowerer.armTag(g.type.unionId, UNDEFINED_T) >= 0;
|
|
713
|
+
if (lowerer.isRuntimeOptionalGlobal(g) || storageHasUndefined) {
|
|
714
|
+
// A checker-narrowed global receiver (`if (tags !== undefined)
|
|
715
|
+
// tags[tags.length] = ...`, or `a ? Object.keys(a) : ...`) is
|
|
716
|
+
// still stored as the runtime optional union. Extract the proven
|
|
717
|
+
// value arm before routing the member/element operation so dynamic
|
|
718
|
+
// keys and builtin record walkers do not see a union receiver.
|
|
719
|
+
const narrowed = lowerer.mapTypeOf(lowerer.typeOf(expr));
|
|
720
|
+
const arithmeticUnion = g.type.kind === "union" &&
|
|
721
|
+
(lowerer.unions.get(g.type.unionId)?.arms.length === 2) &&
|
|
722
|
+
lowerer.unions.get(g.type.unionId).arms.some((a) => a.kind === "f64") &&
|
|
723
|
+
lowerer.unions.get(g.type.unionId).arms.some((a) => a.kind === "string");
|
|
724
|
+
if (!arithmeticUnion && g.type.kind === "union" && narrowed && narrowed.kind !== "union" &&
|
|
725
|
+
!isUnitType(narrowed) &&
|
|
726
|
+
narrowed.kind !== "f64" && narrowed.kind !== "string" && narrowed.kind !== "bool" &&
|
|
727
|
+
lowerer.armTag(g.type.unionId, narrowed) >= 0) {
|
|
728
|
+
const helper = lowerer.narrowedArmHelper(g.type.unionId, narrowed, loc);
|
|
729
|
+
if (helper)
|
|
730
|
+
return { kind: "call", callee: helper, args: [ref], type: narrowed, loc };
|
|
731
|
+
}
|
|
732
|
+
const use = runtimeOptionalUseOf(expr);
|
|
733
|
+
// The enclosing logical/conditional expression consumes the union
|
|
734
|
+
// before any member operation runs (`(value ?? fallback).length`).
|
|
735
|
+
// Keep the tagged value intact and let that lazy expression decide
|
|
736
|
+
// its branch; extracting here would evaluate or throw too early.
|
|
737
|
+
if (use?.complex)
|
|
738
|
+
return ref;
|
|
739
|
+
if (use?.optional)
|
|
740
|
+
return ref;
|
|
741
|
+
if (use?.kind === "property")
|
|
742
|
+
return runtimeOptionalReceiverRead(lowerer, expr, g, use.access.name.text, loc);
|
|
743
|
+
if (use?.kind === "element") {
|
|
744
|
+
const key = runtimeOptionalElementKey(use.access.argumentExpression);
|
|
745
|
+
if (key === null)
|
|
746
|
+
lowerer.unsupported("SC1090", use.access, "a computed read from a runtime-optional global (use a literal key)");
|
|
747
|
+
return runtimeOptionalReceiverRead(lowerer, expr, g, key, loc);
|
|
748
|
+
}
|
|
749
|
+
if (use?.kind === "call")
|
|
750
|
+
return runtimeOptionalReceiverRead(lowerer, expr, g, null, loc);
|
|
751
|
+
return ref;
|
|
752
|
+
}
|
|
753
|
+
return lowerer.maybeNarrow(ref, expr);
|
|
690
754
|
}
|
|
691
755
|
// `undefined` — tsc's intrinsic global (a local shadowing the name
|
|
692
756
|
// resolved above): a unit literal, exactly like the `null` keyword.
|
|
@@ -709,9 +773,7 @@ function lowerExprInner(lowerer, expr) {
|
|
|
709
773
|
// directory at `/tmp`. Bake the matching GUEST spelling so resource
|
|
710
774
|
// lookups relative to these CommonJS globals stay inside the exposed
|
|
711
775
|
// capability instead of naming an unreachable host-absolute path.
|
|
712
|
-
const fileName = lowerer
|
|
713
|
-
? wasiGuestPath(sf.fileName) ?? sf.fileName.replace(/\\/g, "/")
|
|
714
|
-
: sf.fileName;
|
|
776
|
+
const fileName = moduleFileName(lowerer, sf);
|
|
715
777
|
const value = expr.text === "__dirname"
|
|
716
778
|
? (lowerer.targetPlatform === "wasi" ? posix.dirname(fileName) : dirname(fileName))
|
|
717
779
|
: fileName;
|
|
@@ -1243,6 +1305,15 @@ function lowerExprInner(lowerer, expr) {
|
|
|
1243
1305
|
if (en)
|
|
1244
1306
|
return en;
|
|
1245
1307
|
}
|
|
1308
|
+
// `import.meta.<field>` — the file-backed ESM metadata that survives
|
|
1309
|
+
// compilation as a constant. Only direct, identifier-named reads are
|
|
1310
|
+
// claimed here; the bare meta object, computed access, writes, and
|
|
1311
|
+
// unsupported members continue through the generic module-loader fence
|
|
1312
|
+
// below. The source file is the module identity, so imported modules
|
|
1313
|
+
// receive their own filename/dirname and only the entry receives main.
|
|
1314
|
+
const importMeta = importMetaProperty(lowerer, expr);
|
|
1315
|
+
if (importMeta)
|
|
1316
|
+
return importMeta;
|
|
1246
1317
|
// `require.main.filename` / `require.main?.filename` — CommonJS
|
|
1247
1318
|
// entry-module identity: in a compiled binary require.main IS the
|
|
1248
1319
|
// entry module (never undefined in a CJS graph, so the chain's guard
|
|
@@ -1536,8 +1607,14 @@ function lowerExprInner(lowerer, expr) {
|
|
|
1536
1607
|
lowerer.lowerFieldRead(expr);
|
|
1537
1608
|
// A union-typed field read narrows like an identifier when the
|
|
1538
1609
|
// checker has narrowed this use to one arm.
|
|
1539
|
-
if (handled)
|
|
1610
|
+
if (handled) {
|
|
1611
|
+
if (handled.type.kind === "union" &&
|
|
1612
|
+
lowerer.armTag(handled.type.unionId, UNDEFINED_T) >= 0 &&
|
|
1613
|
+
handled.kind === "recordGet" &&
|
|
1614
|
+
lowerer.isRuntimeOptionalField(handled.shapeId, handled.field))
|
|
1615
|
+
return handled;
|
|
1540
1616
|
return lowerer.maybeNarrow(handled, expr);
|
|
1617
|
+
}
|
|
1541
1618
|
// `globalThis.<name>` that no lowering above claimed
|
|
1542
1619
|
// (globalThis.crypto, globalThis.SubtleCrypto, globalThis.localStorage
|
|
1543
1620
|
// — the harness's capability-conditional knownGlobals adds): the
|
|
@@ -1778,6 +1855,46 @@ function lowerExprInner(lowerer, expr) {
|
|
|
1778
1855
|
lowerer.unsupported(entry.code, expr, entry.feature);
|
|
1779
1856
|
lowerer.unsupported("SC1090", expr, `syntax '${ts.SyntaxKind[expr.kind]}'`);
|
|
1780
1857
|
}
|
|
1858
|
+
function moduleFileName(lowerer, sf) {
|
|
1859
|
+
return lowerer.targetPlatform === "wasi"
|
|
1860
|
+
? wasiGuestPath(sf.fileName) ?? sf.fileName.replace(/\\/g, "/")
|
|
1861
|
+
: sf.fileName;
|
|
1862
|
+
}
|
|
1863
|
+
function importMetaProperty(lowerer, expr) {
|
|
1864
|
+
if (expr.questionDotToken !== undefined || !ts.isMetaProperty(expr.expression))
|
|
1865
|
+
return null;
|
|
1866
|
+
const meta = expr.expression;
|
|
1867
|
+
if (meta.keywordToken !== ts.SyntaxKind.ImportKeyword || meta.name.text !== "meta")
|
|
1868
|
+
return null;
|
|
1869
|
+
const sf = expr.getSourceFile();
|
|
1870
|
+
const fileName = moduleFileName(lowerer, sf);
|
|
1871
|
+
if (lowerer.dynamic) {
|
|
1872
|
+
lowerer.unsupported("SC1090", expr, `'import.meta.${expr.name.text}' in a dynamic island (module-loader metadata is supported only in static ESM)`);
|
|
1873
|
+
}
|
|
1874
|
+
switch (expr.name.text) {
|
|
1875
|
+
case "url":
|
|
1876
|
+
return {
|
|
1877
|
+
kind: "strLit",
|
|
1878
|
+
value: pathToFileURL(fileName, { windows: lowerer.targetPlatform === "win32" }).href,
|
|
1879
|
+
type: STRING,
|
|
1880
|
+
loc: locOf(expr),
|
|
1881
|
+
};
|
|
1882
|
+
case "filename":
|
|
1883
|
+
return { kind: "strLit", value: fileName, type: STRING, loc: locOf(expr) };
|
|
1884
|
+
case "dirname":
|
|
1885
|
+
return {
|
|
1886
|
+
kind: "strLit",
|
|
1887
|
+
value: lowerer.targetPlatform === "wasi" ? posix.dirname(fileName) : dirname(fileName),
|
|
1888
|
+
type: STRING,
|
|
1889
|
+
loc: locOf(expr),
|
|
1890
|
+
};
|
|
1891
|
+
case "main":
|
|
1892
|
+
return { kind: "boolLit", value: sf === lowerer.entry, type: BOOL, loc: locOf(expr) };
|
|
1893
|
+
default:
|
|
1894
|
+
lowerer.unsupported("SC1090", expr, `'import.meta.${expr.name.text}' (only url, filename, dirname, and main are supported in static ESM)`);
|
|
1895
|
+
return null;
|
|
1896
|
+
}
|
|
1897
|
+
}
|
|
1781
1898
|
/** `c ? a : b` — see the inline comments. `expected` plays the contextual
|
|
1782
1899
|
* array type's role when the caller knows the slot's array type and tsc's
|
|
1783
1900
|
* API doesn't surface it (a ternary as a SPREAD source — lowerArrayLiteral
|
|
@@ -2125,19 +2242,65 @@ export function maybeNarrow(lowerer, expr, node) {
|
|
|
2125
2242
|
* even though an OOB-safe assignment may have restored undefined. Check
|
|
2126
2243
|
* that hidden arm and throw the member-read TypeError Node would produce. */
|
|
2127
2244
|
function runtimeOptionalReceiverRead(lowerer, expr, local, property, loc) {
|
|
2128
|
-
if (local.type.kind !== "union")
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2245
|
+
if (local.type.kind !== "union") {
|
|
2246
|
+
lowerer.runtimeOptionalLocals.delete(local);
|
|
2247
|
+
lowerer.runtimeOptionalStorageLocals.delete(local);
|
|
2248
|
+
return varRef(local.id, local.type, loc);
|
|
2249
|
+
}
|
|
2250
|
+
// A non-null/type assertion around the receiver carries the useful
|
|
2251
|
+
// checker narrowing on the wrapper rather than on the identifier token
|
|
2252
|
+
// itself (`gg!.day`). Follow only erased wrappers; the member/call node
|
|
2253
|
+
// still receives the checked extraction built below.
|
|
2254
|
+
let narrowedNode = expr;
|
|
2255
|
+
while ((ts.isParenthesizedExpression(narrowedNode.parent) ||
|
|
2256
|
+
ts.isAssertionExpression(narrowedNode.parent) ||
|
|
2257
|
+
ts.isSatisfiesExpression(narrowedNode.parent) ||
|
|
2258
|
+
ts.isNonNullExpression(narrowedNode.parent)) &&
|
|
2259
|
+
narrowedNode.parent.expression === narrowedNode) {
|
|
2260
|
+
narrowedNode = narrowedNode.parent;
|
|
2261
|
+
}
|
|
2262
|
+
const narrowed = lowerer.mapTypeOf(lowerer.typeOf(narrowedNode));
|
|
2263
|
+
if (!narrowed || isUnitType(narrowed)) {
|
|
2132
2264
|
lowerer.unsupported("SC1090", expr, "a runtime-optional capture whose narrowed value type is not representable");
|
|
2133
2265
|
}
|
|
2266
|
+
if (narrowed.kind === "union") {
|
|
2267
|
+
// The checker may keep the receiver at the storage union unchanged
|
|
2268
|
+
// (`string | undefined` on a JS `.match()` call). There is no
|
|
2269
|
+
// sub-union to re-tag: preserve the value for the downstream
|
|
2270
|
+
// nullable-receiver lowering, which performs the member-specific
|
|
2271
|
+
// checked extraction. Refusing here deferred the whole statement to
|
|
2272
|
+
// a runtime fence before that lowering could run.
|
|
2273
|
+
if (typeEquals(local.type, narrowed)) {
|
|
2274
|
+
return varRef(local.id, local.type, loc);
|
|
2275
|
+
}
|
|
2276
|
+
// A captured array element can itself be a union of records (for
|
|
2277
|
+
// example Circle | Square). The optional storage union has one extra
|
|
2278
|
+
// undefined arm; re-tag it to the checker-narrowed value union with a
|
|
2279
|
+
// checked trap for that arm, then let union property lowering inspect
|
|
2280
|
+
// the surviving record arms.
|
|
2281
|
+
const helper = lowerer.narrowedRetagHelper(expr, local.type.unionId, narrowed.unionId, loc);
|
|
2282
|
+
if (!helper) {
|
|
2283
|
+
lowerer.unsupported("SC1090", expr, "a runtime-optional capture whose narrowed union is not representable");
|
|
2284
|
+
}
|
|
2285
|
+
return { kind: "call", callee: helper, args: [varRef(local.id, local.type, loc)], type: narrowed, loc };
|
|
2286
|
+
}
|
|
2134
2287
|
const valueTag = lowerer.armTag(local.type.unionId, narrowed);
|
|
2135
2288
|
const undefTag = lowerer.armTag(local.type.unionId, UNDEFINED_T);
|
|
2136
2289
|
if (valueTag < 0 || undefTag < 0)
|
|
2137
2290
|
throw new InternalCompilerError("runtime-optional local is missing its value or undefined arm");
|
|
2138
2291
|
const def = lowerer.unions.get(local.type.unionId);
|
|
2139
2292
|
if (!def || def.arms.length !== 2) {
|
|
2140
|
-
|
|
2293
|
+
// The checker may narrow a runtime-optional capture to one arm of a
|
|
2294
|
+
// value union (`Circle | Square | undefined` -> `Circle`). The
|
|
2295
|
+
// two-arm fast path below cannot extract that arm safely: a later
|
|
2296
|
+
// array mutation can leave any of the stored tags in the slot. Use
|
|
2297
|
+
// the checked helper so every other arm, including undefined, remains
|
|
2298
|
+
// a catchable TypeError rather than an unchecked payload read.
|
|
2299
|
+
const helper = lowerer.narrowedArmHelper(local.type.unionId, narrowed, loc);
|
|
2300
|
+
if (!helper) {
|
|
2301
|
+
lowerer.unsupported("SC1090", expr, "a direct read from a runtime-optional capture with multiple value arms");
|
|
2302
|
+
}
|
|
2303
|
+
return { kind: "call", callee: helper, args: [varRef(local.id, local.type, loc)], type: narrowed, loc };
|
|
2141
2304
|
}
|
|
2142
2305
|
const message = property === null
|
|
2143
2306
|
? `${expr.text} is not a function`
|
|
@@ -2209,6 +2372,25 @@ function runtimeOptionalUseOf(expr) {
|
|
|
2209
2372
|
}
|
|
2210
2373
|
return null;
|
|
2211
2374
|
}
|
|
2375
|
+
/** Object.keys/Object.entries require an object receiver, but their first
|
|
2376
|
+
* argument is not syntactically a member receiver. Recognize that one
|
|
2377
|
+
* standard-library position so a runtime-optional loop element can be
|
|
2378
|
+
* checked against the checker-narrowed record arm before builtin lowering. */
|
|
2379
|
+
function runtimeOptionalObjectWalkerArg(lowerer, expr) {
|
|
2380
|
+
let arg = expr;
|
|
2381
|
+
while ((ts.isParenthesizedExpression(arg.parent) ||
|
|
2382
|
+
ts.isAssertionExpression(arg.parent) ||
|
|
2383
|
+
ts.isSatisfiesExpression(arg.parent) ||
|
|
2384
|
+
ts.isNonNullExpression(arg.parent)) &&
|
|
2385
|
+
arg.parent.expression === arg) {
|
|
2386
|
+
arg = arg.parent;
|
|
2387
|
+
}
|
|
2388
|
+
const call = arg.parent;
|
|
2389
|
+
if (!ts.isCallExpression(call) || call.arguments[0] !== arg || !ts.isPropertyAccessExpression(call.expression))
|
|
2390
|
+
return false;
|
|
2391
|
+
const access = call.expression;
|
|
2392
|
+
return (access.name.text === "keys" || access.name.text === "entries") && lowerer.isStdlibMember(access);
|
|
2393
|
+
}
|
|
2212
2394
|
function runtimeOptionalElementKey(expr) {
|
|
2213
2395
|
if (ts.isStringLiteral(expr) || ts.isNumericLiteral(expr))
|
|
2214
2396
|
return expr.text;
|
|
@@ -2758,7 +2940,8 @@ export function lowerOptionalChain(lowerer, expr) {
|
|
|
2758
2940
|
dotNode = tail;
|
|
2759
2941
|
recvNode = tail.expression;
|
|
2760
2942
|
}
|
|
2761
|
-
const
|
|
2943
|
+
const loweredReceiver = lowerer.lowerExpr(recvNode);
|
|
2944
|
+
const receiver = lowerer.runtimeOptionalSourceValue(recvNode, loweredReceiver) ?? loweredReceiver;
|
|
2762
2945
|
if (receiver.type.kind === "dyn") {
|
|
2763
2946
|
// `pkg?.name` / `pkg?.scripts?.[k]` on a JSON.parse result: dyn
|
|
2764
2947
|
// represents undefined directly, so the chain step IS the keyed
|
|
@@ -3153,6 +3336,10 @@ export function lowerIntrinsicProperty(lowerer, expr) {
|
|
|
3153
3336
|
if (kind === "child" ? !isChildSurfaceMember(lowerer, expr) : !tupleLengthOnArray && !lowerer.isStdlibMember(expr))
|
|
3154
3337
|
return null;
|
|
3155
3338
|
const name = expr.name.text;
|
|
3339
|
+
const strictReceiver = (expected) => {
|
|
3340
|
+
const value = lowerer.lowerExpr(expr.expression);
|
|
3341
|
+
return lowerer.runtimeOptionalPropertyReceiver(expr.expression, value, expected, name) ?? value;
|
|
3342
|
+
};
|
|
3156
3343
|
if (kind === "child") {
|
|
3157
3344
|
const loc = locOf(expr);
|
|
3158
3345
|
// The lifecycle reads, Node's exact shapes (SEMANTICS.md pins the
|
|
@@ -3315,7 +3502,7 @@ export function lowerIntrinsicProperty(lowerer, expr) {
|
|
|
3315
3502
|
}
|
|
3316
3503
|
if (kind === "regex") {
|
|
3317
3504
|
if (name === "source" || name === "flags") {
|
|
3318
|
-
const receiver =
|
|
3505
|
+
const receiver = strictReceiver({ kind: "regex" });
|
|
3319
3506
|
return { kind: "regexIntrinsic", method: name, receiver, args: [], type: STRING, loc: locOf(expr) };
|
|
3320
3507
|
}
|
|
3321
3508
|
if (name === "test") {
|
|
@@ -3334,7 +3521,19 @@ export function lowerIntrinsicProperty(lowerer, expr) {
|
|
|
3334
3521
|
if (kind === "bytes") {
|
|
3335
3522
|
const loc = locOf(expr);
|
|
3336
3523
|
if (name === "length" || name === "byteLength") {
|
|
3337
|
-
const
|
|
3524
|
+
const expected = lowerer.mapTypeOf(lowerer.typeOf(expr.expression));
|
|
3525
|
+
let receiver = expected?.kind === "bytes"
|
|
3526
|
+
? strictReceiver(expected)
|
|
3527
|
+
: lowerer.lowerExpr(expr.expression);
|
|
3528
|
+
if (receiver.type.kind === "union" && lowerer.armTag(receiver.type.unionId, UNDEFINED_T) >= 0) {
|
|
3529
|
+
const present = lowerer.stripUndefinedArm(receiver.type);
|
|
3530
|
+
const helper = present.kind === "bytes"
|
|
3531
|
+
? lowerer.narrowedArmHelper(receiver.type.unionId, present, locOf(expr.expression))
|
|
3532
|
+
: null;
|
|
3533
|
+
receiver = helper
|
|
3534
|
+
? { kind: "call", callee: helper, args: [receiver], type: present, loc: locOf(expr.expression) }
|
|
3535
|
+
: lowerer.maybeNarrow(receiver, expr.expression);
|
|
3536
|
+
}
|
|
3338
3537
|
// An island handle behind a typed-array .d.ts surface: the engine
|
|
3339
3538
|
// property read, exiting at the declared number type (the array
|
|
3340
3539
|
// .length rule). Chain-handled reads stay jsval.
|
|
@@ -3387,7 +3586,21 @@ export function lowerIntrinsicProperty(lowerer, expr) {
|
|
|
3387
3586
|
return null;
|
|
3388
3587
|
}
|
|
3389
3588
|
if (name === "length") {
|
|
3390
|
-
const
|
|
3589
|
+
const expected = kind === "string"
|
|
3590
|
+
? STRING
|
|
3591
|
+
: lowerer.mapTypeOf(lowerer.typeOf(expr.expression));
|
|
3592
|
+
let receiver = expected && (expected.kind === "string" || expected.kind === "array")
|
|
3593
|
+
? strictReceiver(expected)
|
|
3594
|
+
: lowerer.lowerExpr(expr.expression);
|
|
3595
|
+
if (receiver.type.kind === "union" && lowerer.armTag(receiver.type.unionId, UNDEFINED_T) >= 0 && kind === "array") {
|
|
3596
|
+
const present = lowerer.stripUndefinedArm(receiver.type);
|
|
3597
|
+
const helper = present.kind === "array"
|
|
3598
|
+
? lowerer.narrowedArmHelper(receiver.type.unionId, present, locOf(expr.expression))
|
|
3599
|
+
: null;
|
|
3600
|
+
receiver = helper
|
|
3601
|
+
? { kind: "call", callee: helper, args: [receiver], type: present, loc: locOf(expr.expression) }
|
|
3602
|
+
: lowerer.maybeNarrow(receiver, expr.expression);
|
|
3603
|
+
}
|
|
3391
3604
|
// An island handle behind an array-typed .d.ts surface
|
|
3392
3605
|
// (`parts().length` on a declared `string[]` return — arrays never
|
|
3393
3606
|
// exit eagerly, so the value stays jsval): an engine property read,
|
|
@@ -3597,8 +3810,25 @@ export function lowerArrayLiteral(lowerer, expr, expected) {
|
|
|
3597
3810
|
}
|
|
3598
3811
|
lowerer.badType(expr, tsType);
|
|
3599
3812
|
}
|
|
3600
|
-
|
|
3813
|
+
let type = mapped;
|
|
3814
|
+
// Keep the array payload type fixed. Optional reads are stored through
|
|
3815
|
+
// arrayValueStore, which records UNDEFINED in the state byte while the
|
|
3816
|
+
// payload remains number/string/etc.; widening the array element here
|
|
3817
|
+
// would force boxed union storage and change aliases.
|
|
3601
3818
|
const spreads = [];
|
|
3819
|
+
const holes = new Set();
|
|
3820
|
+
const runtimeOptionalElements = new Set();
|
|
3821
|
+
let needsStateBuild = false;
|
|
3822
|
+
for (let i = 0; i < expr.elements.length; i++) {
|
|
3823
|
+
const candidate = expr.elements[i];
|
|
3824
|
+
if (ts.isSpreadElement(candidate) || ts.isOmittedExpression(candidate))
|
|
3825
|
+
continue;
|
|
3826
|
+
const probe = probeLower(lowerer, candidate);
|
|
3827
|
+
if (probe && lowerer.runtimeOptionalWidening(probe.type, type.elem) !== null) {
|
|
3828
|
+
runtimeOptionalElements.add(i);
|
|
3829
|
+
needsStateBuild = true;
|
|
3830
|
+
}
|
|
3831
|
+
}
|
|
3602
3832
|
const elems = expr.elements.map((el, i) => {
|
|
3603
3833
|
if (ts.isSpreadElement(el)) {
|
|
3604
3834
|
// `[...xs, b]`: xs must be an array of the literal's own element
|
|
@@ -3674,16 +3904,14 @@ export function lowerArrayLiteral(lowerer, expr, expected) {
|
|
|
3674
3904
|
spreads.push(i);
|
|
3675
3905
|
return src;
|
|
3676
3906
|
}
|
|
3677
|
-
// A HOLE (`[,]` — an elision)
|
|
3678
|
-
//
|
|
3679
|
-
//
|
|
3680
|
-
//
|
|
3681
|
-
// documented sparse-literal divergence.
|
|
3907
|
+
// A HOLE (`[,]` — an elision) remains absent. Ordinary reads still
|
|
3908
|
+
// answer undefined, while presence-sensitive methods and concat keep
|
|
3909
|
+
// the hole. The state-building path below advances length without
|
|
3910
|
+
// writing a value into this slot.
|
|
3682
3911
|
if (ts.isOmittedExpression(el)) {
|
|
3683
|
-
|
|
3684
|
-
|
|
3685
|
-
|
|
3686
|
-
lowerer.unsupported("SC1090", el, "holes in array literals of this element type");
|
|
3912
|
+
needsStateBuild = true;
|
|
3913
|
+
holes.add(i);
|
|
3914
|
+
return { kind: "unitLit", unit: "undefined", type: UNDEFINED_T, loc: locOf(el) };
|
|
3687
3915
|
}
|
|
3688
3916
|
// An ARRAY-LITERAL element under a UNION element slot whose own type
|
|
3689
3917
|
// has no lift into the slot builds against the union's single array
|
|
@@ -3716,12 +3944,80 @@ export function lowerArrayLiteral(lowerer, expr, expected) {
|
|
|
3716
3944
|
// Elements flow into the element slot like an assignment: union
|
|
3717
3945
|
// elements wrap plain arm values (`[1, "a"]` as (number | string)[]),
|
|
3718
3946
|
// holes reject inside lowerExpr.
|
|
3719
|
-
|
|
3947
|
+
let lowered;
|
|
3948
|
+
const directOptionalCandidate = runtimeOptionalElements.has(i) ||
|
|
3949
|
+
ts.isElementAccessExpression(el) ||
|
|
3950
|
+
(ts.isCallExpression(el) && ts.isPropertyAccessExpression(el.expression) &&
|
|
3951
|
+
(el.expression.name.text === "pop" || el.expression.name.text === "shift"));
|
|
3952
|
+
if (directOptionalCandidate) {
|
|
3953
|
+
const raw = lowerer.lowerExpr(el);
|
|
3954
|
+
if (!typeEquals(raw.type, type.elem)) {
|
|
3955
|
+
needsStateBuild = true;
|
|
3956
|
+
return raw;
|
|
3957
|
+
}
|
|
3958
|
+
lowered = raw;
|
|
3959
|
+
}
|
|
3960
|
+
else {
|
|
3961
|
+
lowered = lowerer.lowerExprExpecting(el, type.elem);
|
|
3962
|
+
}
|
|
3720
3963
|
if (!typeEquals(lowered.type, type.elem))
|
|
3721
3964
|
lowerer.badType(el, lowerer.typeOf(el));
|
|
3722
3965
|
return lowered;
|
|
3723
3966
|
});
|
|
3724
|
-
|
|
3967
|
+
if (!needsStateBuild) {
|
|
3968
|
+
return { kind: "arrayLit", elems, ...(spreads.length > 0 ? { spreads } : {}), type, loc };
|
|
3969
|
+
}
|
|
3970
|
+
const out = lowerer.declareHiddenLocal("%arrayLit", type);
|
|
3971
|
+
const outRef = varRef(out.id, type, loc);
|
|
3972
|
+
const body = [
|
|
3973
|
+
{ kind: "varDecl", localId: out.id, init: { kind: "arrayLit", elems: [], type, loc }, loc },
|
|
3974
|
+
];
|
|
3975
|
+
const spreadSet = new Set(spreads);
|
|
3976
|
+
const currentLength = () => ({
|
|
3977
|
+
kind: "arrIntrinsic",
|
|
3978
|
+
method: "length",
|
|
3979
|
+
receiver: outRef,
|
|
3980
|
+
args: [],
|
|
3981
|
+
type: F64,
|
|
3982
|
+
loc,
|
|
3983
|
+
});
|
|
3984
|
+
const nextLength = () => ({
|
|
3985
|
+
kind: "bin",
|
|
3986
|
+
op: "+",
|
|
3987
|
+
left: currentLength(),
|
|
3988
|
+
right: { kind: "numLit", value: 1, type: F64, loc },
|
|
3989
|
+
type: F64,
|
|
3990
|
+
loc,
|
|
3991
|
+
});
|
|
3992
|
+
for (let i = 0; i < elems.length; i++) {
|
|
3993
|
+
const el = elems[i];
|
|
3994
|
+
if (spreadSet.has(i)) {
|
|
3995
|
+
body.push({
|
|
3996
|
+
kind: "exprStmt",
|
|
3997
|
+
expr: { kind: "arrIntrinsic", method: "pushSpread", receiver: outRef, args: [el], type: F64, loc },
|
|
3998
|
+
loc,
|
|
3999
|
+
});
|
|
4000
|
+
continue;
|
|
4001
|
+
}
|
|
4002
|
+
if (holes.has(i)) {
|
|
4003
|
+
body.push({ kind: "arraySetLength", arr: outRef, length: nextLength(), loc });
|
|
4004
|
+
continue;
|
|
4005
|
+
}
|
|
4006
|
+
let value = el;
|
|
4007
|
+
if (!typeEquals(value.type, type.elem)) {
|
|
4008
|
+
const temp = lowerer.declareHiddenLocal("%arrayValue", value.type);
|
|
4009
|
+
body.push({ kind: "varDecl", localId: temp.id, init: value, loc });
|
|
4010
|
+
value = varRef(temp.id, temp.type, loc);
|
|
4011
|
+
}
|
|
4012
|
+
body.push(arrayValueStore(lowerer, outRef, currentLength(), value, type.elem, loc));
|
|
4013
|
+
}
|
|
4014
|
+
return {
|
|
4015
|
+
kind: "seqExpr",
|
|
4016
|
+
stmts: body,
|
|
4017
|
+
result: outRef,
|
|
4018
|
+
type,
|
|
4019
|
+
loc,
|
|
4020
|
+
};
|
|
3725
4021
|
}
|
|
3726
4022
|
/** `{ a: 1, b: "x" }` → recordLit. The record type comes from the
|
|
3727
4023
|
* contextual type when tsc has one (annotated declarations, arguments,
|
|
@@ -4032,6 +4328,42 @@ function fenceAccessorSpreadSource(lowerer, prop, srcShape) {
|
|
|
4032
4328
|
lowerer.unsupported("SC1090", prop, "object spread of sources carrying get/set accessor properties (Node invokes each getter once during the copy — the field-copy desugar cannot model the calls; bind the reads to consts first)");
|
|
4033
4329
|
}
|
|
4034
4330
|
}
|
|
4331
|
+
/** Convert an undefined-armed numeric value at an arithmetic use. Ordinary
|
|
4332
|
+
* reads preserve their tagged value; JavaScript's ToNumber(undefined) is NaN,
|
|
4333
|
+
* so compounds and binary arithmetic use this checked one-time extraction. */
|
|
4334
|
+
export function lowerOptionalNumber(lowerer, operand, loc, narrowedNode) {
|
|
4335
|
+
if (operand.type.kind !== "union" || lowerer.armTag(operand.type.unionId, UNDEFINED_T) < 0)
|
|
4336
|
+
return operand;
|
|
4337
|
+
const directNumber = lowerer.stripUndefinedArm(operand.type).kind === "f64";
|
|
4338
|
+
const checkerNumber = narrowedNode !== undefined && lowerer.mapTypeOf(lowerer.typeOf(narrowedNode))?.kind === "f64";
|
|
4339
|
+
if (!directNumber && (!checkerNumber || lowerer.armTag(operand.type.unionId, F64) < 0))
|
|
4340
|
+
return operand;
|
|
4341
|
+
const undefTag = lowerer.armTag(operand.type.unionId, UNDEFINED_T);
|
|
4342
|
+
const valueTag = lowerer.armTag(operand.type.unionId, F64);
|
|
4343
|
+
if (undefTag < 0 || valueTag < 0)
|
|
4344
|
+
return operand;
|
|
4345
|
+
const checked = directNumber ? null : lowerer.narrowedArmHelper(operand.type.unionId, F64, loc);
|
|
4346
|
+
if (!directNumber && !checked)
|
|
4347
|
+
return operand;
|
|
4348
|
+
let stable = operand;
|
|
4349
|
+
let prefix = [];
|
|
4350
|
+
if (!pureReemittable(operand)) {
|
|
4351
|
+
const tmp = lowerer.declareHiddenLocal("%arith", operand.type);
|
|
4352
|
+
stable = varRef(tmp.id, operand.type, loc);
|
|
4353
|
+
prefix = [{ kind: "varDecl", localId: tmp.id, init: operand, loc }];
|
|
4354
|
+
}
|
|
4355
|
+
const value = {
|
|
4356
|
+
kind: "ternary",
|
|
4357
|
+
cond: { kind: "unionIsTag", unionId: operand.type.unionId, tag: undefTag, negated: false, value: stable, type: BOOL, loc },
|
|
4358
|
+
then: { kind: "bin", op: "/", left: numLit(0, loc), right: numLit(0, loc), type: F64, loc },
|
|
4359
|
+
else_: checked
|
|
4360
|
+
? { kind: "call", callee: checked, args: [stable], type: F64, loc }
|
|
4361
|
+
: { kind: "unionNarrow", unionId: operand.type.unionId, tag: valueTag, value: stable, type: F64, loc },
|
|
4362
|
+
type: F64,
|
|
4363
|
+
loc,
|
|
4364
|
+
};
|
|
4365
|
+
return prefix.length === 0 ? value : { kind: "seqExpr", stmts: prefix, result: value, type: F64, loc };
|
|
4366
|
+
}
|
|
4035
4367
|
/** The JS trap-closure fallback for FUNCTION-VALUED object properties: a
|
|
4036
4368
|
* lambda whose body fails to lower inside a JS object literal becomes a
|
|
4037
4369
|
* closure of the field's exact func type whose body is the runtime fence —
|
|
@@ -4179,6 +4511,39 @@ function literalUnionArmOf(lowerer, expr, tsType, recordArms) {
|
|
|
4179
4511
|
const [only] = candidates;
|
|
4180
4512
|
return recordArms.find((a) => a.shapeId === only) ?? null;
|
|
4181
4513
|
}
|
|
4514
|
+
/** Object spread treats nullish sources as empty. Preserve evaluation once,
|
|
4515
|
+
* then turn `Record<K, V> | undefined` into either that record or a fresh
|
|
4516
|
+
* empty record of the same represented shape. */
|
|
4517
|
+
function lowerOptionalIndexSpreadSource(lowerer, source, loc) {
|
|
4518
|
+
if (source.type.kind !== "union")
|
|
4519
|
+
return null;
|
|
4520
|
+
const def = lowerer.unions.get(source.type.unionId);
|
|
4521
|
+
if (!def || def.arms.length !== 2)
|
|
4522
|
+
return null;
|
|
4523
|
+
const undefinedTag = def.arms.findIndex((arm) => arm.kind === "undefinedT");
|
|
4524
|
+
const recordTag = def.arms.findIndex((arm) => arm.kind === "record");
|
|
4525
|
+
if (undefinedTag < 0 || recordTag < 0)
|
|
4526
|
+
return null;
|
|
4527
|
+
const recordType = def.arms[recordTag];
|
|
4528
|
+
if (recordType.kind !== "record")
|
|
4529
|
+
return null;
|
|
4530
|
+
const prefix = [];
|
|
4531
|
+
let stable = source;
|
|
4532
|
+
if (!pureReemittable(source)) {
|
|
4533
|
+
const local = lowerer.declareHiddenLocal("%spread", source.type);
|
|
4534
|
+
prefix.push({ kind: "varDecl", localId: local.id, init: source, loc });
|
|
4535
|
+
stable = varRef(local.id, source.type, loc);
|
|
4536
|
+
}
|
|
4537
|
+
const selected = {
|
|
4538
|
+
kind: "ternary",
|
|
4539
|
+
cond: { kind: "unionIsTag", unionId: source.type.unionId, tag: undefinedTag, negated: false, value: stable, type: BOOL, loc },
|
|
4540
|
+
then: { kind: "recordLit", fields: [], type: recordType, loc },
|
|
4541
|
+
else_: { kind: "unionNarrow", unionId: source.type.unionId, tag: recordTag, value: stable, type: recordType, loc },
|
|
4542
|
+
type: recordType,
|
|
4543
|
+
loc,
|
|
4544
|
+
};
|
|
4545
|
+
return prefix.length === 0 ? selected : { kind: "seqExpr", stmts: prefix, result: selected, type: recordType, loc };
|
|
4546
|
+
}
|
|
4182
4547
|
export function lowerObjectLiteral(lowerer, expr) {
|
|
4183
4548
|
const loc = locOf(expr);
|
|
4184
4549
|
// The RUNTIME-KEYED literal (JS): a computed key that doesn't fold to a
|
|
@@ -4464,6 +4829,23 @@ export function lowerObjectLiteral(lowerer, expr) {
|
|
|
4464
4829
|
}
|
|
4465
4830
|
}
|
|
4466
4831
|
const fieldTypes = new Map(shape.fields.map((f) => [f.name, f.type]));
|
|
4832
|
+
// File-scope JavaScript object bindings live in the checked-dynamic tree
|
|
4833
|
+
// to preserve open writes and identity. Build an optional-field literal
|
|
4834
|
+
// from its actually written keys so Object.hasOwn can distinguish an
|
|
4835
|
+
// omitted property from a present property whose value is undefined.
|
|
4836
|
+
const topLevelJsDecl = ts.isVariableDeclaration(expr.parent) &&
|
|
4837
|
+
expr.parent.initializer === expr &&
|
|
4838
|
+
ts.isVariableStatement(expr.parent.parent.parent) &&
|
|
4839
|
+
ts.isSourceFile(expr.parent.parent.parent.parent);
|
|
4840
|
+
const assignedProperties = expr.properties.filter((prop) => ts.isPropertyAssignment(prop) || ts.isShorthandPropertyAssignment(prop));
|
|
4841
|
+
if (topLevelJsDecl && isJsSourceFile(expr.getSourceFile()) &&
|
|
4842
|
+
assignedProperties.length === expr.properties.length) {
|
|
4843
|
+
const provided = new Set(assignedProperties.map((prop) => propNameText(lowerer, prop.name)));
|
|
4844
|
+
const omittedOptional = shape.fields.some((field) => !provided.has(field.name) && field.type.kind === "union" &&
|
|
4845
|
+
lowerer.armTag(field.type.unionId, UNDEFINED_T) >= 0);
|
|
4846
|
+
if (omittedOptional)
|
|
4847
|
+
return lowerDynObjectLiteral(lowerer, expr);
|
|
4848
|
+
}
|
|
4467
4849
|
// The clone probe may lower the leading spread before declining (a
|
|
4468
4850
|
// static `as` cast can make the checker-visible shape match while its
|
|
4469
4851
|
// erased IR value keeps a wider shape). Reuse that exact value in the
|
|
@@ -4575,7 +4957,8 @@ export function lowerObjectLiteral(lowerer, expr) {
|
|
|
4575
4957
|
});
|
|
4576
4958
|
continue;
|
|
4577
4959
|
}
|
|
4578
|
-
|
|
4960
|
+
let src = lowerer.lowerExpr(prop.expression);
|
|
4961
|
+
src = lowerOptionalIndexSpreadSource(lowerer, src, locOf(prop)) ?? src;
|
|
4579
4962
|
if (src.type.kind !== "record") {
|
|
4580
4963
|
lowerer.unsupported("SC1090", prop, `object spread of '${lowerer.fmt(src.type)}' sources into an index-signature shape (only index-signature records merge — ${NARROW_FIRST})`);
|
|
4581
4964
|
}
|
|
@@ -4658,7 +5041,7 @@ export function lowerObjectLiteral(lowerer, expr) {
|
|
|
4658
5041
|
}
|
|
4659
5042
|
const shapeMismatch = (node) => {
|
|
4660
5043
|
const own = lowerer.mapTypeOf(lowerer.typeOf(expr));
|
|
4661
|
-
lowerer.pushDiag(recordShapeMismatchDiag(lowerer.fmt(type), own ? lowerer.fmt(own) : lowerer.checker.typeToString(lowerer.typeOf(expr)), locOf(node), own?.kind === "record"
|
|
5044
|
+
lowerer.pushDiag(recordShapeMismatchDiag(lowerer.fmt(type), own ? lowerer.fmt(own) : lowerer.checker.typeToString(lowerer.typeOf(expr)), locOf(node), own?.kind === "record" && type.kind === "record"
|
|
4662
5045
|
? (lowerer.describeRecordWidthBlocker(own.shapeId, type.shapeId) ?? undefined)
|
|
4663
5046
|
: undefined));
|
|
4664
5047
|
throw new PoisonError();
|
|
@@ -5035,7 +5418,7 @@ export function lowerObjectLiteral(lowerer, expr) {
|
|
|
5035
5418
|
continue;
|
|
5036
5419
|
}
|
|
5037
5420
|
const name = propNameText(lowerer, prop.name); // key-form-checked above
|
|
5038
|
-
|
|
5421
|
+
let fieldType = fieldTypes.get(name);
|
|
5039
5422
|
// An undeclared name against an index-signature shape is an OVERFLOW
|
|
5040
5423
|
// entry (tsc typechecked it against the signature's value type);
|
|
5041
5424
|
// against a plain shape it is the width mismatch it always was —
|
|
@@ -5155,6 +5538,14 @@ export function lowerObjectLiteral(lowerer, expr) {
|
|
|
5155
5538
|
fields.push({ name, value: slotted, overflow: true });
|
|
5156
5539
|
continue;
|
|
5157
5540
|
}
|
|
5541
|
+
const promotedField = fieldType ? lowerer.runtimeOptionalWidening(value.type, fieldType) : null;
|
|
5542
|
+
if (promotedField && fieldType) {
|
|
5543
|
+
type = lowerer.runtimeOptionalRecordField(type, name, promotedField);
|
|
5544
|
+
if (type.kind === "record")
|
|
5545
|
+
shape = lowerer.shapes.get(type.shapeId);
|
|
5546
|
+
fieldTypes.set(name, promotedField);
|
|
5547
|
+
fieldType = promotedField;
|
|
5548
|
+
}
|
|
5158
5549
|
value = lowerer.coerceInto(valueNode, value, fieldType); // union-typed fields wrap arm values
|
|
5159
5550
|
if (!typeEquals(value.type, fieldType))
|
|
5160
5551
|
lowerer.badType(valueNode, lowerer.typeOf(valueNode));
|
|
@@ -5592,7 +5983,16 @@ export function lowerElementAccess(lowerer, expr) {
|
|
|
5592
5983
|
// result; any invalid index traps (the array discipline; JS would
|
|
5593
5984
|
// read undefined — divergence 4's policy).
|
|
5594
5985
|
if (receiverIr?.kind === "bytes") {
|
|
5595
|
-
|
|
5986
|
+
let recv = lowerer.lowerExpr(expr.expression);
|
|
5987
|
+
if (recv.type.kind === "union" && lowerer.armTag(recv.type.unionId, UNDEFINED_T) >= 0) {
|
|
5988
|
+
const present = lowerer.stripUndefinedArm(recv.type);
|
|
5989
|
+
const helper = present.kind === "bytes"
|
|
5990
|
+
? lowerer.narrowedArmHelper(recv.type.unionId, present, locOf(expr.expression))
|
|
5991
|
+
: null;
|
|
5992
|
+
recv = helper
|
|
5993
|
+
? { kind: "call", callee: helper, args: [recv], type: present, loc: locOf(expr.expression) }
|
|
5994
|
+
: lowerer.maybeNarrow(recv, expr.expression);
|
|
5995
|
+
}
|
|
5596
5996
|
// A typed-array .d.ts surface whose VALUE is an island handle
|
|
5597
5997
|
// (`data()[0]` on a declared Uint8Array return — call results only
|
|
5598
5998
|
// exit primitives eagerly): the engine element read, exiting at the
|
|
@@ -5612,7 +6012,7 @@ export function lowerElementAccess(lowerer, expr) {
|
|
|
5612
6012
|
if (receiverIr?.kind === "record") {
|
|
5613
6013
|
const shape = lowerer.shapes.get(receiverIr.shapeId);
|
|
5614
6014
|
if (shape?.tuple) {
|
|
5615
|
-
|
|
6015
|
+
let obj = lowerer.lowerExpr(expr.expression);
|
|
5616
6016
|
// A tuple-typed .d.ts surface whose VALUE is an island handle
|
|
5617
6017
|
// (`pair()[0]` on a declared `[number, string]` return — the
|
|
5618
6018
|
// anonymous tuple type has no npm symbol, so the checker maps it
|
|
@@ -5621,22 +6021,38 @@ export function lowerElementAccess(lowerer, expr) {
|
|
|
5621
6021
|
// like the array path.
|
|
5622
6022
|
if (obj.type.kind === "jsval")
|
|
5623
6023
|
return islandElementRead(lowerer, expr, obj);
|
|
6024
|
+
if (obj.type.kind === "union" && lowerer.armTag(obj.type.unionId, UNDEFINED_T) >= 0) {
|
|
6025
|
+
// The checker sees the outer `arrays[i]` as a tuple when
|
|
6026
|
+
// noUncheckedIndexedAccess is disabled, but the lowered read is
|
|
6027
|
+
// still tuple | undefined. A nested tuple field must validate the
|
|
6028
|
+
// outer tag before recordGet; an unchecked unionNarrow would let a
|
|
6029
|
+
// missing outer element reach the record backend as a bad pointer.
|
|
6030
|
+
const present = lowerer.stripUndefinedArm(obj.type);
|
|
6031
|
+
const helper = present.kind === "record"
|
|
6032
|
+
? lowerer.narrowedArmHelper(obj.type.unionId, present, locOf(expr.expression))
|
|
6033
|
+
: null;
|
|
6034
|
+
obj = helper
|
|
6035
|
+
? { kind: "call", callee: helper, args: [obj], type: present, loc: locOf(expr.expression) }
|
|
6036
|
+
: lowerer.maybeNarrow(obj, expr.expression);
|
|
6037
|
+
}
|
|
5624
6038
|
const idx = tupleLiteralIndex(expr.argumentExpression);
|
|
5625
6039
|
if (idx === null) {
|
|
5626
6040
|
lowerer.unsupported("SC1090", expr.argumentExpression, "tuple indexing with a non-literal index (tuples are fixed-shape — use t[0], t[1], ...)");
|
|
5627
6041
|
}
|
|
5628
|
-
const
|
|
6042
|
+
const shapeId = obj.type.kind === "record" ? obj.type.shapeId : receiverIr.shapeId;
|
|
6043
|
+
const actualShape = lowerer.shapes.get(shapeId) ?? shape;
|
|
6044
|
+
const fieldType = actualShape.fields.find((f) => f.name === String(idx))?.type;
|
|
5629
6045
|
if (fieldType === undefined)
|
|
5630
6046
|
lowerer.badType(expr, lowerer.typeOf(expr)); // OOB smuggled past tsc
|
|
5631
6047
|
const get = {
|
|
5632
6048
|
kind: "recordGet",
|
|
5633
6049
|
obj,
|
|
5634
|
-
shapeId
|
|
6050
|
+
shapeId,
|
|
5635
6051
|
field: String(idx),
|
|
5636
6052
|
type: fieldType,
|
|
5637
6053
|
loc: locOf(expr),
|
|
5638
6054
|
};
|
|
5639
|
-
return lowerer.maybeNarrow(get, expr);
|
|
6055
|
+
return lowerer.isRuntimeOptionalField(shapeId, String(idx)) ? get : lowerer.maybeNarrow(get, expr);
|
|
5640
6056
|
}
|
|
5641
6057
|
if (shape && !shape.tuple) {
|
|
5642
6058
|
// A record-typed .d.ts surface whose VALUE is an island handle
|
|
@@ -5751,6 +6167,65 @@ export function lowerElementAccess(lowerer, expr) {
|
|
|
5751
6167
|
lowerer.unsupported("SC1090", expr, "element access on non-array values");
|
|
5752
6168
|
}
|
|
5753
6169
|
let arr = lowerer.lowerExpr(expr.expression);
|
|
6170
|
+
const runtimeOptionalSource = lowerer.runtimeOptionalSourceValue(expr.expression, arr);
|
|
6171
|
+
if (runtimeOptionalSource?.kind === "varRef" &&
|
|
6172
|
+
runtimeOptionalSource.type.kind === "union" &&
|
|
6173
|
+
arr.type.kind === "array") {
|
|
6174
|
+
// lowerer.lowerElementAccess has already recognized a captured
|
|
6175
|
+
// array | undefined receiver and supplied its checked extraction as
|
|
6176
|
+
// the expression override. Rebuild that check here so a missing
|
|
6177
|
+
// receiver throws the member-access error Node exposes, rather than
|
|
6178
|
+
// the generic target-union narrowing error. The stored receiver is a
|
|
6179
|
+
// varRef, so both the tag test and extraction read the same value
|
|
6180
|
+
// without repeating an effect.
|
|
6181
|
+
const stored = runtimeOptionalSource;
|
|
6182
|
+
if (stored.type.kind !== "union")
|
|
6183
|
+
throw new InternalCompilerError("runtime-optional array source lost its union storage");
|
|
6184
|
+
const unionId = stored.type.unionId;
|
|
6185
|
+
const undefTag = lowerer.armTag(unionId, UNDEFINED_T);
|
|
6186
|
+
const valueTag = lowerer.armTag(unionId, arr.type);
|
|
6187
|
+
const property = runtimeOptionalElementKey(expr.argumentExpression);
|
|
6188
|
+
if (undefTag >= 0 && valueTag >= 0 && property !== null) {
|
|
6189
|
+
arr = {
|
|
6190
|
+
kind: "ternary",
|
|
6191
|
+
cond: {
|
|
6192
|
+
kind: "unionIsTag",
|
|
6193
|
+
unionId,
|
|
6194
|
+
tag: undefTag,
|
|
6195
|
+
negated: false,
|
|
6196
|
+
value: stored,
|
|
6197
|
+
type: BOOL,
|
|
6198
|
+
loc: locOf(expr.expression),
|
|
6199
|
+
},
|
|
6200
|
+
then: nodeThrowExpr(1, "", `Cannot read properties of undefined (reading '${property}')`, arr.type, locOf(expr.expression)),
|
|
6201
|
+
else_: {
|
|
6202
|
+
kind: "unionNarrow",
|
|
6203
|
+
unionId,
|
|
6204
|
+
tag: valueTag,
|
|
6205
|
+
value: stored,
|
|
6206
|
+
type: arr.type,
|
|
6207
|
+
loc: locOf(expr.expression),
|
|
6208
|
+
},
|
|
6209
|
+
type: arr.type,
|
|
6210
|
+
loc: locOf(expr.expression),
|
|
6211
|
+
};
|
|
6212
|
+
}
|
|
6213
|
+
}
|
|
6214
|
+
if (arr.type.kind === "union" &&
|
|
6215
|
+
lowerer.armTag(arr.type.unionId, UNDEFINED_T) >= 0 &&
|
|
6216
|
+
receiverIr.kind === "array") {
|
|
6217
|
+
// As with tuple receivers above, an unchecked outer read can be
|
|
6218
|
+
// undefined even when the checker reports an array. Extract the
|
|
6219
|
+
// present array arm with a runtime tag check before arrayGet or the
|
|
6220
|
+
// safe indexed-read helper runs.
|
|
6221
|
+
const present = lowerer.stripUndefinedArm(arr.type);
|
|
6222
|
+
const helper = present.kind === "array"
|
|
6223
|
+
? lowerer.narrowedArmHelper(arr.type.unionId, present, locOf(expr.expression))
|
|
6224
|
+
: null;
|
|
6225
|
+
arr = helper
|
|
6226
|
+
? { kind: "call", callee: helper, args: [arr], type: present, loc: locOf(expr.expression) }
|
|
6227
|
+
: lowerer.maybeNarrow(arr, expr.expression);
|
|
6228
|
+
}
|
|
5754
6229
|
// The checker sees an array but the VALUE is an island handle (an
|
|
5755
6230
|
// array-typed .d.ts surface: `parts()[0]` on a declared `string[]`
|
|
5756
6231
|
// return, `issue.path[0]` on a declared member — arrays never exit
|
|
@@ -5782,12 +6257,22 @@ export function lowerElementAccess(lowerer, expr) {
|
|
|
5782
6257
|
// signature where the .d.ts says Function[], which would flatten the
|
|
5783
6258
|
// element to a zero-parameter closure).
|
|
5784
6259
|
const elemT = arr.type.kind === "array" ? arr.type.elem : receiverIr.elem;
|
|
5785
|
-
//
|
|
5786
|
-
//
|
|
5787
|
-
//
|
|
5788
|
-
//
|
|
5789
|
-
//
|
|
5790
|
-
|
|
6260
|
+
// Every ordinary array read is a JavaScript property read: an invalid
|
|
6261
|
+
// index answers undefined, even when the checker was built without
|
|
6262
|
+
// noUncheckedIndexedAccess. Keep the explicit optional union in the IR
|
|
6263
|
+
// so it can flow through templates, locals, calls, returns, operators,
|
|
6264
|
+
// and narrowing. The helper performs one length/proof check and then
|
|
6265
|
+
// uses the dense getter for the proven-present path, so this does not
|
|
6266
|
+
// change the storage representation or the low-level getter itself.
|
|
6267
|
+
if (arr.type.kind === "array") {
|
|
6268
|
+
// A non-null assertion is the explicit proven-present form. Preserve
|
|
6269
|
+
// the dense getter's established bounds trap here; turning `xs[i]!`
|
|
6270
|
+
// into an optional union would change library ABI checks and the
|
|
6271
|
+
// existing catchable RangeError contract for an out-of-bounds assert.
|
|
6272
|
+
const provenPresent = ts.isNonNullExpression(expr.parent) && expr.parent.expression === expr;
|
|
6273
|
+
if (provenPresent) {
|
|
6274
|
+
return lowerer.maybeNarrow({ kind: "arrayGet", arr, index, type: elemT, loc: locOf(expr) }, expr);
|
|
6275
|
+
}
|
|
5791
6276
|
const safe = lowerSafeIndexRead(lowerer, arr, index, locOf(expr));
|
|
5792
6277
|
if (safe)
|
|
5793
6278
|
return safe;
|
|
@@ -5807,16 +6292,6 @@ export function lowerElementAccess(lowerer, expr) {
|
|
|
5807
6292
|
// narrows literal-index element accesses).
|
|
5808
6293
|
return lowerer.maybeNarrow({ kind: "arrayGet", arr, index, type: elemT, loc: locOf(expr) }, expr);
|
|
5809
6294
|
}
|
|
5810
|
-
function isFreshArrayProbe(node) {
|
|
5811
|
-
let current = node;
|
|
5812
|
-
while (ts.isParenthesizedExpression(current))
|
|
5813
|
-
current = current.expression;
|
|
5814
|
-
return (ts.isCallExpression(current) &&
|
|
5815
|
-
ts.isPropertyAccessExpression(current.expression) &&
|
|
5816
|
-
(current.expression.name.text === "filter" ||
|
|
5817
|
-
current.expression.name.text === "slice" ||
|
|
5818
|
-
current.expression.name.text === "sort"));
|
|
5819
|
-
}
|
|
5820
6295
|
/** `o[k]` where the RECEIVER is an island value — a jsval-mapped checker
|
|
5821
6296
|
* type, or an array/tuple-typed .d.ts surface whose lowered value is a
|
|
5822
6297
|
* handle. The key marshals in (any JS key kind — the engine does its
|
|
@@ -5883,8 +6358,7 @@ export function lowerRecordKeyRead(lowerer, expr, shapeId, shape, includeUndefin
|
|
|
5883
6358
|
// Number AND checked-dynamic keys ride the JS-exact formatter —
|
|
5884
6359
|
// property keys ARE strings (o[k] is o[String(k)] in JS), and a dyn
|
|
5885
6360
|
// key (agent.sockets[agent.getName(...)]) stringifies the same way.
|
|
5886
|
-
|
|
5887
|
-
dk = lowerer.ensureString(dk, keyNode);
|
|
6361
|
+
dk = lowerRecordPropertyKey(lowerer, dk, keyNode);
|
|
5888
6362
|
if (dk.type.kind !== "string") {
|
|
5889
6363
|
lowerer.unsupported("SC1090", keyNode, "indexing records with non-string or non-number keys");
|
|
5890
6364
|
}
|
|
@@ -5914,8 +6388,7 @@ export function lowerRecordKeyRead(lowerer, expr, shapeId, shape, includeUndefin
|
|
|
5914
6388
|
: lowerer.lowerExpr(keyNode);
|
|
5915
6389
|
// Runtime NUMBER keys ride the JS-exact formatter (o[n] is o[String(n)]
|
|
5916
6390
|
// in JS — the number-keyed-signature access path).
|
|
5917
|
-
|
|
5918
|
-
key = lowerer.ensureString(key, keyNode);
|
|
6391
|
+
key = lowerRecordPropertyKey(lowerer, key, keyNode);
|
|
5919
6392
|
if (key.type.kind !== "string") {
|
|
5920
6393
|
lowerer.unsupported("SC1090", keyNode, "indexing records with non-string or non-number keys");
|
|
5921
6394
|
}
|
|
@@ -5958,6 +6431,16 @@ export function lowerRecordKeyRead(lowerer, expr, shapeId, shape, includeUndefin
|
|
|
5958
6431
|
};
|
|
5959
6432
|
return includeUndefined ? read : lowerer.maybeNarrow(read, expr);
|
|
5960
6433
|
}
|
|
6434
|
+
/** ToPropertyKey for represented record keys. Missing array-derived string
|
|
6435
|
+
* values remain real undefined values, whose JavaScript property spelling
|
|
6436
|
+
* is the string "undefined". */
|
|
6437
|
+
function lowerRecordPropertyKey(lowerer, key, node) {
|
|
6438
|
+
if (key.type.kind === "f64" || key.type.kind === "bool" || key.type.kind === "dyn" ||
|
|
6439
|
+
lowerer.runtimeOptionalWidening(key.type, STRING) !== null) {
|
|
6440
|
+
return lowerer.ensureString(key, node);
|
|
6441
|
+
}
|
|
6442
|
+
return key;
|
|
6443
|
+
}
|
|
5961
6444
|
/** A keyed read used only to observe absence (`if (map[k])`, or the left
|
|
5962
6445
|
* side of `||`/`??`). JavaScript answers undefined for missing record keys
|
|
5963
6446
|
* and array indices even when noUncheckedIndexedAccess is disabled. Keep
|
|
@@ -6229,8 +6712,7 @@ export function lowerElementWrite(lowerer, expr) {
|
|
|
6229
6712
|
let key = litKey !== null
|
|
6230
6713
|
? { kind: "strLit", value: litKey, type: STRING, loc: locOf(target.argumentExpression) }
|
|
6231
6714
|
: lowerer.lowerExpr(target.argumentExpression);
|
|
6232
|
-
|
|
6233
|
-
key = lowerer.ensureString(key, target.argumentExpression);
|
|
6715
|
+
key = lowerRecordPropertyKey(lowerer, key, target.argumentExpression);
|
|
6234
6716
|
if (key.type.kind !== "string") {
|
|
6235
6717
|
lowerer.unsupported("SC1090", target.argumentExpression, "indexing records with non-string or non-number keys");
|
|
6236
6718
|
}
|
|
@@ -6308,11 +6790,7 @@ export function lowerElementWrite(lowerer, expr) {
|
|
|
6308
6790
|
: lowerer.lowerExpr(target.argumentExpression);
|
|
6309
6791
|
// Runtime number/boolean/dyn keys stringify — ToPropertyKey,
|
|
6310
6792
|
// exactly the index-signature path's rule.
|
|
6311
|
-
|
|
6312
|
-
key = lowerer.ensureString(key, target.argumentExpression);
|
|
6313
|
-
if (key.type.kind === "bool" || key.type.kind === "dyn") {
|
|
6314
|
-
key = { kind: "toString", operand: key, type: STRING, loc: locOf(target.argumentExpression) };
|
|
6315
|
-
}
|
|
6793
|
+
key = lowerRecordPropertyKey(lowerer, key, target.argumentExpression);
|
|
6316
6794
|
if (key.type.kind !== "string") {
|
|
6317
6795
|
lowerer.unsupported("SC1090", target.argumentExpression, "indexing records with non-string or non-number keys");
|
|
6318
6796
|
}
|
|
@@ -6341,8 +6819,7 @@ export function lowerElementWrite(lowerer, expr) {
|
|
|
6341
6819
|
: lowerer.lowerExpr(target.argumentExpression);
|
|
6342
6820
|
// Runtime NUMBER keys canonicalize through the JS-exact formatter
|
|
6343
6821
|
// (o[n] = v writes o[String(n)] — the number-keyed-signature path).
|
|
6344
|
-
|
|
6345
|
-
key = lowerer.ensureString(key, target.argumentExpression);
|
|
6822
|
+
key = lowerRecordPropertyKey(lowerer, key, target.argumentExpression);
|
|
6346
6823
|
if (key.type.kind !== "string") {
|
|
6347
6824
|
lowerer.unsupported("SC1090", target.argumentExpression, "indexing records with non-string or non-number keys");
|
|
6348
6825
|
}
|
|
@@ -6376,11 +6853,7 @@ export function lowerElementWrite(lowerer, expr) {
|
|
|
6376
6853
|
let key = litKey !== null
|
|
6377
6854
|
? { kind: "strLit", value: litKey, type: STRING, loc: locOf(target.argumentExpression) }
|
|
6378
6855
|
: lowerer.lowerExpr(target.argumentExpression);
|
|
6379
|
-
|
|
6380
|
-
key = lowerer.ensureString(key, target.argumentExpression);
|
|
6381
|
-
if (key.type.kind === "bool" || key.type.kind === "dyn") {
|
|
6382
|
-
key = { kind: "toString", operand: key, type: STRING, loc: locOf(target.argumentExpression) };
|
|
6383
|
-
}
|
|
6856
|
+
key = lowerRecordPropertyKey(lowerer, key, target.argumentExpression);
|
|
6384
6857
|
if (key.type.kind !== "string") {
|
|
6385
6858
|
lowerer.unsupported("SC1090", target.argumentExpression, "indexing checked-dynamic values with non-string or non-number keys");
|
|
6386
6859
|
}
|
|
@@ -6394,7 +6867,7 @@ export function lowerElementWrite(lowerer, expr) {
|
|
|
6394
6867
|
if (receiverIr?.kind !== "array") {
|
|
6395
6868
|
lowerer.unsupported("SC1090", target, "assignment to non-array elements");
|
|
6396
6869
|
}
|
|
6397
|
-
|
|
6870
|
+
let arr = lowerer.lowerExpr(target.expression);
|
|
6398
6871
|
// The write twin of the island element read: an array-typed .d.ts
|
|
6399
6872
|
// surface whose VALUE is a handle takes the engine keyed write, never
|
|
6400
6873
|
// a static arraySet over a jsval.
|
|
@@ -6404,17 +6877,29 @@ export function lowerElementWrite(lowerer, expr) {
|
|
|
6404
6877
|
const loc = locOf(expr);
|
|
6405
6878
|
return { kind: "exprStmt", expr: { kind: "jsOp", op: "setIdx", args: [arr, key, value], type: VOID, loc }, loc };
|
|
6406
6879
|
}
|
|
6880
|
+
if (arr.type.kind === "union" &&
|
|
6881
|
+
lowerer.armTag(arr.type.unionId, UNDEFINED_T) >= 0 &&
|
|
6882
|
+
receiverIr.kind === "array") {
|
|
6883
|
+
const present = lowerer.stripUndefinedArm(arr.type);
|
|
6884
|
+
const helper = present.kind === "array"
|
|
6885
|
+
? lowerer.narrowedArmHelper(arr.type.unionId, present, locOf(target.expression))
|
|
6886
|
+
: null;
|
|
6887
|
+
if (helper) {
|
|
6888
|
+
arr = { kind: "call", callee: helper, args: [arr], type: present, loc: locOf(target.expression) };
|
|
6889
|
+
}
|
|
6890
|
+
}
|
|
6891
|
+
if (arr.type.kind !== "array") {
|
|
6892
|
+
lowerer.unsupported("SC1090", target.expression, "assignment through a possibly missing nested array receiver");
|
|
6893
|
+
}
|
|
6407
6894
|
const index = lowerer.lowerExpr(target.argumentExpression);
|
|
6408
6895
|
if (index.type.kind !== "f64") {
|
|
6409
6896
|
lowerer.unsupported("SC1090", target.argumentExpression, "indexing with non-number keys");
|
|
6410
6897
|
}
|
|
6411
|
-
//
|
|
6412
|
-
//
|
|
6413
|
-
|
|
6414
|
-
|
|
6415
|
-
|
|
6416
|
-
}
|
|
6417
|
-
return { kind: "arraySet", arr, index, value, loc: locOf(expr) };
|
|
6898
|
+
// Optional array reads carry the source array's payload type plus an
|
|
6899
|
+
// undefined arm. arrayValueStore turns that arm into the runtime's
|
|
6900
|
+
// UNDEFINED state while keeping number[]/string[] payload storage scalar.
|
|
6901
|
+
const value = lowerer.lowerExpr(expr.right);
|
|
6902
|
+
return arrayValueStore(lowerer, arr, index, value, receiverIr.elem, locOf(expr));
|
|
6418
6903
|
}
|
|
6419
6904
|
export function ensureString(lowerer, e, node) {
|
|
6420
6905
|
if (e.type.kind === "string")
|
|
@@ -6717,10 +7202,11 @@ export function lowerPrefixUnary(lowerer, expr) {
|
|
|
6717
7202
|
const loc = locOf(expr);
|
|
6718
7203
|
switch (expr.operator) {
|
|
6719
7204
|
case ts.SyntaxKind.MinusToken: {
|
|
6720
|
-
const
|
|
6721
|
-
if (
|
|
6722
|
-
return { kind: "jsOp", op: "neg", args: [
|
|
7205
|
+
const raw = lowerer.lowerExpr(expr.operand);
|
|
7206
|
+
if (raw.type.kind === "jsval") {
|
|
7207
|
+
return { kind: "jsOp", op: "neg", args: [raw], type: JSVAL, loc };
|
|
6723
7208
|
}
|
|
7209
|
+
const operand = lowerOptionalNumber(lowerer, raw, loc, expr.operand);
|
|
6724
7210
|
if (operand.type.kind !== "f64")
|
|
6725
7211
|
lowerer.unsupported("SC1043", expr);
|
|
6726
7212
|
if (operand.kind === "numLit")
|
|
@@ -6728,16 +7214,17 @@ export function lowerPrefixUnary(lowerer, expr) {
|
|
|
6728
7214
|
return { kind: "unary", op: "-", operand, type: F64, loc };
|
|
6729
7215
|
}
|
|
6730
7216
|
case ts.SyntaxKind.PlusToken: {
|
|
6731
|
-
const
|
|
7217
|
+
const raw = lowerer.lowerExpr(expr.operand);
|
|
6732
7218
|
// Unary + is ToNumber; on an already-number operand it's identity,
|
|
6733
7219
|
// and a STRING operand runs the runtime's ECMA-exact StringToNumber
|
|
6734
7220
|
// (num.fromString — Number(aString)'s lowering, scr_string.c).
|
|
6735
|
-
if (
|
|
6736
|
-
return { kind: "jsOp", op: "plus", args: [
|
|
7221
|
+
if (raw.type.kind === "jsval") {
|
|
7222
|
+
return { kind: "jsOp", op: "plus", args: [raw], type: JSVAL, loc };
|
|
6737
7223
|
}
|
|
6738
|
-
if (
|
|
6739
|
-
return { kind: "libCall", fn: "num.fromString", args: [
|
|
7224
|
+
if (raw.type.kind === "string") {
|
|
7225
|
+
return { kind: "libCall", fn: "num.fromString", args: [raw], type: F64, loc };
|
|
6740
7226
|
}
|
|
7227
|
+
const operand = lowerOptionalNumber(lowerer, raw, loc, expr.operand);
|
|
6741
7228
|
if (operand.type.kind !== "f64")
|
|
6742
7229
|
lowerer.unsupported("SC1043", expr);
|
|
6743
7230
|
return operand;
|
|
@@ -6749,7 +7236,7 @@ export function lowerPrefixUnary(lowerer, expr) {
|
|
|
6749
7236
|
}
|
|
6750
7237
|
case ts.SyntaxKind.TildeToken: {
|
|
6751
7238
|
// `~x`: ToInt32, complement, back to f64 (JS-exact, incl. NaN → -1).
|
|
6752
|
-
const operand = lowerer.lowerExpr(expr.operand);
|
|
7239
|
+
const operand = lowerOptionalNumber(lowerer, lowerer.lowerExpr(expr.operand), loc, expr.operand);
|
|
6753
7240
|
if (operand.type.kind !== "f64")
|
|
6754
7241
|
lowerer.unsupported("SC1043", expr);
|
|
6755
7242
|
return { kind: "unary", op: "~", operand, type: F64, loc };
|
|
@@ -6815,9 +7302,9 @@ function lowerIncDec(lowerer, expr, prefix) {
|
|
|
6815
7302
|
lowerer.unsupported("SC1043", expr);
|
|
6816
7303
|
return { kind: "incDec", op, prefix, localId: target.id, type: F64, loc };
|
|
6817
7304
|
}
|
|
6818
|
-
/** Whether a lowered statement can live inside a seqExpr
|
|
6819
|
-
*
|
|
6820
|
-
*
|
|
7305
|
+
/** Whether a lowered statement can live inside a seqExpr. These statements
|
|
7306
|
+
* do not leave the expression through a jump; the native emitters can place
|
|
7307
|
+
* their writes and local state branches before evaluating the final result.
|
|
6821
7308
|
* Blocks check their bodies recursively. */
|
|
6822
7309
|
function seqExprSafeStmt(s) {
|
|
6823
7310
|
switch (s.kind) {
|
|
@@ -6828,8 +7315,13 @@ function seqExprSafeStmt(s) {
|
|
|
6828
7315
|
case "recordSet":
|
|
6829
7316
|
case "recordKeySet":
|
|
6830
7317
|
case "arraySet":
|
|
7318
|
+
case "arraySetLength":
|
|
6831
7319
|
case "bytesSet":
|
|
7320
|
+
case "arraySetUndefined":
|
|
7321
|
+
case "arrayDelete":
|
|
6832
7322
|
return true;
|
|
7323
|
+
case "if":
|
|
7324
|
+
return s.then.every(seqExprSafeStmt) && (s.else_?.every(seqExprSafeStmt) ?? true);
|
|
6833
7325
|
case "block":
|
|
6834
7326
|
return s.body.every(seqExprSafeStmt);
|
|
6835
7327
|
default:
|
|
@@ -7335,12 +7827,118 @@ export function lowerBinary(lowerer, expr) {
|
|
|
7335
7827
|
type, loc,
|
|
7336
7828
|
};
|
|
7337
7829
|
}
|
|
7338
|
-
|
|
7830
|
+
// An unchecked array read keeps its undefined arm in the IR even when
|
|
7831
|
+
// the checker typed the binding as `string`. An optional string is not
|
|
7832
|
+
// itself a string operand for `+`: `undefined + 1` and
|
|
7833
|
+
// `undefined + undefined` are NaN, while a present string concatenates.
|
|
7834
|
+
// Keep this predicate exact so a nullable string does not silently take
|
|
7835
|
+
// the string path either.
|
|
7836
|
+
const optionalStringType = (type) => {
|
|
7837
|
+
if (type.kind !== "union")
|
|
7838
|
+
return false;
|
|
7839
|
+
const arms = lowerer.unions.get(type.unionId)?.arms;
|
|
7840
|
+
return !!arms && arms.length === 2 &&
|
|
7841
|
+
arms.some((arm) => arm.kind === "string") &&
|
|
7842
|
+
arms.some((arm) => arm.kind === "undefinedT");
|
|
7843
|
+
};
|
|
7844
|
+
const arithmeticNumber = (operand, node) => lowerOptionalNumber(lowerer, operand, loc, node);
|
|
7845
|
+
const arithmeticLeft = strictEquality ? left : arithmeticNumber(left, expr.left);
|
|
7846
|
+
const arithmeticRight = strictEquality ? right : arithmeticNumber(right, expr.right);
|
|
7847
|
+
const plainBothNum = left.type.kind === "f64" && right.type.kind === "f64";
|
|
7848
|
+
const bothNum = arithmeticLeft.type.kind === "f64" && arithmeticRight.type.kind === "f64";
|
|
7339
7849
|
const bothStr = left.type.kind === "string" && right.type.kind === "string";
|
|
7850
|
+
const lowerOptionalStringPlus = () => {
|
|
7851
|
+
const leftOptional = optionalStringType(left.type);
|
|
7852
|
+
const rightOptional = optionalStringType(right.type);
|
|
7853
|
+
if (!leftOptional && !rightOptional)
|
|
7854
|
+
return null;
|
|
7855
|
+
const allowed = (type) => type.kind === "f64" || type.kind === "string" || optionalStringType(type);
|
|
7856
|
+
if (!allowed(left.type) || !allowed(right.type))
|
|
7857
|
+
return null;
|
|
7858
|
+
// The tag tests and the chosen concatenation branch must see the same
|
|
7859
|
+
// operand values. This matters for `take()[0] + 1` and for two reads
|
|
7860
|
+
// with observable getters/calls.
|
|
7861
|
+
const prefix = [];
|
|
7862
|
+
const stable = (operand) => {
|
|
7863
|
+
if (pureReemittable(operand) || droppableStatic(operand))
|
|
7864
|
+
return operand;
|
|
7865
|
+
const tmp = lowerer.declareHiddenLocal("%plus", operand.type);
|
|
7866
|
+
prefix.push({ kind: "varDecl", localId: tmp.id, init: operand, loc });
|
|
7867
|
+
return varRef(tmp.id, operand.type, loc);
|
|
7868
|
+
};
|
|
7869
|
+
const stableLeft = stable(left);
|
|
7870
|
+
const stableRight = stable(right);
|
|
7871
|
+
const leftString = lowerer.ensureString(stableLeft, expr.left);
|
|
7872
|
+
const rightString = lowerer.ensureString(stableRight, expr.right);
|
|
7873
|
+
const concat = { kind: "strConcat", left: leftString, right: rightString, type: STRING, loc };
|
|
7874
|
+
const nan = { kind: "bin", op: "/", left: numLit(0, loc), right: numLit(0, loc), type: F64, loc };
|
|
7875
|
+
const resultT = { kind: "union", unionId: lowerer.unions.intern([F64, STRING]) };
|
|
7876
|
+
lowerer.runtimeOptionalArithmeticTypes.set(expr, resultT);
|
|
7877
|
+
const numberTag = lowerer.armTag(resultT.unionId, F64);
|
|
7878
|
+
const stringTag = lowerer.armTag(resultT.unionId, STRING);
|
|
7879
|
+
if (numberTag < 0 || stringTag < 0)
|
|
7880
|
+
throw new InternalCompilerError("lowerer bug: optional string plus result union");
|
|
7881
|
+
const number = () => ({
|
|
7882
|
+
kind: "unionWrap", unionId: resultT.unionId, tag: numberTag, value: nan, type: resultT, loc,
|
|
7883
|
+
});
|
|
7884
|
+
const string = () => ({
|
|
7885
|
+
kind: "unionWrap", unionId: resultT.unionId, tag: stringTag, value: concat, type: resultT, loc,
|
|
7886
|
+
});
|
|
7887
|
+
const undefinedTest = (operand) => ({
|
|
7888
|
+
kind: "unionIsTag",
|
|
7889
|
+
unionId: operand.type.kind === "union" ? operand.type.unionId : "",
|
|
7890
|
+
tag: operand.type.kind === "union" ? lowerer.armTag(operand.type.unionId, UNDEFINED_T) : -1,
|
|
7891
|
+
negated: false,
|
|
7892
|
+
value: operand,
|
|
7893
|
+
type: BOOL,
|
|
7894
|
+
loc,
|
|
7895
|
+
});
|
|
7896
|
+
let result;
|
|
7897
|
+
if (leftOptional && rightOptional) {
|
|
7898
|
+
const rightMissing = undefinedTest(stableRight);
|
|
7899
|
+
const bothMissing = {
|
|
7900
|
+
kind: "ternary",
|
|
7901
|
+
cond: rightMissing,
|
|
7902
|
+
then: number(),
|
|
7903
|
+
else_: string(),
|
|
7904
|
+
type: resultT,
|
|
7905
|
+
loc,
|
|
7906
|
+
};
|
|
7907
|
+
result = {
|
|
7908
|
+
kind: "ternary",
|
|
7909
|
+
cond: undefinedTest(stableLeft),
|
|
7910
|
+
then: bothMissing,
|
|
7911
|
+
else_: string(),
|
|
7912
|
+
type: resultT,
|
|
7913
|
+
loc,
|
|
7914
|
+
};
|
|
7915
|
+
}
|
|
7916
|
+
else if (leftOptional) {
|
|
7917
|
+
result = {
|
|
7918
|
+
kind: "ternary",
|
|
7919
|
+
cond: undefinedTest(stableLeft),
|
|
7920
|
+
then: number(),
|
|
7921
|
+
else_: string(),
|
|
7922
|
+
type: resultT,
|
|
7923
|
+
loc,
|
|
7924
|
+
};
|
|
7925
|
+
}
|
|
7926
|
+
else {
|
|
7927
|
+
result = {
|
|
7928
|
+
kind: "ternary",
|
|
7929
|
+
cond: undefinedTest(stableRight),
|
|
7930
|
+
then: number(),
|
|
7931
|
+
else_: string(),
|
|
7932
|
+
type: resultT,
|
|
7933
|
+
loc,
|
|
7934
|
+
};
|
|
7935
|
+
}
|
|
7936
|
+
return prefix.length === 0 ? result : { kind: "seqExpr", stmts: prefix, result, type: resultT, loc };
|
|
7937
|
+
};
|
|
7340
7938
|
switch (op) {
|
|
7341
7939
|
case ts.SyntaxKind.PlusToken:
|
|
7342
7940
|
if (bothNum)
|
|
7343
|
-
return { kind: "bin", op: "+", left, right, type: F64, loc };
|
|
7941
|
+
return { kind: "bin", op: "+", left: arithmeticLeft, right: arithmeticRight, type: F64, loc };
|
|
7344
7942
|
if (left.type.kind === "string" || right.type.kind === "string") {
|
|
7345
7943
|
return {
|
|
7346
7944
|
kind: "strConcat",
|
|
@@ -7349,6 +7947,9 @@ export function lowerBinary(lowerer, expr) {
|
|
|
7349
7947
|
type: STRING, loc,
|
|
7350
7948
|
};
|
|
7351
7949
|
}
|
|
7950
|
+
const optionalStringPlus = lowerOptionalStringPlus();
|
|
7951
|
+
if (optionalStringPlus)
|
|
7952
|
+
return optionalStringPlus;
|
|
7352
7953
|
lowerer.unsupported("SC1043", expr);
|
|
7353
7954
|
break;
|
|
7354
7955
|
case ts.SyntaxKind.MinusToken:
|
|
@@ -7362,7 +7963,7 @@ export function lowerBinary(lowerer, expr) {
|
|
|
7362
7963
|
: op === ts.SyntaxKind.AsteriskToken ? "*"
|
|
7363
7964
|
: op === ts.SyntaxKind.SlashToken ? "/"
|
|
7364
7965
|
: op === ts.SyntaxKind.PercentToken ? "%" : "**";
|
|
7365
|
-
return { kind: "bin", op: binOp, left, right, type: F64, loc };
|
|
7966
|
+
return { kind: "bin", op: binOp, left: arithmeticLeft, right: arithmeticRight, type: F64, loc };
|
|
7366
7967
|
}
|
|
7367
7968
|
// The bitwise six, JS-exact: operands through ToInt32/ToUint32
|
|
7368
7969
|
// (NaN/±Infinity → 0, truncate, wrap mod 2^32), the operation in
|
|
@@ -7381,12 +7982,12 @@ export function lowerBinary(lowerer, expr) {
|
|
|
7381
7982
|
: op === ts.SyntaxKind.CaretToken ? "^"
|
|
7382
7983
|
: op === ts.SyntaxKind.LessThanLessThanToken ? "<<"
|
|
7383
7984
|
: op === ts.SyntaxKind.GreaterThanGreaterThanToken ? ">>" : ">>>";
|
|
7384
|
-
return { kind: "bin", op: bitOp, left, right, type: F64, loc };
|
|
7985
|
+
return { kind: "bin", op: bitOp, left: arithmeticLeft, right: arithmeticRight, type: F64, loc };
|
|
7385
7986
|
}
|
|
7386
7987
|
case ts.SyntaxKind.EqualsEqualsEqualsToken:
|
|
7387
7988
|
case ts.SyntaxKind.ExclamationEqualsEqualsToken: {
|
|
7388
7989
|
const negated = op === ts.SyntaxKind.ExclamationEqualsEqualsToken;
|
|
7389
|
-
if (
|
|
7990
|
+
if (plainBothNum)
|
|
7390
7991
|
return { kind: "bin", op: negated ? "!==" : "===", left, right, type: BOOL, loc };
|
|
7391
7992
|
if (bothStr)
|
|
7392
7993
|
return { kind: "strEq", negated, left, right, type: BOOL, loc };
|
|
@@ -7445,6 +8046,38 @@ export function lowerBinary(lowerer, expr) {
|
|
|
7445
8046
|
loc,
|
|
7446
8047
|
};
|
|
7447
8048
|
}
|
|
8049
|
+
if (left.type.kind === "union" && right.type.kind === "union") {
|
|
8050
|
+
const leftDef = lowerer.unions.get(left.type.unionId);
|
|
8051
|
+
const rightDef = lowerer.unions.get(right.type.unionId);
|
|
8052
|
+
if (leftDef && rightDef) {
|
|
8053
|
+
const leftValues = leftDef.arms.filter((arm) => !isUnitType(arm));
|
|
8054
|
+
const rightValues = rightDef.arms.filter((arm) => !isUnitType(arm));
|
|
8055
|
+
const sameValueArms = leftValues.length === rightValues.length &&
|
|
8056
|
+
leftValues.every((arm) => rightValues.some((candidate) => typeEquals(candidate, arm)));
|
|
8057
|
+
if (sameValueArms) {
|
|
8058
|
+
const arms = [...leftDef.arms];
|
|
8059
|
+
for (const arm of rightDef.arms) {
|
|
8060
|
+
if (!arms.some((candidate) => typeEquals(candidate, arm)))
|
|
8061
|
+
arms.push(arm);
|
|
8062
|
+
}
|
|
8063
|
+
arms.sort((a, b) => (typeKey(a) < typeKey(b) ? -1 : 1));
|
|
8064
|
+
const common = {
|
|
8065
|
+
kind: "union",
|
|
8066
|
+
unionId: lowerer.unions.intern(arms),
|
|
8067
|
+
};
|
|
8068
|
+
return {
|
|
8069
|
+
kind: "unionEq",
|
|
8070
|
+
unionId: common.unionId,
|
|
8071
|
+
negated,
|
|
8072
|
+
sameValue: false,
|
|
8073
|
+
left: lowerer.coerceInto(expr.left, left, common),
|
|
8074
|
+
right: lowerer.coerceInto(expr.right, right, common),
|
|
8075
|
+
type: BOOL,
|
|
8076
|
+
loc,
|
|
8077
|
+
};
|
|
8078
|
+
}
|
|
8079
|
+
}
|
|
8080
|
+
}
|
|
7448
8081
|
lowerer.unsupported("SC1090", expr, `comparisons of union-typed values (${NARROW_FIRST})`);
|
|
7449
8082
|
}
|
|
7450
8083
|
// Arrays, maps, functions, class instances and records compare by
|
|
@@ -7509,7 +8142,7 @@ export function lowerBinary(lowerer, expr) {
|
|
|
7509
8142
|
: op === ts.SyntaxKind.LessThanEqualsToken ? "<="
|
|
7510
8143
|
: op === ts.SyntaxKind.GreaterThanToken ? ">" : ">=";
|
|
7511
8144
|
if (bothNum)
|
|
7512
|
-
return { kind: "bin", op: cmpOp, left, right, type: BOOL, loc };
|
|
8145
|
+
return { kind: "bin", op: cmpOp, left: arithmeticLeft, right: arithmeticRight, type: BOOL, loc };
|
|
7513
8146
|
if (bothStr)
|
|
7514
8147
|
return { kind: "strCmp", op: cmpOp, left, right, type: BOOL, loc };
|
|
7515
8148
|
lowerer.unsupported("SC1043", expr);
|
|
@@ -9638,8 +10271,21 @@ export function fieldTarget(lowerer, access) {
|
|
|
9638
10271
|
return null;
|
|
9639
10272
|
}
|
|
9640
10273
|
const fieldType = info.fields.get(access.name.text);
|
|
10274
|
+
const lowerObjectReceiver = () => {
|
|
10275
|
+
let obj = lowerer.lowerExpr(access.expression);
|
|
10276
|
+
if (obj.type.kind === "union" && lowerer.armTag(obj.type.unionId, UNDEFINED_T) >= 0) {
|
|
10277
|
+
const present = lowerer.stripUndefinedArm(obj.type);
|
|
10278
|
+
const helper = present.kind === "object"
|
|
10279
|
+
? lowerer.narrowedArmHelper(obj.type.unionId, present, locOf(access.expression))
|
|
10280
|
+
: null;
|
|
10281
|
+
obj = helper
|
|
10282
|
+
? { kind: "call", callee: helper, args: [obj], type: present, loc: locOf(access.expression) }
|
|
10283
|
+
: lowerer.maybeNarrow(obj, access.expression);
|
|
10284
|
+
}
|
|
10285
|
+
return obj;
|
|
10286
|
+
};
|
|
9641
10287
|
if (fieldType) {
|
|
9642
|
-
const obj =
|
|
10288
|
+
const obj = lowerObjectReceiver();
|
|
9643
10289
|
return { container: "class", obj, className: receiverIr.className, field: access.name.text, fieldType };
|
|
9644
10290
|
}
|
|
9645
10291
|
// Accessor property: either half declared anywhere on the chain
|
|
@@ -9648,7 +10294,7 @@ export function fieldTarget(lowerer, access) {
|
|
|
9648
10294
|
const getF = lowerer.findMethodOn(info, `get:${access.name.text}`);
|
|
9649
10295
|
const setF = lowerer.findMethodOn(info, `set:${access.name.text}`);
|
|
9650
10296
|
if (getF || setF) {
|
|
9651
|
-
const obj =
|
|
10297
|
+
const obj = lowerObjectReceiver();
|
|
9652
10298
|
return {
|
|
9653
10299
|
container: "accessor",
|
|
9654
10300
|
obj,
|
|
@@ -9663,13 +10309,35 @@ export function fieldTarget(lowerer, access) {
|
|
|
9663
10309
|
const shape = lowerer.shapes.get(receiverIr.shapeId);
|
|
9664
10310
|
const fieldType = shape?.fields.find((f) => f.name === access.name.text)?.type;
|
|
9665
10311
|
if (fieldType) {
|
|
9666
|
-
|
|
10312
|
+
let obj = lowerer.lowerExpr(access.expression);
|
|
9667
10313
|
// A checker-record receiver whose VALUE stayed dyn (the erased
|
|
9668
10314
|
// all-unknown-fields cast — `(err as { code?: unknown }).code`):
|
|
9669
10315
|
// decline, and the dyn keyed-read fallback answers.
|
|
10316
|
+
if (obj.type.kind === "union" && lowerer.armTag(obj.type.unionId, UNDEFINED_T) >= 0) {
|
|
10317
|
+
// A nested property read such as `rows[i].value` has the same
|
|
10318
|
+
// unchecked outer array-read issue as tuple indexing. Validate
|
|
10319
|
+
// the record arm before fieldGet/recordGet so a missing outer
|
|
10320
|
+
// value raises a catchable TypeError rather than reaching the
|
|
10321
|
+
// backend with a union receiver.
|
|
10322
|
+
const present = lowerer.stripUndefinedArm(obj.type);
|
|
10323
|
+
const helper = present.kind === "record"
|
|
10324
|
+
? lowerer.narrowedArmHelper(obj.type.unionId, present, locOf(access.expression))
|
|
10325
|
+
: null;
|
|
10326
|
+
obj = helper
|
|
10327
|
+
? { kind: "call", callee: helper, args: [obj], type: present, loc: locOf(access.expression) }
|
|
10328
|
+
: lowerer.maybeNarrow(obj, access.expression);
|
|
10329
|
+
}
|
|
9670
10330
|
if (obj.type.kind !== "record")
|
|
9671
10331
|
return null;
|
|
9672
|
-
|
|
10332
|
+
const actualShape = lowerer.shapes.get(obj.type.shapeId);
|
|
10333
|
+
const actualField = actualShape?.fields.find((f) => f.name === access.name.text);
|
|
10334
|
+
return {
|
|
10335
|
+
container: "record",
|
|
10336
|
+
obj,
|
|
10337
|
+
shapeId: obj.type.shapeId,
|
|
10338
|
+
field: access.name.text,
|
|
10339
|
+
fieldType: actualField?.type ?? fieldType,
|
|
10340
|
+
};
|
|
9673
10341
|
}
|
|
9674
10342
|
// A RECORD accessor property: either slot present makes the name an
|
|
9675
10343
|
// accessor target (get/set share the property namespace — tsc
|