@scriptc/compiler 0.0.29 → 0.0.31
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 +5 -13
- package/dist/backend/emission/emit-async.d.ts +1 -0
- package/dist/backend/emission/emit-async.js +27 -1
- package/dist/backend/emission/emit-async.js.map +1 -1
- package/dist/backend/emission/emit-exprs.js +109 -18
- package/dist/backend/emission/emit-exprs.js.map +1 -1
- package/dist/backend/emission/emitter.d.ts +1 -0
- package/dist/backend/emission/emitter.js +25 -3
- package/dist/backend/emission/emitter.js.map +1 -1
- package/dist/backend/llvm/emitter.js +152 -0
- package/dist/backend/llvm/emitter.js.map +1 -1
- package/dist/backend/mangle.d.ts +1 -0
- package/dist/backend/mangle.js +3 -0
- package/dist/backend/mangle.js.map +1 -1
- package/dist/diagnostics/diagnostic.d.ts +23 -1
- package/dist/diagnostics/diagnostic.js +34 -0
- package/dist/diagnostics/diagnostic.js.map +1 -1
- package/dist/frontend/lowering/lower-calls.d.ts +3 -1
- package/dist/frontend/lowering/lower-calls.js +201 -37
- package/dist/frontend/lowering/lower-calls.js.map +1 -1
- package/dist/frontend/lowering/lower-containers.d.ts +6 -5
- package/dist/frontend/lowering/lower-containers.js +27 -13
- package/dist/frontend/lowering/lower-containers.js.map +1 -1
- package/dist/frontend/lowering/lower-exprs.d.ts +11 -1
- package/dist/frontend/lowering/lower-exprs.js +398 -49
- package/dist/frontend/lowering/lower-exprs.js.map +1 -1
- package/dist/frontend/lowering/lower-server.d.ts +5 -0
- package/dist/frontend/lowering/lower-server.js +105 -55
- package/dist/frontend/lowering/lower-server.js.map +1 -1
- package/dist/frontend/lowering/lower-stmts.js +74 -11
- package/dist/frontend/lowering/lower-stmts.js.map +1 -1
- package/dist/frontend/lowering/lowerer.d.ts +50 -12
- package/dist/frontend/lowering/lowerer.js +84 -15
- package/dist/frontend/lowering/lowerer.js.map +1 -1
- package/dist/index.js +35 -0
- package/dist/index.js.map +1 -1
- package/dist/ir/nodes.d.ts +52 -19
- package/dist/ir/nodes.js +2 -2
- package/dist/ir/nodes.js.map +1 -1
- package/dist/ir/validate.js +67 -9
- package/dist/ir/validate.js.map +1 -1
- package/dist/library/profile.d.ts +33 -0
- package/dist/library/profile.js +161 -3
- package/dist/library/profile.js.map +1 -1
- package/dist/library/sidecar.d.ts +3 -2
- package/dist/library/sidecar.js +7 -3
- package/dist/library/sidecar.js.map +1 -1
- package/package.json +2 -2
- package/surface-manifest.json +1 -1
|
@@ -186,6 +186,8 @@ export declare function finishOptionalChain(L: Lowerer, expr: ts.Expression, id:
|
|
|
186
186
|
* short-circuiting, and mixed operand kinds that have no VALUE
|
|
187
187
|
* representation (`u && flag` — a union and a bool) test fine here. */
|
|
188
188
|
export declare function lowerCondition(L: Lowerer, expr: ts.Expression): IrExpr;
|
|
189
|
+
export declare function runtimeOptionalTrueIds(L: Lowerer, node: ts.Expression): IrLocal[];
|
|
190
|
+
export declare function withRuntimeOptionalNarrowed<T>(L: Lowerer, locals: readonly IrLocal[], fn: () => T): T;
|
|
189
191
|
/** JS ToBoolean: bool passes through; f64/string get a `toBool` wrapper
|
|
190
192
|
* (falsy: 0, -0, NaN, ""); unions get the same wrapper answered by a
|
|
191
193
|
* per-union interned helper (unit arms falsy; scalar/string arms by
|
|
@@ -288,7 +290,15 @@ export declare function lowerElementAccess(L: Lowerer, expr: ts.ElementAccessExp
|
|
|
288
290
|
* fields outside that stay fenced. Declared-only shapes support reads
|
|
289
291
|
* whose key type proves membership (tsc's keyof check): all fields must
|
|
290
292
|
* share the result type, and a smuggled miss traps. */
|
|
291
|
-
export declare function lowerRecordKeyRead(L: Lowerer, expr: ts.ElementAccessExpression, shapeId: string, shape: IrRecordShape): IrExpr;
|
|
293
|
+
export declare function lowerRecordKeyRead(L: Lowerer, expr: ts.ElementAccessExpression, shapeId: string, shape: IrRecordShape, includeUndefined?: boolean): IrExpr;
|
|
294
|
+
/** A keyed read used only to observe absence (`if (map[k])`, or the left
|
|
295
|
+
* side of `||`/`??`). JavaScript answers undefined for missing record keys
|
|
296
|
+
* and array indices even when noUncheckedIndexedAccess is disabled. Keep
|
|
297
|
+
* that value in an explicit union until the surrounding guard/default has
|
|
298
|
+
* consumed it; ordinary unchecked reads retain the typed trap. Assertions
|
|
299
|
+
* around the read are erased here because this context handles the missing
|
|
300
|
+
* arm instead of consuming it as the asserted type. */
|
|
301
|
+
export declare function lowerAbsenceProbe(L: Lowerer, node: ts.Expression): IrExpr | null;
|
|
292
302
|
/** The compile-time string spelling of a record key literal: a string
|
|
293
303
|
* literal's text, or a NON-NEGATIVE numeric literal's canonical JS
|
|
294
304
|
* spelling (`1` → "1", `1e21` → "1e+21" — String(Number(text)), which is
|
|
@@ -12,7 +12,7 @@ import { cjsClassExprWholeExportOf, cjsExportAssignmentOf, cjsExportDiscardReaso
|
|
|
12
12
|
import { ARRAY_METHODS, builtinConstLit, builtinFenceHintOf, builtinModuleConstOf, builtinModulesArrayLit, builtinModuleFnOf, ISLAND_SURFACE, isChildSurfaceMember, MAP_METHODS, NARROW_FIRST, SET_METHODS, STR_METHODS, UNSUPPORTED_EXPR, sideEffectFreeOptionValue, stdlibGlobalNameOf } from "./surfaces.js";
|
|
13
13
|
import { blockedBindingUseDiag, recordShapeMismatchDiag, requiresDynamicPackageDiag, unsupportedDiag } from "../../diagnostics/diagnostic.js";
|
|
14
14
|
import { PoisonError, dynUndefinedExpr, jsFuncNameOf, neverTaintedJsType, nodeThrowExpr, own } from "./lowerer.js";
|
|
15
|
-
import { lowerIndexMergeHelper, lowerNpmStaticSafeIndexRead, strCharsCall } from "./lower-containers.js";
|
|
15
|
+
import { lowerIndexMergeHelper, lowerNpmStaticSafeIndexRead, lowerSafeIndexRead, strCharsCall } from "./lower-containers.js";
|
|
16
16
|
import { npmStaticPackageOfPath } from "../npm-static.js";
|
|
17
17
|
import { unsupportedModuleFeatureOf } from "../shared.js";
|
|
18
18
|
import { fenceEnumObjectValue, lowerEnumAccess } from "./lower-enums.js";
|
|
@@ -204,6 +204,9 @@ function lowerExprInner(L, expr) {
|
|
|
204
204
|
if (ts.isNonNullExpression(expr)) {
|
|
205
205
|
const inner = L.lowerExpr(expr.expression);
|
|
206
206
|
if (inner.type.kind === "union") {
|
|
207
|
+
const use = runtimeOptionalUseOf(expr);
|
|
208
|
+
if (runtimeOptionalAssertionErases(L, expr, inner, use))
|
|
209
|
+
return inner;
|
|
207
210
|
const target = L.mapTypeOf(L.typeOf(expr));
|
|
208
211
|
if (target && target.kind !== "union" && !typeEquals(target, inner.type)) {
|
|
209
212
|
const helper = L.narrowedArmHelper(inner.type.unionId, target, loc);
|
|
@@ -650,6 +653,37 @@ function lowerExprInner(L, expr) {
|
|
|
650
653
|
if (local) {
|
|
651
654
|
if (local.type.kind === "caught")
|
|
652
655
|
return L.caughtRead(expr, local, loc);
|
|
656
|
+
const symbol = L.resolveValueSymbol(expr);
|
|
657
|
+
const runtimeOptionalRoot = L.runtimeOptionalRootOf(local);
|
|
658
|
+
const active = L.runtimeOptionalLocals.has(runtimeOptionalRoot);
|
|
659
|
+
const captured = !!symbol &&
|
|
660
|
+
L.runtimeOptionalStorageLocals.has(runtimeOptionalRoot) &&
|
|
661
|
+
L.ctx.captureBySymbol.get(symbol) === local;
|
|
662
|
+
if (active || captured) {
|
|
663
|
+
const use = runtimeOptionalUseOf(expr);
|
|
664
|
+
if (use?.complex) {
|
|
665
|
+
L.unsupported("SC1090", expr, "a logical or conditional receiver containing a runtime-optional capture");
|
|
666
|
+
}
|
|
667
|
+
if (use?.optional)
|
|
668
|
+
return { kind: "varRef", localId: local.id, type: local.type, loc };
|
|
669
|
+
if (use?.kind === "property")
|
|
670
|
+
return runtimeOptionalReceiverRead(L, expr, local, use.access.name.text, loc);
|
|
671
|
+
if (use?.kind === "element") {
|
|
672
|
+
const key = runtimeOptionalElementKey(use.access.argumentExpression);
|
|
673
|
+
if (key === null) {
|
|
674
|
+
L.unsupported("SC1090", use.access, "a computed read from a runtime-optional capture (use a literal key)");
|
|
675
|
+
}
|
|
676
|
+
return runtimeOptionalReceiverRead(L, expr, local, key, loc);
|
|
677
|
+
}
|
|
678
|
+
if (use?.kind === "call") {
|
|
679
|
+
if (use.comma && !use.optional) {
|
|
680
|
+
L.unsupported("SC1090", use.access, "a direct call through a comma-wrapped runtime-optional capture");
|
|
681
|
+
}
|
|
682
|
+
return runtimeOptionalReceiverRead(L, expr, local, null, loc);
|
|
683
|
+
}
|
|
684
|
+
if (active || captured)
|
|
685
|
+
return { kind: "varRef", localId: local.id, type: local.type, loc };
|
|
686
|
+
}
|
|
653
687
|
return L.maybeNarrow({ kind: "varRef", localId: local.id, type: local.type, loc }, expr);
|
|
654
688
|
}
|
|
655
689
|
// `import x = N.y` aliases resolve transparently through globalOf/
|
|
@@ -877,11 +911,8 @@ function lowerExprInner(L, expr) {
|
|
|
877
911
|
L.noLowering(expr.text, expr, globalHints[expr.text], sym ?? undefined);
|
|
878
912
|
}
|
|
879
913
|
}
|
|
880
|
-
//
|
|
881
|
-
//
|
|
882
|
-
// define mechanism, and neither does Node running the source —
|
|
883
|
-
// the oracle). The read compiles to exactly what Node does at the
|
|
884
|
-
// access: the catchable ReferenceError "<name> is not defined".
|
|
914
|
+
// An ambient global nothing defines compiles to exactly what Node does
|
|
915
|
+
// at the access: the catchable ReferenceError "<name> is not defined".
|
|
885
916
|
// Stdlib/@types/node ambients never reach here (their chokepoint
|
|
886
917
|
// is above); only user-file declares do.
|
|
887
918
|
{
|
|
@@ -1795,9 +1826,7 @@ export function lowerTernary(L, expr, expected) {
|
|
|
1795
1826
|
const constituents = t.isUnionType() ? t.getTypes() : [];
|
|
1796
1827
|
const nonArray = constituents.filter((a) => !L.checker.isArrayType(a) && !L.checker.isTupleType(a));
|
|
1797
1828
|
const mapped = L.mapTypeOf(t);
|
|
1798
|
-
const armCount = mapped?.kind === "union"
|
|
1799
|
-
? (L.unions.get(mapped.unionId)?.arms.filter((a) => a.kind === "array").length ?? 0)
|
|
1800
|
-
: 0;
|
|
1829
|
+
const armCount = mapped?.kind === "union" ? L.arrayValueTags(mapped.unionId).length : 0;
|
|
1801
1830
|
if (sym && nonArray.length === 1 && armCount === 1) {
|
|
1802
1831
|
falseArmNarrowType = nonArray[0];
|
|
1803
1832
|
const collect = (n) => {
|
|
@@ -1913,12 +1942,14 @@ export function lowerTernary(L, expr, expected) {
|
|
|
1913
1942
|
})();
|
|
1914
1943
|
let thenRaw;
|
|
1915
1944
|
let elseRaw;
|
|
1945
|
+
const runtimeTrueIds = runtimeOptionalTrueIds(L, expr.condition);
|
|
1946
|
+
const lowerTrueArm = () => withRuntimeOptionalNarrowed(L, runtimeTrueIds, () => lowerArm(expr.whenTrue, ownArrayJoin ?? undefined));
|
|
1916
1947
|
if (!ctxArray && emptyUntypedArrayArm(expr.whenTrue) && !emptyUntypedArrayArm(expr.whenFalse)) {
|
|
1917
1948
|
elseRaw = L.lowerExpr(expr.whenFalse);
|
|
1918
|
-
thenRaw = lowerArm(expr.whenTrue, elseRaw.type);
|
|
1949
|
+
thenRaw = withRuntimeOptionalNarrowed(L, runtimeTrueIds, () => lowerArm(expr.whenTrue, elseRaw.type));
|
|
1919
1950
|
}
|
|
1920
1951
|
else {
|
|
1921
|
-
thenRaw =
|
|
1952
|
+
thenRaw = lowerTrueArm();
|
|
1922
1953
|
elseRaw = lowerArm(expr.whenFalse, thenRaw.type);
|
|
1923
1954
|
}
|
|
1924
1955
|
// The ternary's IR type is normally the checker's own: it collapses
|
|
@@ -2069,25 +2100,20 @@ export function maybeNarrow(L, expr, node) {
|
|
|
2069
2100
|
}
|
|
2070
2101
|
if (expr.type.kind !== "union")
|
|
2071
2102
|
return expr;
|
|
2072
|
-
const
|
|
2073
|
-
|
|
2074
|
-
//
|
|
2075
|
-
//
|
|
2076
|
-
//
|
|
2077
|
-
//
|
|
2078
|
-
//
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
const
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
const arrayTags = def ? def.arms.flatMap((a, i) => (a.kind === "array" ? [i] : [])) : [];
|
|
2087
|
-
if (arrayTags.length === 1) {
|
|
2088
|
-
const arm = def.arms[arrayTags[0]];
|
|
2089
|
-
return { kind: "unionNarrow", unionId: expr.type.unionId, tag: arrayTags[0], value: expr, type: arm, loc: expr.loc };
|
|
2090
|
-
}
|
|
2103
|
+
const narrowedTs = L.typeOf(node);
|
|
2104
|
+
const narrowed = L.mapTypeOf(narrowedTs);
|
|
2105
|
+
// `Array.isArray(u)` on a union with one readonly array/tuple arm: the
|
|
2106
|
+
// lib predicate narrows to `any[]`, either directly or intersected
|
|
2107
|
+
// with the original union. That checker type can map to the island's
|
|
2108
|
+
// array-of-handles, nothing, or a synthetic intersection record — but
|
|
2109
|
+
// the tag test proved the union's one array-valued arm, so bridge to it
|
|
2110
|
+
// with the same trusted unionNarrow the mapped case below builds.
|
|
2111
|
+
if (L.checkerAnyArrayType(narrowedTs)) {
|
|
2112
|
+
const def = L.unions.get(expr.type.unionId);
|
|
2113
|
+
const arrayTags = L.arrayValueTags(expr.type.unionId);
|
|
2114
|
+
if (arrayTags.length === 1) {
|
|
2115
|
+
const arm = def.arms[arrayTags[0]];
|
|
2116
|
+
return { kind: "unionNarrow", unionId: expr.type.unionId, tag: arrayTags[0], value: expr, type: arm, loc: expr.loc };
|
|
2091
2117
|
}
|
|
2092
2118
|
}
|
|
2093
2119
|
// A checker type narrowed to a UNIT arm (the `=== undefined` branch)
|
|
@@ -2110,6 +2136,109 @@ export function maybeNarrow(L, expr, node) {
|
|
|
2110
2136
|
loc: expr.loc,
|
|
2111
2137
|
};
|
|
2112
2138
|
}
|
|
2139
|
+
/** A runtime-optional slot can be assigned while a truthy guard is in
|
|
2140
|
+
* force. TypeScript then types a direct member receiver as the plain arm,
|
|
2141
|
+
* even though an OOB-safe assignment may have restored undefined. Check
|
|
2142
|
+
* that hidden arm and throw the member-read TypeError Node would produce. */
|
|
2143
|
+
function runtimeOptionalReceiverRead(L, expr, local, property, loc) {
|
|
2144
|
+
if (local.type.kind !== "union")
|
|
2145
|
+
throw new Error("runtime-optional local is not union-typed");
|
|
2146
|
+
const narrowed = L.mapTypeOf(L.typeOf(expr));
|
|
2147
|
+
if (!narrowed || narrowed.kind === "union" || isUnitType(narrowed)) {
|
|
2148
|
+
L.unsupported("SC1090", expr, "a runtime-optional capture whose narrowed value type is not representable");
|
|
2149
|
+
}
|
|
2150
|
+
const valueTag = L.armTag(local.type.unionId, narrowed);
|
|
2151
|
+
const undefTag = L.armTag(local.type.unionId, UNDEFINED_T);
|
|
2152
|
+
if (valueTag < 0 || undefTag < 0)
|
|
2153
|
+
throw new Error("runtime-optional local is missing its value or undefined arm");
|
|
2154
|
+
const def = L.unions.get(local.type.unionId);
|
|
2155
|
+
if (!def || def.arms.length !== 2) {
|
|
2156
|
+
L.unsupported("SC1090", expr, "a direct read from a runtime-optional capture with multiple value arms");
|
|
2157
|
+
}
|
|
2158
|
+
const ref = () => ({ kind: "varRef", localId: local.id, type: local.type, loc });
|
|
2159
|
+
const message = property === null
|
|
2160
|
+
? `${expr.text} is not a function`
|
|
2161
|
+
: `Cannot read properties of undefined (reading '${property}')`;
|
|
2162
|
+
return {
|
|
2163
|
+
kind: "ternary",
|
|
2164
|
+
cond: {
|
|
2165
|
+
kind: "unionIsTag",
|
|
2166
|
+
unionId: local.type.unionId,
|
|
2167
|
+
tag: undefTag,
|
|
2168
|
+
negated: false,
|
|
2169
|
+
value: ref(),
|
|
2170
|
+
type: BOOL,
|
|
2171
|
+
loc,
|
|
2172
|
+
},
|
|
2173
|
+
then: nodeThrowExpr(1, "", message, narrowed, loc),
|
|
2174
|
+
else_: {
|
|
2175
|
+
kind: "unionNarrow",
|
|
2176
|
+
unionId: local.type.unionId,
|
|
2177
|
+
tag: valueTag,
|
|
2178
|
+
value: ref(),
|
|
2179
|
+
type: narrowed,
|
|
2180
|
+
loc,
|
|
2181
|
+
},
|
|
2182
|
+
type: narrowed,
|
|
2183
|
+
loc,
|
|
2184
|
+
};
|
|
2185
|
+
}
|
|
2186
|
+
function runtimeOptionalUseOf(expr) {
|
|
2187
|
+
let receiver = expr;
|
|
2188
|
+
let complex = false;
|
|
2189
|
+
let comma = false;
|
|
2190
|
+
for (;;) {
|
|
2191
|
+
const parent = receiver.parent;
|
|
2192
|
+
if ((ts.isParenthesizedExpression(parent) || ts.isAssertionExpression(parent) ||
|
|
2193
|
+
ts.isSatisfiesExpression(parent) ||
|
|
2194
|
+
ts.isNonNullExpression(parent)) &&
|
|
2195
|
+
parent.expression === receiver) {
|
|
2196
|
+
receiver = parent;
|
|
2197
|
+
continue;
|
|
2198
|
+
}
|
|
2199
|
+
if (ts.isBinaryExpression(parent) && parent.operatorToken.kind === ts.SyntaxKind.CommaToken && parent.right === receiver) {
|
|
2200
|
+
comma = true;
|
|
2201
|
+
receiver = parent;
|
|
2202
|
+
continue;
|
|
2203
|
+
}
|
|
2204
|
+
if ((ts.isBinaryExpression(parent) &&
|
|
2205
|
+
(parent.operatorToken.kind === ts.SyntaxKind.AmpersandAmpersandToken ||
|
|
2206
|
+
parent.operatorToken.kind === ts.SyntaxKind.BarBarToken ||
|
|
2207
|
+
parent.operatorToken.kind === ts.SyntaxKind.QuestionQuestionToken) &&
|
|
2208
|
+
(parent.left === receiver || parent.right === receiver)) ||
|
|
2209
|
+
(ts.isConditionalExpression(parent) &&
|
|
2210
|
+
(parent.condition === receiver || parent.whenTrue === receiver || parent.whenFalse === receiver))) {
|
|
2211
|
+
complex = true;
|
|
2212
|
+
receiver = parent;
|
|
2213
|
+
continue;
|
|
2214
|
+
}
|
|
2215
|
+
break;
|
|
2216
|
+
}
|
|
2217
|
+
const access = receiver.parent;
|
|
2218
|
+
if (ts.isPropertyAccessExpression(access) && access.expression === receiver) {
|
|
2219
|
+
return { kind: "property", access, optional: access.questionDotToken !== undefined, complex, comma };
|
|
2220
|
+
}
|
|
2221
|
+
if (ts.isElementAccessExpression(access) && access.expression === receiver) {
|
|
2222
|
+
return { kind: "element", access, optional: access.questionDotToken !== undefined, complex, comma };
|
|
2223
|
+
}
|
|
2224
|
+
if (ts.isCallExpression(access) && access.expression === receiver) {
|
|
2225
|
+
return { kind: "call", access, optional: access.questionDotToken !== undefined, complex, comma };
|
|
2226
|
+
}
|
|
2227
|
+
return null;
|
|
2228
|
+
}
|
|
2229
|
+
function runtimeOptionalElementKey(expr) {
|
|
2230
|
+
if (ts.isStringLiteral(expr) || ts.isNumericLiteral(expr))
|
|
2231
|
+
return expr.text;
|
|
2232
|
+
if (ts.isNoSubstitutionTemplateLiteral(expr))
|
|
2233
|
+
return expr.text;
|
|
2234
|
+
return null;
|
|
2235
|
+
}
|
|
2236
|
+
function runtimeOptionalAssertionErases(L, expr, inner, use) {
|
|
2237
|
+
if (inner.type.kind !== "union" || !use?.optional || use.complex || L.armTag(inner.type.unionId, UNDEFINED_T) < 0)
|
|
2238
|
+
return false;
|
|
2239
|
+
const target = L.mapTypeOf(L.typeOf(expr));
|
|
2240
|
+
return !!target && target.kind !== "union" && typeEquals(L.stripUndefinedArm(inner.type), target);
|
|
2241
|
+
}
|
|
2113
2242
|
/** `v === undefined` / `v !== null` — the narrowing tests for unit-armed
|
|
2114
2243
|
* unions. A union operand compared with a unit literal lowers to a
|
|
2115
2244
|
* runtime TAG test (unionIsTag); afterwards tsc's control-flow narrowing
|
|
@@ -2374,7 +2503,7 @@ function pureCondExpr(e) {
|
|
|
2374
2503
|
* results (several non-unit arms) and defaults that change the result
|
|
2375
2504
|
* type are fenced with narrow-first hints. */
|
|
2376
2505
|
export function lowerNullishCoalesce(L, expr, loc) {
|
|
2377
|
-
const left = L.lowerExpr(expr.left);
|
|
2506
|
+
const left = lowerAbsenceProbe(L, expr.left) ?? L.lowerExpr(expr.left);
|
|
2378
2507
|
if (left.type.kind === "dyn") {
|
|
2379
2508
|
// `a ?? b` on a CHECKED-DYNAMIC left: the deciding test is the
|
|
2380
2509
|
// runtime kind (scr_dyn_is_nullish — UNDEF/NULL take the default;
|
|
@@ -2850,7 +2979,14 @@ export function finishOptionalChain(L, expr, id, receiver, body, loc) {
|
|
|
2850
2979
|
if (body.type.kind === "dyn") {
|
|
2851
2980
|
return { kind: "optChain", id, receiver, body, type: DYN, loc };
|
|
2852
2981
|
}
|
|
2853
|
-
|
|
2982
|
+
let type = L.irTypeOf(expr);
|
|
2983
|
+
const hiddenOptionalResult = (type.kind !== "union" || L.armTag(type.unionId, UNDEFINED_T) < 0) &&
|
|
2984
|
+
receiver.type.kind === "union" && L.armTag(receiver.type.unionId, UNDEFINED_T) >= 0 &&
|
|
2985
|
+
body.type.kind !== "generator" && body.type.kind !== "jsval"
|
|
2986
|
+
? L.withUndefinedArmOf(body.type)
|
|
2987
|
+
: null;
|
|
2988
|
+
if (hiddenOptionalResult)
|
|
2989
|
+
type = hiddenOptionalResult;
|
|
2854
2990
|
if (type.kind !== "union" || L.armTag(type.unionId, UNDEFINED_T) < 0) {
|
|
2855
2991
|
L.unsupported("SC1090", expr, `'?.' where the result '${L.fmt(type)}' has no undefined arm (narrow the receiver with 'if (x !== undefined)' instead)`);
|
|
2856
2992
|
}
|
|
@@ -2877,7 +3013,18 @@ export function lowerCondition(L, expr) {
|
|
|
2877
3013
|
// (true for &&, false for ||) — aliased-typeof narrows the left
|
|
2878
3014
|
// PROVES under that polarity hold while it lowers (`type ===
|
|
2879
3015
|
// 'string' && val.length > 0`, the ms entry shape).
|
|
2880
|
-
const
|
|
3016
|
+
const runtimeOptionalLocal = isAnd ? runtimeOptionalLocalOf(L, e.left) : null;
|
|
3017
|
+
const right = L.narrowingAliases(aliasTypeofNarrows(L, e.left, isAnd), () => {
|
|
3018
|
+
if (runtimeOptionalLocal === null)
|
|
3019
|
+
return L.lowerCondition(e.right);
|
|
3020
|
+
L.runtimeOptionalLocals.delete(L.runtimeOptionalRootOf(runtimeOptionalLocal));
|
|
3021
|
+
try {
|
|
3022
|
+
return L.lowerCondition(e.right);
|
|
3023
|
+
}
|
|
3024
|
+
finally {
|
|
3025
|
+
L.runtimeOptionalLocals.add(L.runtimeOptionalRootOf(runtimeOptionalLocal));
|
|
3026
|
+
}
|
|
3027
|
+
});
|
|
2881
3028
|
return {
|
|
2882
3029
|
kind: "logical",
|
|
2883
3030
|
op: isAnd ? "&&" : "||",
|
|
@@ -2888,7 +3035,40 @@ export function lowerCondition(L, expr) {
|
|
|
2888
3035
|
};
|
|
2889
3036
|
}
|
|
2890
3037
|
}
|
|
2891
|
-
return L.ensureBool(L.lowerExpr(expr), expr);
|
|
3038
|
+
return L.ensureBool(lowerAbsenceProbe(L, expr) ?? L.lowerExpr(expr), expr);
|
|
3039
|
+
}
|
|
3040
|
+
function runtimeOptionalLocalOf(L, node) {
|
|
3041
|
+
let expr = node;
|
|
3042
|
+
while (ts.isParenthesizedExpression(expr))
|
|
3043
|
+
expr = expr.expression;
|
|
3044
|
+
if (!ts.isIdentifier(expr))
|
|
3045
|
+
return null;
|
|
3046
|
+
const local = L.resolveLocal(expr);
|
|
3047
|
+
return local && L.runtimeOptionalLocals.has(L.runtimeOptionalRootOf(local)) ? local : null;
|
|
3048
|
+
}
|
|
3049
|
+
export function runtimeOptionalTrueIds(L, node) {
|
|
3050
|
+
let expr = node;
|
|
3051
|
+
while (ts.isParenthesizedExpression(expr))
|
|
3052
|
+
expr = expr.expression;
|
|
3053
|
+
if (ts.isBinaryExpression(expr) &&
|
|
3054
|
+
expr.operatorToken.kind === ts.SyntaxKind.AmpersandAmpersandToken) {
|
|
3055
|
+
return [...runtimeOptionalTrueIds(L, expr.left), ...runtimeOptionalTrueIds(L, expr.right)];
|
|
3056
|
+
}
|
|
3057
|
+
const local = runtimeOptionalLocalOf(L, expr);
|
|
3058
|
+
return local === null ? [] : [local];
|
|
3059
|
+
}
|
|
3060
|
+
export function withRuntimeOptionalNarrowed(L, locals, fn) {
|
|
3061
|
+
if (locals.length === 0)
|
|
3062
|
+
return fn();
|
|
3063
|
+
for (const local of locals)
|
|
3064
|
+
L.runtimeOptionalLocals.delete(L.runtimeOptionalRootOf(local));
|
|
3065
|
+
try {
|
|
3066
|
+
return fn();
|
|
3067
|
+
}
|
|
3068
|
+
finally {
|
|
3069
|
+
for (const local of locals)
|
|
3070
|
+
L.runtimeOptionalLocals.add(L.runtimeOptionalRootOf(local));
|
|
3071
|
+
}
|
|
2892
3072
|
}
|
|
2893
3073
|
/** JS ToBoolean: bool passes through; f64/string get a `toBool` wrapper
|
|
2894
3074
|
* (falsy: 0, -0, NaN, ""); unions get the same wrapper answered by a
|
|
@@ -5378,9 +5558,17 @@ export function lowerElementAccess(L, expr) {
|
|
|
5378
5558
|
// `const cmd = ['pwd', []]`, whose binding lowered checked-dynamic)
|
|
5379
5559
|
// dispatches as unmapped so the dyn-receiver branch below reads
|
|
5380
5560
|
// through the checked-dynamic tree instead of dynChecking into never's f64 residue.
|
|
5381
|
-
|
|
5561
|
+
let receiverIr = neverTaintedJsType(L, expr.expression, L.typeOf(expr.expression))
|
|
5382
5562
|
? null
|
|
5383
5563
|
: L.mapTypeOf(L.typeOf(expr.expression));
|
|
5564
|
+
// A readonly tuple union under Array.isArray is checker-typed as an
|
|
5565
|
+
// intersection with any[], whose structural mapping is not the tuple
|
|
5566
|
+
// shape. maybeNarrow on the receiver uses the runtime tag proof to
|
|
5567
|
+
// extract the one array-valued arm; dispatch element access from that
|
|
5568
|
+
// lowered representation, like the existing any[] array-method path.
|
|
5569
|
+
const checkerArray = L.checkerArrayValue(expr.expression);
|
|
5570
|
+
if (checkerArray)
|
|
5571
|
+
receiverIr = checkerArray.type;
|
|
5384
5572
|
if (receiverIr?.kind === "jsval") {
|
|
5385
5573
|
// Dispatch follows the RUNTIME world (383(d)): a checker-'any'
|
|
5386
5574
|
// receiver whose value LOWERED checked-dynamic (`bag.list[0]` where
|
|
@@ -5595,13 +5783,18 @@ export function lowerElementAccess(L, expr) {
|
|
|
5595
5783
|
// signature where the .d.ts says Function[], which would flatten the
|
|
5596
5784
|
// element to a zero-parameter closure).
|
|
5597
5785
|
const elemT = arr.type.kind === "array" ? arr.type.elem : receiverIr.elem;
|
|
5598
|
-
//
|
|
5599
|
-
//
|
|
5600
|
-
//
|
|
5601
|
-
//
|
|
5602
|
-
//
|
|
5603
|
-
|
|
5604
|
-
|
|
5786
|
+
// OOB-SAFE reads over fresh/probe array results. These expressions are
|
|
5787
|
+
// commonly used to ask for a first/last match (`filter(...).sort(...)[0]`,
|
|
5788
|
+
// `slice(-1)[0]`); an empty result is ordinary undefined, not a bounds
|
|
5789
|
+
// failure. Downstream lowering keeps the explicit optional union, so an
|
|
5790
|
+
// unchecked consumer still cannot place it into a plain element slot.
|
|
5791
|
+
if (arr.type.kind === "array" && isFreshArrayProbe(expr.expression)) {
|
|
5792
|
+
const safe = lowerSafeIndexRead(L, arr, index, locOf(expr));
|
|
5793
|
+
if (safe)
|
|
5794
|
+
return safe;
|
|
5795
|
+
}
|
|
5796
|
+
// --npm-static package files retain the original slice-specific route
|
|
5797
|
+
// for compatibility with package-source inference.
|
|
5605
5798
|
if (arr.type.kind === "array" &&
|
|
5606
5799
|
arr.kind === "arrIntrinsic" &&
|
|
5607
5800
|
arr.method === "slice" &&
|
|
@@ -5615,6 +5808,16 @@ export function lowerElementAccess(L, expr) {
|
|
|
5615
5808
|
// narrows literal-index element accesses).
|
|
5616
5809
|
return L.maybeNarrow({ kind: "arrayGet", arr, index, type: elemT, loc: locOf(expr) }, expr);
|
|
5617
5810
|
}
|
|
5811
|
+
function isFreshArrayProbe(node) {
|
|
5812
|
+
let current = node;
|
|
5813
|
+
while (ts.isParenthesizedExpression(current))
|
|
5814
|
+
current = current.expression;
|
|
5815
|
+
return (ts.isCallExpression(current) &&
|
|
5816
|
+
ts.isPropertyAccessExpression(current.expression) &&
|
|
5817
|
+
(current.expression.name.text === "filter" ||
|
|
5818
|
+
current.expression.name.text === "slice" ||
|
|
5819
|
+
current.expression.name.text === "sort"));
|
|
5820
|
+
}
|
|
5618
5821
|
/** `o[k]` where the RECEIVER is an island value — a jsval-mapped checker
|
|
5619
5822
|
* type, or an array/tuple-typed .d.ts surface whose lowered value is a
|
|
5620
5823
|
* handle. The key marshals in (any JS key kind — the engine does its
|
|
@@ -5654,7 +5857,7 @@ function islandElementRead(L, expr, obj) {
|
|
|
5654
5857
|
* fields outside that stay fenced. Declared-only shapes support reads
|
|
5655
5858
|
* whose key type proves membership (tsc's keyof check): all fields must
|
|
5656
5859
|
* share the result type, and a smuggled miss traps. */
|
|
5657
|
-
export function lowerRecordKeyRead(L, expr, shapeId, shape) {
|
|
5860
|
+
export function lowerRecordKeyRead(L, expr, shapeId, shape, includeUndefined = false) {
|
|
5658
5861
|
const loc = locOf(expr);
|
|
5659
5862
|
const keyNode = expr.argumentExpression;
|
|
5660
5863
|
// Literal declared keys are plain field reads (narrowing included).
|
|
@@ -5721,7 +5924,7 @@ export function lowerRecordKeyRead(L, expr, shapeId, shape) {
|
|
|
5721
5924
|
let declared = null;
|
|
5722
5925
|
if (shape.indexValue) {
|
|
5723
5926
|
declared = shape.indexValue;
|
|
5724
|
-
if (L.program.getCompilerOptions().noUncheckedIndexedAccess) {
|
|
5927
|
+
if (includeUndefined || L.program.getCompilerOptions().noUncheckedIndexedAccess) {
|
|
5725
5928
|
declared = L.withUndefinedArmOf(declared);
|
|
5726
5929
|
if (!declared)
|
|
5727
5930
|
L.badType(expr, L.typeOf(expr));
|
|
@@ -5740,7 +5943,72 @@ export function lowerRecordKeyRead(L, expr, shapeId, shape) {
|
|
|
5740
5943
|
if (!recordKeyResultOk(L, overflowOnly ? { ...shape, fields: [] } : shape, declared)) {
|
|
5741
5944
|
L.unsupported("SC1090", expr, `dynamic keyed reads of '${L.fmt({ kind: "record", shapeId })}' as '${L.fmt(declared)}' (every declared field must be readable at that type)`);
|
|
5742
5945
|
}
|
|
5743
|
-
|
|
5946
|
+
const read = {
|
|
5947
|
+
kind: "recordKeyGet",
|
|
5948
|
+
obj,
|
|
5949
|
+
shapeId,
|
|
5950
|
+
key,
|
|
5951
|
+
...(overflowOnly ? { overflowOnly: true } : {}),
|
|
5952
|
+
type: declared,
|
|
5953
|
+
loc,
|
|
5954
|
+
};
|
|
5955
|
+
return includeUndefined ? read : L.maybeNarrow(read, expr);
|
|
5956
|
+
}
|
|
5957
|
+
/** A keyed read used only to observe absence (`if (map[k])`, or the left
|
|
5958
|
+
* side of `||`/`??`). JavaScript answers undefined for missing record keys
|
|
5959
|
+
* and array indices even when noUncheckedIndexedAccess is disabled. Keep
|
|
5960
|
+
* that value in an explicit union until the surrounding guard/default has
|
|
5961
|
+
* consumed it; ordinary unchecked reads retain the typed trap. Assertions
|
|
5962
|
+
* around the read are erased here because this context handles the missing
|
|
5963
|
+
* arm instead of consuming it as the asserted type. */
|
|
5964
|
+
export function lowerAbsenceProbe(L, node) {
|
|
5965
|
+
let expr = node;
|
|
5966
|
+
while (ts.isParenthesizedExpression(expr) || ts.isAsExpression(expr) || ts.isTypeAssertion(expr)) {
|
|
5967
|
+
expr = expr.expression;
|
|
5968
|
+
}
|
|
5969
|
+
if (ts.isPropertyAccessExpression(expr)) {
|
|
5970
|
+
const target = L.fieldTarget(expr);
|
|
5971
|
+
if (target?.container !== "recordOvf")
|
|
5972
|
+
return null;
|
|
5973
|
+
if (target.fieldType.kind === "union" &&
|
|
5974
|
+
L.armTag(target.fieldType.unionId, UNDEFINED_T) >= 0) {
|
|
5975
|
+
return null;
|
|
5976
|
+
}
|
|
5977
|
+
const type = L.withUndefinedArmOf(target.fieldType);
|
|
5978
|
+
if (!type)
|
|
5979
|
+
return null;
|
|
5980
|
+
return {
|
|
5981
|
+
kind: "recordKeyGet",
|
|
5982
|
+
obj: target.obj,
|
|
5983
|
+
shapeId: target.shapeId,
|
|
5984
|
+
key: { kind: "strLit", value: target.field, type: STRING, loc: locOf(expr) },
|
|
5985
|
+
overflowOnly: true,
|
|
5986
|
+
type,
|
|
5987
|
+
loc: locOf(expr),
|
|
5988
|
+
};
|
|
5989
|
+
}
|
|
5990
|
+
if (!ts.isElementAccessExpression(expr))
|
|
5991
|
+
return null;
|
|
5992
|
+
const header = L.lowerHttpHeadersElement(expr);
|
|
5993
|
+
if (header)
|
|
5994
|
+
return header;
|
|
5995
|
+
const receiverT = L.mapTypeOf(L.typeOf(expr.expression));
|
|
5996
|
+
if (receiverT?.kind === "record") {
|
|
5997
|
+
const shape = L.shapes.get(receiverT.shapeId);
|
|
5998
|
+
if (shape && !shape.tuple && shape.indexValue && shape.indexValue.kind !== "dyn") {
|
|
5999
|
+
return L.lowerRecordKeyRead(expr, receiverT.shapeId, shape, true);
|
|
6000
|
+
}
|
|
6001
|
+
return null;
|
|
6002
|
+
}
|
|
6003
|
+
if (receiverT?.kind !== "array")
|
|
6004
|
+
return null;
|
|
6005
|
+
const arr = L.lowerExpr(expr.expression);
|
|
6006
|
+
if (arr.type.kind !== "array")
|
|
6007
|
+
return null;
|
|
6008
|
+
const index = L.lowerExpr(expr.argumentExpression);
|
|
6009
|
+
if (index.type.kind !== "f64")
|
|
6010
|
+
return null;
|
|
6011
|
+
return lowerSafeIndexRead(L, arr, index, locOf(expr));
|
|
5744
6012
|
}
|
|
5745
6013
|
/** The compile-time string spelling of a record key literal: a string
|
|
5746
6014
|
* literal's text, or a NON-NEGATIVE numeric literal's canonical JS
|
|
@@ -6320,6 +6588,9 @@ export function lowerAsExpression(L, expr) {
|
|
|
6320
6588
|
L.unsupported("SC1063", expr);
|
|
6321
6589
|
}
|
|
6322
6590
|
const inner = L.lowerExpr(expr.expression);
|
|
6591
|
+
const use = runtimeOptionalUseOf(expr);
|
|
6592
|
+
if (inner.type.kind === "union" && runtimeOptionalAssertionErases(L, expr, inner, use))
|
|
6593
|
+
return inner;
|
|
6323
6594
|
if (inner.type.kind !== "dyn" && inner.type.kind !== "jsval") {
|
|
6324
6595
|
// A STATIC value cast `as any` is the explicit island entrance.
|
|
6325
6596
|
const targetTs0 = L.checker.getTypeFromTypeNode(expr.type);
|
|
@@ -6560,6 +6831,9 @@ export function lowerBinary(L, expr) {
|
|
|
6560
6831
|
L.rejectUnresolved(expr.left, `assignment to '${expr.left.text}' (not a writable local or module global)`);
|
|
6561
6832
|
}
|
|
6562
6833
|
const value = L.lowerExprExpecting(expr.right, target.type);
|
|
6834
|
+
const runtimeOptionalRoot = L.runtimeOptionalRootOf(target);
|
|
6835
|
+
if (L.runtimeOptionalStorageLocals.has(runtimeOptionalRoot))
|
|
6836
|
+
L.runtimeOptionalLocals.add(runtimeOptionalRoot);
|
|
6563
6837
|
return { kind: "assignExpr", localId: target.id, value, type: target.type, loc };
|
|
6564
6838
|
}
|
|
6565
6839
|
// `events.defaultMaxListeners = v` — the module-property write
|
|
@@ -6719,11 +6993,16 @@ export function lowerBinary(L, expr) {
|
|
|
6719
6993
|
L.unsupported("SC1040", expr);
|
|
6720
6994
|
}
|
|
6721
6995
|
if (op === ts.SyntaxKind.AmpersandAmpersandToken || op === ts.SyntaxKind.BarBarToken) {
|
|
6996
|
+
if (op === ts.SyntaxKind.BarBarToken) {
|
|
6997
|
+
const fallback = lowerAbsenceAwareOrChain(L, expr);
|
|
6998
|
+
if (fallback)
|
|
6999
|
+
return fallback;
|
|
7000
|
+
}
|
|
6722
7001
|
// JS value semantics: `a && b` is `toBool(a) ? b : a` — the result is
|
|
6723
7002
|
// an operand value, so both operands must share one IR kind (which is
|
|
6724
7003
|
// also what tsc's type says, modulo literal-union collapsing that
|
|
6725
7004
|
// mapType already handles). Mixed kinds (`n && s`) stay rejected.
|
|
6726
|
-
const left = L.lowerExpr(expr.left);
|
|
7005
|
+
const left = lowerAbsenceProbe(L, expr.left) ?? L.lowerExpr(expr.left);
|
|
6727
7006
|
const right = L.lowerExpr(expr.right);
|
|
6728
7007
|
// A LITERAL-unit left operand (a compile-time undefined/null — the
|
|
6729
7008
|
// capability-probe members: `process.features.inspector ||
|
|
@@ -6894,8 +7173,10 @@ export function lowerBinary(L, expr) {
|
|
|
6894
7173
|
if (test)
|
|
6895
7174
|
return test;
|
|
6896
7175
|
}
|
|
6897
|
-
const
|
|
6898
|
-
|
|
7176
|
+
const strictEquality = op === ts.SyntaxKind.EqualsEqualsEqualsToken ||
|
|
7177
|
+
op === ts.SyntaxKind.ExclamationEqualsEqualsToken;
|
|
7178
|
+
const left = strictEquality ? lowerAbsenceProbe(L, expr.left) ?? L.lowerExpr(expr.left) : L.lowerExpr(expr.left);
|
|
7179
|
+
const right = strictEquality ? lowerAbsenceProbe(L, expr.right) ?? L.lowerExpr(expr.right) : L.lowerExpr(expr.right);
|
|
6899
7180
|
if (left.type.kind === "dyn" || right.type.kind === "dyn") {
|
|
6900
7181
|
// The narrowing unit comparisons ARE answerable on unknown: `v ===
|
|
6901
7182
|
// undefined` / `v !== null` test the dyn node's kind directly, and
|
|
@@ -7106,6 +7387,29 @@ export function lowerBinary(L, expr) {
|
|
|
7106
7387
|
const ut = left.type.kind === "union" ? left.type : right.type;
|
|
7107
7388
|
const bothUnion = left.type.kind === "union" && right.type.kind === "union";
|
|
7108
7389
|
const sameUnion = bothUnion && typeEquals(left.type, right.type);
|
|
7390
|
+
const plainFunc = left.type.kind === "func" ? left : right.type.kind === "func" ? right : null;
|
|
7391
|
+
const unionArms = L.unions.get(ut.unionId)?.arms ?? [];
|
|
7392
|
+
const funcArm = unionArms.find((arm) => arm.kind === "func");
|
|
7393
|
+
// `listeners()` reports original prefix callbacks, but the
|
|
7394
|
+
// checker views an indexed result as function | undefined. For
|
|
7395
|
+
// identity comparison, retain the original closure pointer rather
|
|
7396
|
+
// than adapting it to the tuple signature.
|
|
7397
|
+
if (!bothUnion &&
|
|
7398
|
+
plainFunc !== null &&
|
|
7399
|
+
funcArm?.kind === "func" &&
|
|
7400
|
+
unionArms.filter((arm) => arm.kind === "func").length === 1 &&
|
|
7401
|
+
unionArms.some(isUnitType)) {
|
|
7402
|
+
return {
|
|
7403
|
+
kind: "unionFuncEq",
|
|
7404
|
+
unionId: ut.unionId,
|
|
7405
|
+
tag: L.armTag(ut.unionId, funcArm),
|
|
7406
|
+
union: left.type.kind === "union" ? left : right,
|
|
7407
|
+
func: plainFunc,
|
|
7408
|
+
negated,
|
|
7409
|
+
type: BOOL,
|
|
7410
|
+
loc,
|
|
7411
|
+
};
|
|
7412
|
+
}
|
|
7109
7413
|
if ((sameUnion || !bothUnion) && L.eqComparableUnion(ut.unionId)) {
|
|
7110
7414
|
return {
|
|
7111
7415
|
kind: "unionEq",
|
|
@@ -7193,6 +7497,44 @@ export function lowerBinary(L, expr) {
|
|
|
7193
7497
|
}
|
|
7194
7498
|
L.unsupported("SC1090", expr, `operator '${ts.tokenToString(op) ?? ts.SyntaxKind[op]}'`);
|
|
7195
7499
|
}
|
|
7500
|
+
function lowerAbsenceAwareOrChain(L, expr) {
|
|
7501
|
+
const nodes = [];
|
|
7502
|
+
const collect = (node) => {
|
|
7503
|
+
if (ts.isBinaryExpression(node) &&
|
|
7504
|
+
node.operatorToken.kind === ts.SyntaxKind.BarBarToken) {
|
|
7505
|
+
collect(node.left);
|
|
7506
|
+
collect(node.right);
|
|
7507
|
+
}
|
|
7508
|
+
else {
|
|
7509
|
+
nodes.push(node);
|
|
7510
|
+
}
|
|
7511
|
+
};
|
|
7512
|
+
collect(expr);
|
|
7513
|
+
if (nodes.length < 3)
|
|
7514
|
+
return null;
|
|
7515
|
+
const lowered = nodes.map((node) => lowerAbsenceProbe(L, node) ?? L.lowerExpr(node));
|
|
7516
|
+
if (!lowered.some((value) => value.type.kind === "union" && L.armTag(value.type.unionId, UNDEFINED_T) >= 0)) {
|
|
7517
|
+
return null;
|
|
7518
|
+
}
|
|
7519
|
+
let result = lowered[lowered.length - 1];
|
|
7520
|
+
for (let i = lowered.length - 2; i >= 0; i--) {
|
|
7521
|
+
const left = lowered[i];
|
|
7522
|
+
const loc = locOf(nodes[i]);
|
|
7523
|
+
if (left.type.kind === "union") {
|
|
7524
|
+
const def = L.unions.get(left.type.unionId);
|
|
7525
|
+
const nonUnit = def?.arms.filter((arm) => !isUnitType(arm)) ?? [];
|
|
7526
|
+
if (nonUnit.length !== 1 || !typeEquals(nonUnit[0], result.type))
|
|
7527
|
+
return null;
|
|
7528
|
+
L.requireTruthyUnion(left.type.unionId, expr);
|
|
7529
|
+
result = { kind: "orDefault", left, right: result, type: result.type, loc };
|
|
7530
|
+
continue;
|
|
7531
|
+
}
|
|
7532
|
+
if (!typeEquals(left.type, result.type))
|
|
7533
|
+
return null;
|
|
7534
|
+
result = { kind: "logical", op: "||", left, right: result, type: result.type, loc };
|
|
7535
|
+
}
|
|
7536
|
+
return result;
|
|
7537
|
+
}
|
|
7196
7538
|
/** `typeof e === "lit"` / `typeof e !== "lit"` where e is a catch
|
|
7197
7539
|
* binding: the runtime kind test over the snapshot's tag. Only the
|
|
7198
7540
|
* primitive literals narrow ("string"/"number"/"boolean" — exactly what
|
|
@@ -9009,7 +9351,14 @@ export function lowerFieldRead(L, expr) {
|
|
|
9009
9351
|
return L.fieldGetExpr(target, locOf(expr), expr);
|
|
9010
9352
|
if (expr.questionDotToken)
|
|
9011
9353
|
return null;
|
|
9012
|
-
|
|
9354
|
+
let receiverIr = L.mapTypeOf(L.typeOf(expr.expression));
|
|
9355
|
+
// The readonly-tuple Array.isArray narrow has no directly mappable
|
|
9356
|
+
// checker type (`Result & any[]`), but maybeNarrow extracted the tuple
|
|
9357
|
+
// record behind it. Route tuple properties such as `.length` from that
|
|
9358
|
+
// lowered value, the element-access bridge's twin.
|
|
9359
|
+
const checkerArray = L.checkerArrayValue(expr.expression);
|
|
9360
|
+
if (checkerArray?.type.kind === "record")
|
|
9361
|
+
receiverIr = checkerArray.type;
|
|
9013
9362
|
if (receiverIr?.kind === "object" &&
|
|
9014
9363
|
(L.findMethodOn(L.classes.get(receiverIr.className) ?? null, expr.name.text) ||
|
|
9015
9364
|
findGenericMethodOn(L, L.classes.get(receiverIr.className) ?? null, expr.name.text))) {
|