@scriptc/compiler 0.1.1 → 0.1.3
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 +104 -31
- package/ambient/scriptc.d.ts +2 -1
- package/dist/backend/build-cache.js.map +1 -1
- package/dist/backend/c/async.d.ts +5 -0
- package/dist/backend/c/async.js +81 -2
- package/dist/backend/c/async.js.map +1 -1
- package/dist/backend/c/c-emitter.d.ts +25 -10
- package/dist/backend/c/c-emitter.js +74 -22
- package/dist/backend/c/c-emitter.js.map +1 -1
- package/dist/backend/c/exprs.d.ts +8 -1
- package/dist/backend/c/exprs.js +371 -18
- package/dist/backend/c/exprs.js.map +1 -1
- package/dist/backend/c/island.js.map +1 -1
- package/dist/backend/c/may-throw.js.map +1 -1
- package/dist/backend/c/shapes.js +15 -13
- package/dist/backend/c/shapes.js.map +1 -1
- package/dist/backend/c/stmts.d.ts +3 -5
- package/dist/backend/c/stmts.js +58 -17
- package/dist/backend/c/stmts.js.map +1 -1
- package/dist/backend/c/types.d.ts +4 -0
- package/dist/backend/c/types.js +18 -1
- package/dist/backend/c/types.js.map +1 -1
- package/dist/backend/c/types.test.js +8 -1
- package/dist/backend/c/types.test.js.map +1 -1
- package/dist/backend/c/walkers.js +63 -16
- package/dist/backend/c/walkers.js.map +1 -1
- package/dist/backend/external-c.js.map +1 -1
- package/dist/backend/json-literal.d.ts +7 -0
- package/dist/backend/json-literal.js +17 -0
- package/dist/backend/json-literal.js.map +1 -0
- package/dist/backend/json-literal.test.d.ts +1 -0
- package/dist/backend/json-literal.test.js +17 -0
- package/dist/backend/json-literal.test.js.map +1 -0
- package/dist/backend/link-plan.d.ts +1 -1
- package/dist/backend/link-plan.js +5 -1
- package/dist/backend/link-plan.js.map +1 -1
- package/dist/backend/linker.js.map +1 -1
- package/dist/backend/llvm/blocks.js.map +1 -1
- package/dist/backend/llvm/classes.js +8 -7
- package/dist/backend/llvm/classes.js.map +1 -1
- package/dist/backend/llvm/common.d.ts +4 -0
- package/dist/backend/llvm/common.js +6 -0
- package/dist/backend/llvm/common.js.map +1 -1
- package/dist/backend/llvm/common.test.d.ts +1 -0
- package/dist/backend/llvm/common.test.js +9 -0
- package/dist/backend/llvm/common.test.js.map +1 -0
- package/dist/backend/llvm/dyn.d.ts +4 -0
- package/dist/backend/llvm/dyn.js +80 -16
- package/dist/backend/llvm/dyn.js.map +1 -1
- package/dist/backend/llvm/emitter.js +132 -25
- package/dist/backend/llvm/emitter.js.map +1 -1
- package/dist/backend/llvm/expr-async.js +85 -3
- package/dist/backend/llvm/expr-async.js.map +1 -1
- package/dist/backend/llvm/expr-bytes.js.map +1 -1
- package/dist/backend/llvm/expr-calls.js.map +1 -1
- package/dist/backend/llvm/expr-containers.js.map +1 -1
- package/dist/backend/llvm/expr-context.d.ts +5 -0
- package/dist/backend/llvm/expr-control.js.map +1 -1
- package/dist/backend/llvm/expr-dispatch.js.map +1 -1
- package/dist/backend/llvm/expr-dynamic.js +15 -3
- package/dist/backend/llvm/expr-dynamic.js.map +1 -1
- package/dist/backend/llvm/expr-island.js.map +1 -1
- package/dist/backend/llvm/expr-primitives.js +8 -0
- package/dist/backend/llvm/expr-primitives.js.map +1 -1
- package/dist/backend/llvm/expr-records.js +3 -3
- package/dist/backend/llvm/expr-records.js.map +1 -1
- package/dist/backend/llvm/expr-stream-bridges.js +222 -10
- package/dist/backend/llvm/expr-stream-bridges.js.map +1 -1
- package/dist/backend/llvm/expr-stream-callbacks.d.ts +3 -0
- package/dist/backend/llvm/expr-stream-callbacks.js +53 -0
- package/dist/backend/llvm/expr-stream-callbacks.js.map +1 -1
- package/dist/backend/llvm/lib-dispatch.js +24 -0
- package/dist/backend/llvm/lib-dispatch.js.map +1 -1
- package/dist/backend/llvm/lib-process.js +50 -5
- package/dist/backend/llvm/lib-process.js.map +1 -1
- package/dist/backend/llvm/lib-shared.js +64 -0
- package/dist/backend/llvm/lib-shared.js.map +1 -1
- package/dist/backend/llvm/lib-stream.js +6 -3
- package/dist/backend/llvm/lib-stream.js.map +1 -1
- package/dist/backend/llvm/shapes.js +10 -8
- package/dist/backend/llvm/shapes.js.map +1 -1
- package/dist/backend/llvm/split.js.map +1 -1
- package/dist/backend/llvm/unsupported.js.map +1 -1
- package/dist/backend/llvm/walkers.js +7 -5
- package/dist/backend/llvm/walkers.js.map +1 -1
- package/dist/backend/native-codegen.js.map +1 -1
- package/dist/backend/native-codegen.test.js +2 -0
- package/dist/backend/native-codegen.test.js.map +1 -1
- package/dist/backend/native-link-info.d.ts +2 -1
- package/dist/backend/native-link-info.js +4 -0
- package/dist/backend/native-link-info.js.map +1 -1
- package/dist/backend/native-link-info.test.js +24 -1
- package/dist/backend/native-link-info.test.js.map +1 -1
- package/dist/backend/native-toolchain.d.ts +8 -6
- package/dist/backend/native-toolchain.js +28 -13
- package/dist/backend/native-toolchain.js.map +1 -1
- package/dist/backend/native-toolchain.test.js +36 -1
- package/dist/backend/native-toolchain.test.js.map +1 -1
- package/dist/backend/runtime-pack-targets.test.js +1 -1
- package/dist/backend/runtime-pack-targets.test.js.map +1 -1
- package/dist/backend/runtime-pack.js.map +1 -1
- package/dist/backend/runtime-pack.test.js +1 -0
- package/dist/backend/runtime-pack.test.js.map +1 -1
- package/dist/backend/targets.d.ts +5 -0
- package/dist/backend/targets.js +9 -0
- package/dist/backend/targets.js.map +1 -1
- package/dist/backend/targets.test.js +6 -1
- package/dist/backend/targets.test.js.map +1 -1
- package/dist/backend/vendor-archives.js.map +1 -1
- package/dist/compat/fetch-profile.js +4 -2
- package/dist/compat/fetch-profile.js.map +1 -1
- package/dist/coverage/report.js.map +1 -1
- package/dist/coverage/surface-manifest.js +13 -0
- package/dist/coverage/surface-manifest.js.map +1 -1
- package/dist/diagnostics/diagnostic.d.ts +6 -5
- package/dist/diagnostics/diagnostic.js +10 -9
- package/dist/diagnostics/diagnostic.js.map +1 -1
- package/dist/diagnostics/render.js.map +1 -1
- package/dist/executable/executable-cache.d.ts +1 -0
- package/dist/executable/executable-cache.js +1 -0
- package/dist/executable/executable-cache.js.map +1 -1
- package/dist/executable/executable-cache.test.js +1 -0
- package/dist/executable/executable-cache.test.js.map +1 -1
- package/dist/ffi/ffi-manifest.js.map +1 -1
- package/dist/frontend/cjs-lexer.js.map +1 -1
- package/dist/frontend/dts-paths.js.map +1 -1
- package/dist/frontend/input-tracker.d.ts +10 -0
- package/dist/frontend/input-tracker.js +26 -3
- package/dist/frontend/input-tracker.js.map +1 -1
- package/dist/frontend/input-tracker.test.js +8 -1
- package/dist/frontend/input-tracker.test.js.map +1 -1
- package/dist/frontend/lib-contract.js.map +1 -1
- package/dist/frontend/lowering/array-values.d.ts +4 -0
- package/dist/frontend/lowering/array-values.js +18 -2
- package/dist/frontend/lowering/array-values.js.map +1 -1
- package/dist/frontend/lowering/callback-arg.js.map +1 -1
- package/dist/frontend/lowering/containers/bytes.d.ts +31 -0
- package/dist/frontend/lowering/containers/bytes.js +1105 -0
- package/dist/frontend/lowering/containers/bytes.js.map +1 -0
- package/dist/frontend/lowering/containers/string-and-regexp.d.ts +17 -0
- package/dist/frontend/lowering/containers/string-and-regexp.js +345 -0
- package/dist/frontend/lowering/containers/string-and-regexp.js.map +1 -0
- package/dist/frontend/lowering/expressions/evaluation-safety.d.ts +13 -0
- package/dist/frontend/lowering/expressions/evaluation-safety.js +76 -0
- package/dist/frontend/lowering/expressions/evaluation-safety.js.map +1 -0
- package/dist/frontend/lowering/expressions/object-literals.d.ts +44 -0
- package/dist/frontend/lowering/expressions/object-literals.js +1815 -0
- package/dist/frontend/lowering/expressions/object-literals.js.map +1 -0
- package/dist/frontend/lowering/expressions/optional-chains.d.ts +31 -0
- package/dist/frontend/lowering/expressions/optional-chains.js +382 -0
- package/dist/frontend/lowering/expressions/optional-chains.js.map +1 -0
- package/dist/frontend/lowering/expressions/try-lower-expression.d.ts +7 -0
- package/dist/frontend/lowering/expressions/try-lower-expression.js +26 -0
- package/dist/frontend/lowering/expressions/try-lower-expression.js.map +1 -0
- package/dist/frontend/lowering/lib-boundary.js +7 -2
- package/dist/frontend/lowering/lib-boundary.js.map +1 -1
- package/dist/frontend/lowering/lower-assert.js +16 -10
- package/dist/frontend/lowering/lower-assert.js.map +1 -1
- package/dist/frontend/lowering/lower-async-iteration.d.ts +7 -0
- package/dist/frontend/lowering/lower-async-iteration.js +657 -0
- package/dist/frontend/lowering/lower-async-iteration.js.map +1 -0
- package/dist/frontend/lowering/lower-builtins.d.ts +40 -21
- package/dist/frontend/lowering/lower-builtins.js +611 -115
- package/dist/frontend/lowering/lower-builtins.js.map +1 -1
- package/dist/frontend/lowering/lower-calls.d.ts +21 -10
- package/dist/frontend/lowering/lower-calls.js +516 -75
- package/dist/frontend/lowering/lower-calls.js.map +1 -1
- package/dist/frontend/lowering/lower-classes.d.ts +7 -13
- package/dist/frontend/lowering/lower-classes.js +128 -35
- package/dist/frontend/lowering/lower-classes.js.map +1 -1
- package/dist/frontend/lowering/lower-comptime.js.map +1 -1
- package/dist/frontend/lowering/lower-containers.d.ts +1 -47
- package/dist/frontend/lowering/lower-containers.js +147 -1475
- package/dist/frontend/lowering/lower-containers.js.map +1 -1
- package/dist/frontend/lowering/lower-event-emitter.js +51 -5
- package/dist/frontend/lowering/lower-event-emitter.js.map +1 -1
- package/dist/frontend/lowering/lower-exprs.d.ts +0 -78
- package/dist/frontend/lowering/lower-exprs.js +503 -2353
- package/dist/frontend/lowering/lower-exprs.js.map +1 -1
- package/dist/frontend/lowering/lower-generators.d.ts +18 -3
- package/dist/frontend/lowering/lower-generators.js +145 -9
- package/dist/frontend/lowering/lower-generators.js.map +1 -1
- package/dist/frontend/lowering/lower-inspect.js +10 -3
- package/dist/frontend/lowering/lower-inspect.js.map +1 -1
- package/dist/frontend/lowering/lower-island.d.ts +6 -0
- package/dist/frontend/lowering/lower-island.js +37 -5
- package/dist/frontend/lowering/lower-island.js.map +1 -1
- package/dist/frontend/lowering/lower-modules.js +60 -15
- package/dist/frontend/lowering/lower-modules.js.map +1 -1
- package/dist/frontend/lowering/lower-node-module.d.ts +18 -0
- package/dist/frontend/lowering/lower-node-module.js +302 -0
- package/dist/frontend/lowering/lower-node-module.js.map +1 -0
- package/dist/frontend/lowering/lower-server.js +1 -1
- package/dist/frontend/lowering/lower-server.js.map +1 -1
- package/dist/frontend/lowering/lower-stmts.d.ts +10 -28
- package/dist/frontend/lowering/lower-stmts.js +542 -259
- package/dist/frontend/lowering/lower-stmts.js.map +1 -1
- package/dist/frontend/lowering/lower-stream.js +4 -4
- package/dist/frontend/lowering/lower-stream.js.map +1 -1
- package/dist/frontend/lowering/lowerer.d.ts +41 -38
- package/dist/frontend/lowering/lowerer.js +148 -62
- package/dist/frontend/lowering/lowerer.js.map +1 -1
- package/dist/frontend/lowering/optional-arguments.d.ts +9 -0
- package/dist/frontend/lowering/optional-arguments.js +57 -0
- package/dist/frontend/lowering/optional-arguments.js.map +1 -0
- package/dist/frontend/lowering/surfaces.d.ts +5 -3
- package/dist/frontend/lowering/surfaces.js +33 -13
- package/dist/frontend/lowering/surfaces.js.map +1 -1
- package/dist/frontend/npm-static-declarations.d.ts +35 -0
- package/dist/frontend/npm-static-declarations.js +519 -0
- package/dist/frontend/npm-static-declarations.js.map +1 -0
- package/dist/frontend/npm-static-declarations.test.d.ts +1 -0
- package/dist/frontend/npm-static-declarations.test.js +132 -0
- package/dist/frontend/npm-static-declarations.test.js.map +1 -0
- package/dist/frontend/npm-static.d.ts +2 -0
- package/dist/frontend/npm-static.js +26 -10
- package/dist/frontend/npm-static.js.map +1 -1
- package/dist/frontend/npm.d.ts +60 -0
- package/dist/frontend/npm.js +38 -4
- package/dist/frontend/npm.js.map +1 -1
- package/dist/frontend/npm.test.d.ts +1 -0
- package/dist/frontend/npm.test.js +50 -0
- package/dist/frontend/npm.test.js.map +1 -0
- package/dist/frontend/program.d.ts +3 -2
- package/dist/frontend/program.js +513 -70
- package/dist/frontend/program.js.map +1 -1
- package/dist/frontend/resolve.d.ts +26 -3
- package/dist/frontend/resolve.js +166 -29
- package/dist/frontend/resolve.js.map +1 -1
- package/dist/frontend/resolve.test.js +24 -1
- package/dist/frontend/resolve.test.js.map +1 -1
- package/dist/frontend/runtime-resolve.d.ts +22 -0
- package/dist/frontend/runtime-resolve.js +101 -0
- package/dist/frontend/runtime-resolve.js.map +1 -0
- package/dist/frontend/runtime-resolve.test.d.ts +1 -0
- package/dist/frontend/runtime-resolve.test.js +18 -0
- package/dist/frontend/runtime-resolve.test.js.map +1 -0
- package/dist/frontend/ts7/adapter.js +12 -13
- package/dist/frontend/ts7/adapter.js.map +1 -1
- package/dist/frontend/ts7/ast.js.map +1 -1
- package/dist/frontend/ts7/census-check.js +3 -3
- package/dist/frontend/ts7/census-check.js.map +1 -1
- package/dist/frontend/ts7/checker.d.ts +3 -0
- package/dist/frontend/ts7/checker.js +12 -3
- package/dist/frontend/ts7/checker.js.map +1 -1
- package/dist/frontend/ts7/enums.js.map +1 -1
- package/dist/frontend/ts7/program-adapter.js +42 -3
- package/dist/frontend/ts7/program-adapter.js.map +1 -1
- package/dist/frontend/type-mapper.js +171 -16
- package/dist/frontend/type-mapper.js.map +1 -1
- package/dist/index.js +11 -5
- package/dist/index.js.map +1 -1
- package/dist/ir/analysis.js +1 -0
- package/dist/ir/analysis.js.map +1 -1
- package/dist/ir/ir.d.ts +125 -52
- package/dist/ir/ir.js +136 -7
- 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 +120 -63
- package/dist/ir/validate.js.map +1 -1
- package/dist/library/cache-primitives.js.map +1 -1
- package/dist/library/cache-reset.test.js.map +1 -1
- package/dist/library/compiler-self-identity.js.map +1 -1
- package/dist/library/int-infer.js.map +1 -1
- package/dist/library/int-infer.test.js +4 -4
- package/dist/library/int-infer.test.js.map +1 -1
- package/dist/library/library-cache.d.ts +1 -0
- package/dist/library/library-cache.js +1 -0
- package/dist/library/library-cache.js.map +1 -1
- package/dist/library/library-cache.test.js +18 -5
- package/dist/library/library-cache.test.js.map +1 -1
- package/dist/library/library-profile.js.map +1 -1
- package/dist/library/sidecar-validate.js.map +1 -1
- package/dist/library/sidecar.js.map +1 -1
- package/dist/startup-cache.js.map +1 -1
- package/dist/wasi-paths.js.map +1 -1
- package/package.json +18 -18
- package/surface-manifest.json +91 -20
|
@@ -9,12 +9,12 @@ import * as ts from "../ts7/adapter.js";
|
|
|
9
9
|
import { dirname, posix } from "node:path";
|
|
10
10
|
import { pathToFileURL } from "node:url";
|
|
11
11
|
import { wasiGuestPath } from "../../wasi-paths.js";
|
|
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,
|
|
12
|
+
import { BIGINT_T, 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, funcOf, isDynTypedRefType, isJsonSafeType, isSupportedArrayElem, isUnitType, jsOpResultKind, shapeHasAccessorSlots, typeEquals, typeKey, unionFuncSetArmsOk } from "../../ir/ir.js";
|
|
13
13
|
import { cjsClassExprWholeExportOf, cjsExportAssignmentOf, cjsExportDiscardReason, isCjsExportTableLiteral, isCjsJsFile, isJsSourceFile, isModuleExportsAccess, isNodeEsmFile, locOf } from "../program.js";
|
|
14
14
|
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";
|
|
15
|
-
import { blockedBindingUseDiag,
|
|
15
|
+
import { blockedBindingUseDiag, requiresDynamicPackageDiag, unsupportedDiag } from "../../diagnostics/diagnostic.js";
|
|
16
16
|
import { PoisonError, dynUndefinedExpr, jsFuncNameOf, neverTaintedJsType, nodeThrowExpr, own } from "./lowerer.js";
|
|
17
|
-
import {
|
|
17
|
+
import { lowerNpmStaticSafeIndexRead, lowerSafeIndexRead, strCharsCall } from "./lower-containers.js";
|
|
18
18
|
import { arrayValueStore } from "./array-values.js";
|
|
19
19
|
import { npmStaticPackageOfPath } from "../npm-static.js";
|
|
20
20
|
import { unsupportedModuleFeatureOf } from "../builtin-modules.js";
|
|
@@ -29,6 +29,11 @@ import { isConstAssertionTypeNode, isGenericCallableMemberType, isParseArgsDynTy
|
|
|
29
29
|
import { lowerYield } from "./lower-generators.js";
|
|
30
30
|
import { lowerStreamProperty, lowerStreamStateProperty, streamSidesOf } from "./lower-stream.js";
|
|
31
31
|
import { countedFor, numLit, varRef } from "../../ir/build.js";
|
|
32
|
+
import { isSafeToDiscard, isSafeToMoveConditionEarlier, isSafeToRepeat } from "./expressions/evaluation-safety.js";
|
|
33
|
+
import { hasOptionalChainGuard, isOptionalChainTail, isRequireMainFilename } from "./expressions/optional-chains.js";
|
|
34
|
+
import { conditionalSpreadOf, foldedStringKeyOf } from "./expressions/object-literals.js";
|
|
35
|
+
import { tryLowerExpression } from "./expressions/try-lower-expression.js";
|
|
36
|
+
import { fenceNodeModuleMutation, isNodeModuleValue, lowerNodeModuleIdentifier, lowerNodeModuleProperty, lowerRequireCacheElement, lowerRequireCacheHas, lowerRequireMainProperty } from "./lower-node-module.js";
|
|
32
37
|
/** A template piece's RAW text (String.raw's contract: escapes stay
|
|
33
38
|
* characters). 7's client AST ships no rawText at runtime (the typing
|
|
34
39
|
* declares it; the serialized node data omits it), so the raw span comes
|
|
@@ -138,6 +143,16 @@ function lowerExprInner(lowerer, expr) {
|
|
|
138
143
|
}
|
|
139
144
|
return { kind: "numLit", value, type: F64, loc };
|
|
140
145
|
}
|
|
146
|
+
if (ts.isBigIntLiteral(expr)) {
|
|
147
|
+
const spelling = expr.getText().replace(/_/g, "").replace(/n$/i, "");
|
|
148
|
+
return {
|
|
149
|
+
kind: "libCall",
|
|
150
|
+
fn: "bigint.parse",
|
|
151
|
+
args: [{ kind: "strLit", value: spelling, type: STRING, loc }],
|
|
152
|
+
type: BIGINT_T,
|
|
153
|
+
loc,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
141
156
|
if (expr.kind === ts.SyntaxKind.TrueKeyword) {
|
|
142
157
|
return { kind: "boolLit", value: true, type: BOOL, loc };
|
|
143
158
|
}
|
|
@@ -405,7 +420,7 @@ function lowerExprInner(lowerer, expr) {
|
|
|
405
420
|
lowerer.unsupported("SC1090", prop, "a conditional spread mixed with plain spreads in an 'any'-typed object literal");
|
|
406
421
|
}
|
|
407
422
|
const cond = lowerer.lowerCondition(cs.cond);
|
|
408
|
-
if (!
|
|
423
|
+
if (!isSafeToMoveConditionEarlier(cond)) {
|
|
409
424
|
lowerer.unsupported("SC1090", prop, "conditional spreads with effectful conditions in an 'any'-typed object literal (bind the condition to a const first)");
|
|
410
425
|
}
|
|
411
426
|
spread = { cond, whenTrue: cs.whenTrue };
|
|
@@ -533,6 +548,14 @@ function lowerExprInner(lowerer, expr) {
|
|
|
533
548
|
return { kind: "jsOp", op: "arrLit", args, type: JSVAL, loc };
|
|
534
549
|
}
|
|
535
550
|
if (ts.isTypeOfExpression(expr)) {
|
|
551
|
+
if (isNodeModuleValue(lowerer, expr.expression)) {
|
|
552
|
+
return { kind: "strLit", value: "object", type: STRING, loc };
|
|
553
|
+
}
|
|
554
|
+
if (ts.isPropertyAccessExpression(expr.expression)) {
|
|
555
|
+
const presence = lowerPromiseThenPresence(lowerer, expr.expression, { kind: "strLit", value: "function", type: STRING, loc });
|
|
556
|
+
if (presence !== null)
|
|
557
|
+
return presence;
|
|
558
|
+
}
|
|
536
559
|
// `typeof queueMicrotask` / `typeof DOMException` on a STDLIB global
|
|
537
560
|
// whose declared type is callable or constructable: folds to
|
|
538
561
|
// "function" BEFORE the operand lowers — the identity-token story
|
|
@@ -570,7 +593,7 @@ function lowerExprInner(lowerer, expr) {
|
|
|
570
593
|
operand = operand.value;
|
|
571
594
|
}
|
|
572
595
|
const FOLD = {
|
|
573
|
-
f64: "number", string: "string", bool: "boolean", func: "function",
|
|
596
|
+
f64: "number", bigint: "bigint", string: "string", bool: "boolean", func: "function",
|
|
574
597
|
array: "object", object: "object", record: "object",
|
|
575
598
|
symbol: "symbol",
|
|
576
599
|
map: "object", set: "object", promise: "object", bytes: "object",
|
|
@@ -578,7 +601,7 @@ function lowerExprInner(lowerer, expr) {
|
|
|
578
601
|
undefinedT: "undefined", nullT: "object",
|
|
579
602
|
};
|
|
580
603
|
const folded = FOLD[operand.type.kind];
|
|
581
|
-
if (folded &&
|
|
604
|
+
if (folded && isSafeToDiscard(operand)) {
|
|
582
605
|
return { kind: "strLit", value: folded, type: STRING, loc };
|
|
583
606
|
}
|
|
584
607
|
// A union operand: every arm's typeof answer is static, so the value
|
|
@@ -587,7 +610,7 @@ function lowerExprInner(lowerer, expr) {
|
|
|
587
610
|
// tests, so only side-effect-free reads compose — and when every arm
|
|
588
611
|
// agrees the whole expression folds to that one string (dropping only
|
|
589
612
|
// the pure read, the trust-the-checker bet lowerUnitComparison makes).
|
|
590
|
-
if (operand.type.kind === "union" &&
|
|
613
|
+
if (operand.type.kind === "union" && isSafeToRepeat(operand)) {
|
|
591
614
|
const def = lowerer.unions.get(operand.type.unionId);
|
|
592
615
|
const answers = def?.arms.map(typeofAnswer);
|
|
593
616
|
if (def && answers && answers.every((s) => s !== null)) {
|
|
@@ -614,6 +637,9 @@ function lowerExprInner(lowerer, expr) {
|
|
|
614
637
|
lowerer.unsupported("SC1090", expr, "typeof expressions on statically-typed values");
|
|
615
638
|
}
|
|
616
639
|
if (ts.isIdentifier(expr)) {
|
|
640
|
+
const moduleValue = lowerNodeModuleIdentifier(lowerer, expr);
|
|
641
|
+
if (moduleValue)
|
|
642
|
+
return moduleValue;
|
|
617
643
|
if (lowerer.isSelfReference(expr)) {
|
|
618
644
|
return { kind: "selfRef", type: lowerer.ctx.selfType, loc };
|
|
619
645
|
}
|
|
@@ -944,7 +970,6 @@ function lowerExprInner(lowerer, expr) {
|
|
|
944
970
|
// The families with a WHY: each hint states what makes the
|
|
945
971
|
// surface genuinely non-static (or what to use instead).
|
|
946
972
|
const globalHints = {
|
|
947
|
-
BigInt: "arbitrary-precision integers have no static representation — f64 is the one number type (Number.MAX_SAFE_INTEGER bounds exact integers)",
|
|
948
973
|
Proxy: "property-access metaprogramming has no static lowering (every property read must resolve at compile time)",
|
|
949
974
|
Reflect: "reflective property access has no static lowering — read and call members directly",
|
|
950
975
|
Intl: "locale- and ICU-backed behavior lives outside the static runtime (the localeCompare stance: code-unit order, no collation/locale data) — what lowers: the composed new Intl.NumberFormat(\"en-US\").format(x) and x.toLocaleString(\"en-US\") with default options",
|
|
@@ -1276,8 +1301,8 @@ function lowerExprInner(lowerer, expr) {
|
|
|
1276
1301
|
// The tail of an optional chain whose `?.` sits deeper
|
|
1277
1302
|
// (`x?.trim().toLowerCase()`): the whole tail short-circuits with
|
|
1278
1303
|
// the guard, so it lowers as one chain (dyn/island receivers keep
|
|
1279
|
-
// their own undefined-propagating reads — see
|
|
1280
|
-
if (
|
|
1304
|
+
// their own undefined-propagating reads — see isOptionalChainTail).
|
|
1305
|
+
if (isOptionalChainTail(lowerer, expr))
|
|
1281
1306
|
return lowerer.lowerOptionalChain(expr);
|
|
1282
1307
|
return lowerer.lowerCall(expr);
|
|
1283
1308
|
}
|
|
@@ -1325,6 +1350,17 @@ function lowerExprInner(lowerer, expr) {
|
|
|
1325
1350
|
if (isRequireMainFilename(lowerer, expr)) {
|
|
1326
1351
|
return { kind: "strLit", value: lowerer.entry.fileName, type: STRING, loc };
|
|
1327
1352
|
}
|
|
1353
|
+
const requireMain = lowerRequireMainProperty(lowerer, expr);
|
|
1354
|
+
if (requireMain)
|
|
1355
|
+
return requireMain;
|
|
1356
|
+
const moduleProperty = lowerNodeModuleProperty(lowerer, expr);
|
|
1357
|
+
if (moduleProperty)
|
|
1358
|
+
return moduleProperty;
|
|
1359
|
+
if (expr.questionDotToken && expr.name.text === "electron") {
|
|
1360
|
+
const processProperty = lowerer.lowerProcessProperty(expr);
|
|
1361
|
+
if (processProperty)
|
|
1362
|
+
return processProperty;
|
|
1363
|
+
}
|
|
1328
1364
|
// Optional chaining `a?.b`: the guard lowers here (a tag test around
|
|
1329
1365
|
// the plain property lowering below); the handled marker keeps this
|
|
1330
1366
|
// re-entrant dispatch from looping.
|
|
@@ -1333,7 +1369,7 @@ function lowerExprInner(lowerer, expr) {
|
|
|
1333
1369
|
}
|
|
1334
1370
|
// `a?.b.c` — the tail of a chain whose token sits deeper: the whole
|
|
1335
1371
|
// tail short-circuits with the guard.
|
|
1336
|
-
if (!expr.questionDotToken &&
|
|
1372
|
+
if (!expr.questionDotToken && isOptionalChainTail(lowerer, expr)) {
|
|
1337
1373
|
return lowerer.lowerOptionalChain(expr);
|
|
1338
1374
|
}
|
|
1339
1375
|
// Island receiver: o.x is an engine property read (getProp may throw
|
|
@@ -1662,7 +1698,7 @@ function lowerExprInner(lowerer, expr) {
|
|
|
1662
1698
|
const recv = lowerer.lowerExpr(expr.expression);
|
|
1663
1699
|
if (recv.type.kind === "dyn") {
|
|
1664
1700
|
const key = { kind: "strLit", value: expr.name.text, type: STRING, loc: locOf(expr.name) };
|
|
1665
|
-
const opt =
|
|
1701
|
+
const opt = hasOptionalChainGuard(expr.expression);
|
|
1666
1702
|
return lowerer.maybeNarrow({ kind: "dynKeyGet", key, ...(opt ? { optional: true } : {}), value: recv, type: DYN, loc }, expr);
|
|
1667
1703
|
}
|
|
1668
1704
|
}
|
|
@@ -1676,7 +1712,7 @@ function lowerExprInner(lowerer, expr) {
|
|
|
1676
1712
|
// `f.call` as a value). JS files only; TS keeps the fence.
|
|
1677
1713
|
if (!["apply", "bind", "call", "toString", "constructor", "prototype", "caller", "arguments"].includes(expr.name.text) &&
|
|
1678
1714
|
isJsSourceFile(expr.getSourceFile())) {
|
|
1679
|
-
const probed =
|
|
1715
|
+
const probed = tryLowerExpression(lowerer, expr.expression);
|
|
1680
1716
|
if (probed?.type.kind === "func" &&
|
|
1681
1717
|
canBoxFuncIntoDyn(probed.type, (id) => lowerer.shapes.get(id), (id) => lowerer.unions.get(id))) {
|
|
1682
1718
|
const fnName = jsFuncNameOf(expr.expression);
|
|
@@ -1741,7 +1777,7 @@ function lowerExprInner(lowerer, expr) {
|
|
|
1741
1777
|
type: STRING,
|
|
1742
1778
|
loc: locOf(expr.name),
|
|
1743
1779
|
};
|
|
1744
|
-
const opt =
|
|
1780
|
+
const opt = hasOptionalChainGuard(expr.expression);
|
|
1745
1781
|
return lowerer.maybeNarrow({
|
|
1746
1782
|
kind: "dynKeyGet",
|
|
1747
1783
|
key,
|
|
@@ -1801,7 +1837,7 @@ function lowerExprInner(lowerer, expr) {
|
|
|
1801
1837
|
// validated dynCheck as usual.
|
|
1802
1838
|
if (recvLowered.type.kind === "dyn") {
|
|
1803
1839
|
const key = { kind: "strLit", value: expr.name.text, type: STRING, loc: locOf(expr.name) };
|
|
1804
|
-
const opt =
|
|
1840
|
+
const opt = hasOptionalChainGuard(expr.expression);
|
|
1805
1841
|
return lowerer.maybeNarrow({ kind: "dynKeyGet", key, ...(opt ? { optional: true } : {}), value: recvLowered, type: DYN, loc }, expr);
|
|
1806
1842
|
}
|
|
1807
1843
|
// The lowered receiver is a RECORD the checker spelled wider —
|
|
@@ -1868,9 +1904,6 @@ function importMetaProperty(lowerer, expr) {
|
|
|
1868
1904
|
return null;
|
|
1869
1905
|
const sf = expr.getSourceFile();
|
|
1870
1906
|
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
1907
|
switch (expr.name.text) {
|
|
1875
1908
|
case "url":
|
|
1876
1909
|
return {
|
|
@@ -2437,7 +2470,7 @@ export function lowerUnitComparison(lowerer, left, right, negated, loc) {
|
|
|
2437
2470
|
// callback's effects are observable) rides one throwaway tag
|
|
2438
2471
|
// test; droppable reads fold to the bare literal.
|
|
2439
2472
|
const answer = { kind: "boolLit", value: negated, type: BOOL, loc };
|
|
2440
|
-
if (
|
|
2473
|
+
if (isSafeToDiscard(other))
|
|
2441
2474
|
return answer;
|
|
2442
2475
|
const evalOnce = {
|
|
2443
2476
|
kind: "unionIsTag", unionId: other.type.unionId, tag: 0, negated: false, value: other, type: BOOL, loc,
|
|
@@ -2545,7 +2578,7 @@ function lowerLooseNullCompare(lowerer, expr, loc) {
|
|
|
2545
2578
|
// backend emits it per test — restricted to re-emittable pure reads;
|
|
2546
2579
|
// anything effectful gets its own actionable fence (the generic
|
|
2547
2580
|
// SC1040 hint would wrongly suggest `== null` as the fix).
|
|
2548
|
-
if (!
|
|
2581
|
+
if (!isSafeToRepeat(other)) {
|
|
2549
2582
|
lowerer.unsupported("SC1090", expr, `'== null' / '!= null' on a '${lowerer.fmt(ut)}' value that isn't a plain read ` +
|
|
2550
2583
|
`(both unit arms need the operand twice — bind it to a const first)`);
|
|
2551
2584
|
}
|
|
@@ -2575,88 +2608,6 @@ function lowerLooseNullCompare(lowerer, expr, loc) {
|
|
|
2575
2608
|
// `== null` is statically false, `!= null` statically true.
|
|
2576
2609
|
return { kind: "boolLit", value: negated, type: BOOL, loc };
|
|
2577
2610
|
}
|
|
2578
|
-
/** Safe to EMIT twice: plain reads with no side effects — local/global
|
|
2579
|
-
* reads and record/class field reads over such (accessor properties
|
|
2580
|
-
* lower to getter CALLS, so they never appear as field reads). Used by
|
|
2581
|
-
* the two-unit-arm `== null` composition, the union `typeof` forms, and
|
|
2582
|
-
* the union-switch desugar, whose operands ride several tests. */
|
|
2583
|
-
/** A static expression whose evaluation is unobservable — safe to DROP
|
|
2584
|
-
* (typeof's constant fold: JS evaluates the operand, but when nothing in
|
|
2585
|
-
* it can throw, call, write, or allocate observably, folding it away
|
|
2586
|
-
* changes nothing). Strictly wider than pureReemittable: numeric
|
|
2587
|
-
* arithmetic (f64 ops never throw — division by zero is Infinity),
|
|
2588
|
-
* string concatenation, logical/ternary composition, and unit/literal
|
|
2589
|
-
* leaves compose; anything with a call, an island op, an index read (can
|
|
2590
|
-
* trap), or an unknown kind answers false. */
|
|
2591
|
-
export function droppableStatic(e) {
|
|
2592
|
-
switch (e.kind) {
|
|
2593
|
-
case "numLit":
|
|
2594
|
-
case "strLit":
|
|
2595
|
-
case "boolLit":
|
|
2596
|
-
case "unitLit":
|
|
2597
|
-
return true;
|
|
2598
|
-
case "bin":
|
|
2599
|
-
return droppableStatic(e.left) && droppableStatic(e.right);
|
|
2600
|
-
case "unary":
|
|
2601
|
-
return droppableStatic(e.operand);
|
|
2602
|
-
case "logical":
|
|
2603
|
-
return droppableStatic(e.left) && droppableStatic(e.right);
|
|
2604
|
-
case "ternary":
|
|
2605
|
-
return droppableStatic(e.cond) && droppableStatic(e.then) && droppableStatic(e.else_);
|
|
2606
|
-
case "strConcat":
|
|
2607
|
-
return droppableStatic(e.left) && droppableStatic(e.right);
|
|
2608
|
-
case "toBool":
|
|
2609
|
-
return droppableStatic(e.operand);
|
|
2610
|
-
case "unionIsTag":
|
|
2611
|
-
return droppableStatic(e.value);
|
|
2612
|
-
case "unionWrap":
|
|
2613
|
-
return droppableStatic(e.value);
|
|
2614
|
-
// Fresh allocations are unobservable too — only their PIECES can
|
|
2615
|
-
// carry effects (a spread re-reads its source: pure; element and
|
|
2616
|
-
// field initializers recurse).
|
|
2617
|
-
case "arrayLit":
|
|
2618
|
-
return e.elems.every(droppableStatic);
|
|
2619
|
-
case "recordLit":
|
|
2620
|
-
return e.fields.every((f) => droppableStatic(f.value));
|
|
2621
|
-
case "recordClone":
|
|
2622
|
-
return droppableStatic(e.source) && e.overrides.every((f) => droppableStatic(f.value));
|
|
2623
|
-
case "closure":
|
|
2624
|
-
return true;
|
|
2625
|
-
default:
|
|
2626
|
-
return pureReemittable(e);
|
|
2627
|
-
}
|
|
2628
|
-
}
|
|
2629
|
-
export function pureReemittable(e) {
|
|
2630
|
-
if (e.kind === "varRef")
|
|
2631
|
-
return true;
|
|
2632
|
-
if (e.kind === "recordGet" || e.kind === "fieldGet")
|
|
2633
|
-
return pureReemittable(e.obj);
|
|
2634
|
-
// The trust-the-checker narrowing bridges over a pure read: extraction
|
|
2635
|
-
// and downcast are reads too (the +1 on ref payloads is RC bookkeeping,
|
|
2636
|
-
// not an observable effect — each emission owns its own copy).
|
|
2637
|
-
if (e.kind === "unionNarrow" || e.kind === "downcast")
|
|
2638
|
-
return pureReemittable(e.value);
|
|
2639
|
-
return false;
|
|
2640
|
-
}
|
|
2641
|
-
/** A side-effect-free CONDITION: the ToBoolean/test wrappers lowerCondition
|
|
2642
|
-
* builds over pure reads (evaluating one early or twice changes nothing
|
|
2643
|
-
* observable). Used by the island-literal conditional spread, whose
|
|
2644
|
-
* desugar hoists the condition ahead of the properties. */
|
|
2645
|
-
function pureCondExpr(e) {
|
|
2646
|
-
if (e.kind === "boolLit")
|
|
2647
|
-
return true;
|
|
2648
|
-
if (e.kind === "toBool")
|
|
2649
|
-
return pureReemittable(e.operand);
|
|
2650
|
-
if (e.kind === "unionIsTag" || e.kind === "dynTest")
|
|
2651
|
-
return pureReemittable(e.value);
|
|
2652
|
-
if (e.kind === "unary" && e.op === "!")
|
|
2653
|
-
return pureCondExpr(e.operand);
|
|
2654
|
-
if (e.kind === "logical")
|
|
2655
|
-
return pureCondExpr(e.left) && pureCondExpr(e.right);
|
|
2656
|
-
if (e.kind === "jsOp" && (e.op === "truthy" || e.op === "not"))
|
|
2657
|
-
return e.args.every(pureReemittable);
|
|
2658
|
-
return pureReemittable(e);
|
|
2659
|
-
}
|
|
2660
2611
|
/** `a ?? b` — JS-exact nullish coalescing: ONLY null/undefined take the
|
|
2661
2612
|
* default (0, "", and false do not), and the right side evaluates lazily.
|
|
2662
2613
|
* On a unit-armed union left this is the `nullish` node (a runtime tag
|
|
@@ -2705,6 +2656,42 @@ export function lowerNullishCoalesce(lowerer, expr, loc) {
|
|
|
2705
2656
|
const right = lowerer.lowerExprExpecting(expr.right, type);
|
|
2706
2657
|
return { kind: "nullish", left, right, type, loc };
|
|
2707
2658
|
}
|
|
2659
|
+
if (type.kind !== "union" && rest.length > 1 && rest.some((arm) => typeEquals(arm, type))) {
|
|
2660
|
+
const helper = lowerer.narrowedArmHelper(left.type.unionId, type, loc);
|
|
2661
|
+
if (helper !== null) {
|
|
2662
|
+
const stmts = [];
|
|
2663
|
+
let stable = left;
|
|
2664
|
+
if (!isSafeToRepeat(left)) {
|
|
2665
|
+
const local = lowerer.declareHiddenLocal("%nullish", left.type);
|
|
2666
|
+
stmts.push({ kind: "varDecl", localId: local.id, init: left, loc });
|
|
2667
|
+
stable = varRef(local.id, left.type, loc);
|
|
2668
|
+
}
|
|
2669
|
+
const unitTags = def.arms
|
|
2670
|
+
.map((arm, tag) => ({ arm, tag }))
|
|
2671
|
+
.filter(({ arm }) => isUnitType(arm))
|
|
2672
|
+
.map(({ tag }) => tag);
|
|
2673
|
+
let isNullish = { kind: "unionIsTag", unionId: left.type.unionId, tag: unitTags[0], negated: false, value: stable, type: BOOL, loc };
|
|
2674
|
+
for (const tag of unitTags.slice(1)) {
|
|
2675
|
+
isNullish = {
|
|
2676
|
+
kind: "logical",
|
|
2677
|
+
op: "||",
|
|
2678
|
+
left: isNullish,
|
|
2679
|
+
right: { kind: "unionIsTag", unionId: left.type.unionId, tag, negated: false, value: stable, type: BOOL, loc },
|
|
2680
|
+
type: BOOL,
|
|
2681
|
+
loc,
|
|
2682
|
+
};
|
|
2683
|
+
}
|
|
2684
|
+
const result = {
|
|
2685
|
+
kind: "ternary",
|
|
2686
|
+
cond: isNullish,
|
|
2687
|
+
then: lowerer.lowerExprExpecting(expr.right, type),
|
|
2688
|
+
else_: { kind: "call", callee: helper, args: [stable], type, loc },
|
|
2689
|
+
type,
|
|
2690
|
+
loc,
|
|
2691
|
+
};
|
|
2692
|
+
return stmts.length === 0 ? result : { kind: "seqExpr", stmts, result, type, loc };
|
|
2693
|
+
}
|
|
2694
|
+
}
|
|
2708
2695
|
// The RETAGGED shape — the default changes the result union (`s ??
|
|
2709
2696
|
// null` over `string | undefined` answers `null | string`): every
|
|
2710
2697
|
// non-unit arm of the left has a home in the result, so an interned
|
|
@@ -2715,9 +2702,72 @@ export function lowerNullishCoalesce(lowerer, expr, loc) {
|
|
|
2715
2702
|
if (type.kind === "union") {
|
|
2716
2703
|
const leftT = left.type;
|
|
2717
2704
|
const armPairs = rest.map((a) => ({ arm: a, src: lowerer.armTag(leftT.unionId, a), dst: lowerer.armTag(type.unionId, a) }));
|
|
2705
|
+
if (rest.length > 0 && armPairs.every((pair) => pair.src >= 0 && pair.dst >= 0)) {
|
|
2706
|
+
const stmts = [];
|
|
2707
|
+
let stable = left;
|
|
2708
|
+
if (!isSafeToRepeat(left)) {
|
|
2709
|
+
const local = lowerer.declareHiddenLocal("%nullish", leftT);
|
|
2710
|
+
stmts.push({ kind: "varDecl", localId: local.id, init: left, loc });
|
|
2711
|
+
stable = varRef(local.id, leftT, loc);
|
|
2712
|
+
}
|
|
2713
|
+
const unitTags = def.arms
|
|
2714
|
+
.map((arm, tag) => ({ arm, tag }))
|
|
2715
|
+
.filter(({ arm }) => isUnitType(arm))
|
|
2716
|
+
.map(({ tag }) => tag);
|
|
2717
|
+
let isNullish = {
|
|
2718
|
+
kind: "unionIsTag",
|
|
2719
|
+
unionId: leftT.unionId,
|
|
2720
|
+
tag: unitTags[0],
|
|
2721
|
+
negated: false,
|
|
2722
|
+
value: stable,
|
|
2723
|
+
type: BOOL,
|
|
2724
|
+
loc,
|
|
2725
|
+
};
|
|
2726
|
+
for (const tag of unitTags.slice(1)) {
|
|
2727
|
+
isNullish = {
|
|
2728
|
+
kind: "logical",
|
|
2729
|
+
op: "||",
|
|
2730
|
+
left: isNullish,
|
|
2731
|
+
right: { kind: "unionIsTag", unionId: leftT.unionId, tag, negated: false, value: stable, type: BOOL, loc },
|
|
2732
|
+
type: BOOL,
|
|
2733
|
+
loc,
|
|
2734
|
+
};
|
|
2735
|
+
}
|
|
2736
|
+
let present = null;
|
|
2737
|
+
for (let i = armPairs.length - 1; i >= 0; i--) {
|
|
2738
|
+
const pair = armPairs[i];
|
|
2739
|
+
const wrapped = {
|
|
2740
|
+
kind: "unionWrap",
|
|
2741
|
+
unionId: type.unionId,
|
|
2742
|
+
tag: pair.dst,
|
|
2743
|
+
value: { kind: "unionNarrow", unionId: leftT.unionId, tag: pair.src, value: stable, type: pair.arm, loc },
|
|
2744
|
+
type,
|
|
2745
|
+
loc,
|
|
2746
|
+
};
|
|
2747
|
+
present = present === null
|
|
2748
|
+
? wrapped
|
|
2749
|
+
: {
|
|
2750
|
+
kind: "ternary",
|
|
2751
|
+
cond: { kind: "unionIsTag", unionId: leftT.unionId, tag: pair.src, negated: false, value: stable, type: BOOL, loc },
|
|
2752
|
+
then: wrapped,
|
|
2753
|
+
else_: present,
|
|
2754
|
+
type,
|
|
2755
|
+
loc,
|
|
2756
|
+
};
|
|
2757
|
+
}
|
|
2758
|
+
const result = {
|
|
2759
|
+
kind: "ternary",
|
|
2760
|
+
cond: isNullish,
|
|
2761
|
+
then: lowerer.lowerExprExpecting(expr.right, type),
|
|
2762
|
+
else_: present,
|
|
2763
|
+
type,
|
|
2764
|
+
loc,
|
|
2765
|
+
};
|
|
2766
|
+
return stmts.length === 0 ? result : { kind: "seqExpr", stmts, result, type, loc };
|
|
2767
|
+
}
|
|
2718
2768
|
if (armPairs.every((p) => p.src >= 0 && p.dst >= 0)) {
|
|
2719
2769
|
const right = lowerer.lowerExprExpecting(expr.right, type);
|
|
2720
|
-
if (
|
|
2770
|
+
if (isSafeToDiscard(right)) {
|
|
2721
2771
|
const helper = nullishRetagHelper(lowerer, leftT, type, loc);
|
|
2722
2772
|
return { kind: "call", callee: helper, args: [left, right], type, loc };
|
|
2723
2773
|
}
|
|
@@ -2784,384 +2834,6 @@ function nullishRetagHelper(lowerer, leftT, resT, loc) {
|
|
|
2784
2834
|
});
|
|
2785
2835
|
return name;
|
|
2786
2836
|
}
|
|
2787
|
-
/** One optional-chain STEP: `a?.b`, `a?.m(...)`, `a?.[i]` (the token on
|
|
2788
|
-
* the member access) and `f?.()` (the token on the call). The receiver
|
|
2789
|
-
* lowers once; when it is a unit-armed union with ONE non-unit arm, the
|
|
2790
|
-
* member/call lowers exactly as its non-optional spelling would — the
|
|
2791
|
-
* receiver node reads back as the chain's bound narrowed value
|
|
2792
|
-
* (chainRecv) and types as its non-nullish type — and the whole thing
|
|
2793
|
-
* becomes an optChain node: tag test, undefined on the unit path (JS:
|
|
2794
|
-
* null receivers still yield undefined), the body lazily otherwise,
|
|
2795
|
-
* argument side effects included. A receiver the checker types
|
|
2796
|
-
* never-nullish makes `?.` behave exactly like `.` — the guard folds
|
|
2797
|
-
* away and the plain lowering is the value (trust-the-checker, like
|
|
2798
|
-
* ??'s fold). Multi-step TAILS (`a?.b.c`, `x?.trim().toLowerCase()`)
|
|
2799
|
-
* short-circuit whole: the guarded member step is the chain's dot and
|
|
2800
|
-
* every later step lowers inside the guard, checker-narrowed non-nullish
|
|
2801
|
-
* (see chainTailDot); sub-union receivers are fenced with rewrite
|
|
2802
|
-
* hints. */
|
|
2803
|
-
/** The unhandled `?.`-carrying MEMBER step in this node's receiver
|
|
2804
|
-
* spine, when the node is the TAIL of an optional chain whose token sits
|
|
2805
|
-
* deeper — `x?.trim().toLowerCase()` reads nothing past the guard when x
|
|
2806
|
-
* is nullish, so the whole tail must lower inside it. Walks
|
|
2807
|
-
* property/element accesses and call steps only (parens and `!` break
|
|
2808
|
-
* the chain per the grammar: `(a?.b).c` throws on undefined in JS);
|
|
2809
|
-
* the node's OWN token — and a call's immediate callee token — are the
|
|
2810
|
-
* plain entries' cases, not tails, and handled markers make the chain
|
|
2811
|
-
* lowering's re-dispatch walk past its own step. `f?.()` steps stay out
|
|
2812
|
-
* (a call carrying the token has no member step to re-enter — the
|
|
2813
|
-
* split-it fence keeps them). */
|
|
2814
|
-
function chainTailDot(lowerer, expr) {
|
|
2815
|
-
let cur = expr;
|
|
2816
|
-
for (;;) {
|
|
2817
|
-
// An active chain's bound receiver read is a LEAF — its own token
|
|
2818
|
-
// was consumed by the chain that bound it.
|
|
2819
|
-
if (lowerer.chainRecvByNode.has(cur))
|
|
2820
|
-
return null;
|
|
2821
|
-
if (ts.isCallExpression(cur)) {
|
|
2822
|
-
// `f?.()` steps route through their own entry (or are being
|
|
2823
|
-
// handled); a deeper token under one is that chain's business.
|
|
2824
|
-
if (cur.questionDotToken)
|
|
2825
|
-
return null;
|
|
2826
|
-
cur = cur.expression;
|
|
2827
|
-
continue;
|
|
2828
|
-
}
|
|
2829
|
-
if (ts.isPropertyAccessExpression(cur) || ts.isElementAccessExpression(cur)) {
|
|
2830
|
-
if (cur.questionDotToken) {
|
|
2831
|
-
// The node's own token and a call's immediate callee token are
|
|
2832
|
-
// the plain entries' cases; a handled one is mid-re-dispatch
|
|
2833
|
-
// (its receiver read, further down, ends the walk). The walk
|
|
2834
|
-
// NEVER continues past an unhandled deeper token: that guard is
|
|
2835
|
-
// where this tail's chain enters — anything below it belongs to
|
|
2836
|
-
// the receiver's own (nested) chain.
|
|
2837
|
-
if (cur === expr || lowerer.chainHandled.has(cur)) {
|
|
2838
|
-
cur = cur.expression;
|
|
2839
|
-
continue;
|
|
2840
|
-
}
|
|
2841
|
-
if (ts.isCallExpression(expr) && expr.expression === cur)
|
|
2842
|
-
return null;
|
|
2843
|
-
return cur;
|
|
2844
|
-
}
|
|
2845
|
-
cur = cur.expression;
|
|
2846
|
-
continue;
|
|
2847
|
-
}
|
|
2848
|
-
return null;
|
|
2849
|
-
}
|
|
2850
|
-
}
|
|
2851
|
-
/** True when `expr` is `require.main.filename` (either dot optional) on
|
|
2852
|
-
* the AMBIENT CommonJS require — the entry-module identity read. The
|
|
2853
|
-
* value is the ENTRY file's path, a compile-time constant like
|
|
2854
|
-
* __filename; ESM files stay out (Node never defines require there —
|
|
2855
|
-
* the ambient symbol would not resolve anyway). Shared by the property
|
|
2856
|
-
* lowering (the fold) and lowerStringMethodCall's receiver gate (the
|
|
2857
|
-
* checker types the chain `string | undefined`, but the folded receiver
|
|
2858
|
-
* IS a string). */
|
|
2859
|
-
export function isRequireMainFilename(lowerer, expr) {
|
|
2860
|
-
if (!ts.isPropertyAccessExpression(expr) || expr.name.text !== "filename")
|
|
2861
|
-
return false;
|
|
2862
|
-
const main = expr.expression;
|
|
2863
|
-
if (!ts.isPropertyAccessExpression(main) || main.name.text !== "main")
|
|
2864
|
-
return false;
|
|
2865
|
-
if (!lowerer.isStdlibGlobal(main.expression, "require"))
|
|
2866
|
-
return false;
|
|
2867
|
-
return !isNodeEsmFile(expr.getSourceFile());
|
|
2868
|
-
}
|
|
2869
|
-
/** True when `expr` is the tail of an optional chain the STATIC chain
|
|
2870
|
-
* machinery should short-circuit whole: an unhandled deeper `?.` whose
|
|
2871
|
-
* guarded receiver lowers as a unit-armed union. Dyn ('unknown') and
|
|
2872
|
-
* island ('any') receivers answer their tails through their own
|
|
2873
|
-
* undefined-propagating reads and stay out; never-nullish receivers fold
|
|
2874
|
-
* at the token's own entry. */
|
|
2875
|
-
function chainTailClaimed(lowerer, expr) {
|
|
2876
|
-
const tail = chainTailDot(lowerer, expr);
|
|
2877
|
-
if (!tail)
|
|
2878
|
-
return false;
|
|
2879
|
-
const recvT = lowerer.mapTypeOf(lowerer.typeOf(tail.expression));
|
|
2880
|
-
if (recvT?.kind !== "union")
|
|
2881
|
-
return false;
|
|
2882
|
-
const def = lowerer.unions.get(recvT.unionId);
|
|
2883
|
-
return !!def && def.arms.some(isUnitType);
|
|
2884
|
-
}
|
|
2885
|
-
/** True when an EARLIER step of this access chain carries `?.` — JS
|
|
2886
|
-
* short-circuits the whole tail (`a?.b.c` reads nothing when a is
|
|
2887
|
-
* nullish), so a dyn tail read must answer undefined instead of
|
|
2888
|
-
* throwing. Walks the receiver spine only; the argument of the access
|
|
2889
|
-
* itself is not part of the guard. */
|
|
2890
|
-
function chainGuardedByQuestionDot(expr) {
|
|
2891
|
-
let cur = expr;
|
|
2892
|
-
for (;;) {
|
|
2893
|
-
if (ts.isParenthesizedExpression(cur) || ts.isNonNullExpression(cur)) {
|
|
2894
|
-
cur = cur.expression;
|
|
2895
|
-
continue;
|
|
2896
|
-
}
|
|
2897
|
-
if (ts.isPropertyAccessExpression(cur) ||
|
|
2898
|
-
ts.isElementAccessExpression(cur) ||
|
|
2899
|
-
ts.isCallExpression(cur)) {
|
|
2900
|
-
if (cur.questionDotToken)
|
|
2901
|
-
return true;
|
|
2902
|
-
cur = cur.expression;
|
|
2903
|
-
continue;
|
|
2904
|
-
}
|
|
2905
|
-
return false;
|
|
2906
|
-
}
|
|
2907
|
-
}
|
|
2908
|
-
export function lowerOptionalChain(lowerer, expr) {
|
|
2909
|
-
const loc = locOf(expr);
|
|
2910
|
-
// The node CARRYING the ?. token and the receiver expression it guards.
|
|
2911
|
-
let dotNode;
|
|
2912
|
-
let recvNode;
|
|
2913
|
-
if (ts.isCallExpression(expr) && expr.questionDotToken) {
|
|
2914
|
-
dotNode = expr; // f?.()
|
|
2915
|
-
recvNode = expr.expression;
|
|
2916
|
-
}
|
|
2917
|
-
else if (ts.isCallExpression(expr) &&
|
|
2918
|
-
ts.isPropertyAccessExpression(expr.expression) &&
|
|
2919
|
-
expr.expression.questionDotToken) {
|
|
2920
|
-
dotNode = expr.expression; // a?.m()
|
|
2921
|
-
recvNode = expr.expression.expression;
|
|
2922
|
-
}
|
|
2923
|
-
else if ((ts.isPropertyAccessExpression(expr) || ts.isElementAccessExpression(expr)) &&
|
|
2924
|
-
expr.questionDotToken) {
|
|
2925
|
-
dotNode = expr; // a?.b / a?.[i]
|
|
2926
|
-
recvNode = expr.expression;
|
|
2927
|
-
}
|
|
2928
|
-
else {
|
|
2929
|
-
// TAIL entry: the token sits deeper in the receiver spine and JS
|
|
2930
|
-
// short-circuits the WHOLE tail with it (`x?.trim().toLowerCase()`
|
|
2931
|
-
// reads nothing when x is nullish). The guarded member step becomes
|
|
2932
|
-
// the chain's dot; every step above it lowers inside the guard, its
|
|
2933
|
-
// checker type narrowed non-nullish below (the tag test proved it).
|
|
2934
|
-
const tail = chainTailDot(lowerer, expr);
|
|
2935
|
-
if (!tail) {
|
|
2936
|
-
// A `f?.()` step under a member tail, or no token at all (a
|
|
2937
|
-
// dispatch bug): short-circuiting those is not modeled.
|
|
2938
|
-
lowerer.unsupported("SC1090", expr, "multi-step optional chains (split them: const v = a?.b; then use v?.c)");
|
|
2939
|
-
}
|
|
2940
|
-
dotNode = tail;
|
|
2941
|
-
recvNode = tail.expression;
|
|
2942
|
-
}
|
|
2943
|
-
const loweredReceiver = lowerer.lowerExpr(recvNode);
|
|
2944
|
-
const receiver = lowerer.runtimeOptionalSourceValue(recvNode, loweredReceiver) ?? loweredReceiver;
|
|
2945
|
-
if (receiver.type.kind === "dyn") {
|
|
2946
|
-
// `pkg?.name` / `pkg?.scripts?.[k]` on a JSON.parse result: dyn
|
|
2947
|
-
// represents undefined directly, so the chain step IS the keyed
|
|
2948
|
-
// read with the optional (unit-answers-undefined) policy — no
|
|
2949
|
-
// optChain wrapper needed; nested steps compose the same way.
|
|
2950
|
-
if (dotNode === expr && ts.isPropertyAccessExpression(expr)) {
|
|
2951
|
-
const key = { kind: "strLit", value: expr.name.text, type: STRING, loc: locOf(expr.name) };
|
|
2952
|
-
return lowerer.maybeNarrow({ kind: "dynKeyGet", key, optional: true, value: receiver, type: DYN, loc }, expr);
|
|
2953
|
-
}
|
|
2954
|
-
if (dotNode === expr && ts.isElementAccessExpression(expr)) {
|
|
2955
|
-
const key = lowerer.lowerExpr(expr.argumentExpression);
|
|
2956
|
-
if (key.type.kind === "string") {
|
|
2957
|
-
return lowerer.maybeNarrow({ kind: "dynKeyGet", key, optional: true, value: receiver, type: DYN, loc }, expr);
|
|
2958
|
-
}
|
|
2959
|
-
// NUMBER-typed indices (`entries?.[0]`): the property key is
|
|
2960
|
-
// ToString(i), exactly JS — the canonical number text answers
|
|
2961
|
-
// array indices in the dyn helper, anything else (fractions,
|
|
2962
|
-
// negatives, NaN) reads as an absent key.
|
|
2963
|
-
if (key.type.kind === "f64") {
|
|
2964
|
-
const skey = { kind: "toString", operand: key, type: STRING, loc: key.loc };
|
|
2965
|
-
return lowerer.maybeNarrow({ kind: "dynKeyGet", key: skey, optional: true, value: receiver, type: DYN, loc }, expr);
|
|
2966
|
-
}
|
|
2967
|
-
}
|
|
2968
|
-
// The METHOD-call step (`rawName?.match(re)` on a dyn value): a
|
|
2969
|
-
// nullish receiver short-circuits to the undefined dyn singleton
|
|
2970
|
-
// (dyn represents undefined directly); anything else runs the
|
|
2971
|
-
// VALIDATED dynamic dispatch — the same one the truthy-guarded
|
|
2972
|
-
// spelling (`v ? v.match(...) : null`) compiles to, Node-shaped
|
|
2973
|
-
// TypeError on a kind mismatch included. The dispatch's static
|
|
2974
|
-
// result converts back into the checked-dynamic tree (the chain's checker type is
|
|
2975
|
-
// the error-any world's `any`, so dyn IS its representation).
|
|
2976
|
-
if (ts.isCallExpression(expr) &&
|
|
2977
|
-
ts.isPropertyAccessExpression(dotNode) &&
|
|
2978
|
-
dotNode === expr.expression) {
|
|
2979
|
-
const m = dotNode.name.text;
|
|
2980
|
-
const id = `chain.${lowerer.chainCounter++}`;
|
|
2981
|
-
const recvRef = { kind: "chainRecv", id, type: DYN, loc: locOf(recvNode) };
|
|
2982
|
-
lowerer.chainRecvByNode.set(recvNode, recvRef);
|
|
2983
|
-
lowerer.chainHandled.add(dotNode);
|
|
2984
|
-
let body;
|
|
2985
|
-
try {
|
|
2986
|
-
body = lowerer.lowerExpr(expr);
|
|
2987
|
-
}
|
|
2988
|
-
finally {
|
|
2989
|
-
lowerer.chainRecvByNode.delete(recvNode);
|
|
2990
|
-
lowerer.chainHandled.delete(dotNode);
|
|
2991
|
-
}
|
|
2992
|
-
if (body.type.kind !== "dyn") {
|
|
2993
|
-
if (body.type.kind !== "void" && !lowerer.dynConvertible(body.type)) {
|
|
2994
|
-
lowerer.unsupported("SC1100", expr, `optional METHOD calls on 'unknown' values where the result ('${lowerer.fmt(body.type)}' ` +
|
|
2995
|
-
`from '.${m}(...)') has no dynamic representation`);
|
|
2996
|
-
}
|
|
2997
|
-
if (body.type.kind === "void") {
|
|
2998
|
-
return { kind: "optChain", id, receiver, body, type: VOID, loc };
|
|
2999
|
-
}
|
|
3000
|
-
body = { kind: "dynFrom", value: body, type: DYN, loc };
|
|
3001
|
-
}
|
|
3002
|
-
return { kind: "optChain", id, receiver, body, type: DYN, loc };
|
|
3003
|
-
}
|
|
3004
|
-
lowerer.unsupported("SC1100", expr, "optional chaining on 'unknown' values");
|
|
3005
|
-
}
|
|
3006
|
-
// An 'any' (island-handle) receiver: the nullish test asks the ENGINE
|
|
3007
|
-
// value at runtime — null/undefined short-circuit to the engine's
|
|
3008
|
-
// undefined (JS: null receivers yield undefined too), anything else
|
|
3009
|
-
// proceeds as the plain island operation with the receiver evaluated
|
|
3010
|
-
// once (argument side effects stay lazy, like every optChain). The
|
|
3011
|
-
// result is an island value again ('any' in, 'any' out).
|
|
3012
|
-
if (receiver.type.kind === "jsval") {
|
|
3013
|
-
const id = `chain.${lowerer.chainCounter++}`;
|
|
3014
|
-
const recvRef = { kind: "chainRecv", id, type: JSVAL, loc: locOf(recvNode) };
|
|
3015
|
-
if (dotNode === expr && ts.isCallExpression(expr)) {
|
|
3016
|
-
// `a.b?.(...)` — a MEMBER callee: JS calls it with `this` bound to
|
|
3017
|
-
// `a` and short-circuits a nullish member — exactly the engine's
|
|
3018
|
-
// own `o.name?.()` (optCallMethod: the RECEIVER is `a`, evaluated
|
|
3019
|
-
// once through the chain; a nullish `a` itself short-circuits
|
|
3020
|
-
// earlier like any optional chain). Computed members keep a fence.
|
|
3021
|
-
if (ts.isPropertyAccessExpression(recvNode) || ts.isElementAccessExpression(recvNode)) {
|
|
3022
|
-
if (ts.isPropertyAccessExpression(recvNode) &&
|
|
3023
|
-
!recvNode.questionDotToken) {
|
|
3024
|
-
const obj = lowerer.lowerExpr(recvNode.expression);
|
|
3025
|
-
if (obj.type.kind !== "jsval")
|
|
3026
|
-
lowerer.badType(recvNode.expression, lowerer.typeOf(recvNode.expression));
|
|
3027
|
-
const args = expr.arguments.map((a) => lowerer.jsvalIn(lowerer.lowerExpr(a), a));
|
|
3028
|
-
return { kind: "jsOp", op: "optCallMethod", name: recvNode.name.text, args: [obj, ...args], type: JSVAL, loc };
|
|
3029
|
-
}
|
|
3030
|
-
lowerer.unsupported("SC1090", expr, "optional calls of computed 'any' member values (a[k]?.() — bind the member to a const first)");
|
|
3031
|
-
}
|
|
3032
|
-
const args = expr.arguments.map((a) => lowerer.jsvalIn(lowerer.lowerExpr(a), a));
|
|
3033
|
-
const body = { kind: "jsOp", op: "callFn", args: [recvRef, ...args], type: JSVAL, loc };
|
|
3034
|
-
return { kind: "optChain", id, receiver, body, type: JSVAL, loc };
|
|
3035
|
-
}
|
|
3036
|
-
// Member forms (`x?.y`, `x?.y(...)`, `x?.[i]`): re-dispatch the plain
|
|
3037
|
-
// island lowering with the receiver node bound to the chain.
|
|
3038
|
-
lowerer.chainRecvByNode.set(recvNode, recvRef);
|
|
3039
|
-
lowerer.chainHandled.add(dotNode);
|
|
3040
|
-
let body;
|
|
3041
|
-
try {
|
|
3042
|
-
body = lowerer.lowerExpr(expr);
|
|
3043
|
-
}
|
|
3044
|
-
finally {
|
|
3045
|
-
lowerer.chainRecvByNode.delete(recvNode);
|
|
3046
|
-
lowerer.chainHandled.delete(dotNode);
|
|
3047
|
-
}
|
|
3048
|
-
if (body.type.kind !== "jsval")
|
|
3049
|
-
lowerer.badType(expr, lowerer.typeOf(expr));
|
|
3050
|
-
return { kind: "optChain", id, receiver, body, type: JSVAL, loc };
|
|
3051
|
-
}
|
|
3052
|
-
const def = receiver.type.kind === "union" ? lowerer.unions.get(receiver.type.unionId) : undefined;
|
|
3053
|
-
if (!def || !def.arms.some(isUnitType)) {
|
|
3054
|
-
// Never nullish: `?.` IS `.` — re-dispatch the plain lowering. The
|
|
3055
|
-
// receiver subtree above is discarded (lowering is pure IR
|
|
3056
|
-
// construction); the fresh dispatch lowers it again in place. The
|
|
3057
|
-
// checker may still SPELL the receiver nullable while the lowering
|
|
3058
|
-
// answers the plain value (`process.getuid?.()` is the POSIX
|
|
3059
|
-
// number), so narrow the node too — downstream receiver-typed
|
|
3060
|
-
// dispatch must agree with the lowered value, not the spelling.
|
|
3061
|
-
lowerer.chainHandled.add(dotNode);
|
|
3062
|
-
const hadNarrow = lowerer.chainNarrowedType.has(recvNode);
|
|
3063
|
-
if (!hadNarrow) {
|
|
3064
|
-
lowerer.chainNarrowedType.set(recvNode, lowerer.checker.getNonNullableType(lowerer.checker.getTypeAtLocation(recvNode)));
|
|
3065
|
-
}
|
|
3066
|
-
try {
|
|
3067
|
-
return lowerer.lowerExpr(expr);
|
|
3068
|
-
}
|
|
3069
|
-
finally {
|
|
3070
|
-
lowerer.chainHandled.delete(dotNode);
|
|
3071
|
-
if (!hadNarrow)
|
|
3072
|
-
lowerer.chainNarrowedType.delete(recvNode);
|
|
3073
|
-
}
|
|
3074
|
-
}
|
|
3075
|
-
const rest = def.arms.filter((a) => !isUnitType(a));
|
|
3076
|
-
if (rest.length !== 1) {
|
|
3077
|
-
lowerer.unsupported("SC1090", expr, `'?.' on '${lowerer.fmt(receiver.type)}' (the guarded receiver is a sub-union; check a discriminant field first)`);
|
|
3078
|
-
}
|
|
3079
|
-
const narrowed = rest[0];
|
|
3080
|
-
const id = `chain.${lowerer.chainCounter++}`;
|
|
3081
|
-
const recvRef = { kind: "chainRecv", id, type: narrowed, loc: locOf(recvNode) };
|
|
3082
|
-
// `f?.()`: the callee IS the guarded value — build the indirect call
|
|
3083
|
-
// directly (no member dispatch exists to re-enter).
|
|
3084
|
-
if (dotNode === expr && ts.isCallExpression(expr)) {
|
|
3085
|
-
if (narrowed.kind !== "func")
|
|
3086
|
-
lowerer.badType(recvNode, lowerer.typeOf(recvNode));
|
|
3087
|
-
const params = narrowed.params;
|
|
3088
|
-
const args = expr.arguments.map((a, i) => lowerer.lowerExprExpecting(a, params[i]));
|
|
3089
|
-
const body = { kind: "callValue", callee: recvRef, args, type: narrowed.ret, loc };
|
|
3090
|
-
return lowerer.finishOptionalChain(expr, id, receiver, body, loc);
|
|
3091
|
-
}
|
|
3092
|
-
// Member forms: re-dispatch the normal lowering with the receiver node
|
|
3093
|
-
// bound to the chain (reads as chainRecv, types as non-nullish).
|
|
3094
|
-
const narrowedTs = lowerer.checker.getNonNullableType(lowerer.checker.getTypeAtLocation(recvNode));
|
|
3095
|
-
lowerer.chainRecvByNode.set(recvNode, recvRef);
|
|
3096
|
-
lowerer.chainNarrowedType.set(recvNode, narrowedTs);
|
|
3097
|
-
lowerer.chainHandled.add(dotNode);
|
|
3098
|
-
// A TAIL entry's intermediate steps (`x?.trim()` inside
|
|
3099
|
-
// `x?.trim().toLowerCase()`) checker-type with the chain's `|
|
|
3100
|
-
// undefined` even though inside the guard they are proven non-nullish
|
|
3101
|
-
// — narrow each so the downstream method/member dispatch rides the
|
|
3102
|
-
// real receiver kind. Only nodes this chain registers are cleaned up.
|
|
3103
|
-
const tailSteps = [];
|
|
3104
|
-
if (dotNode !== expr && !(ts.isCallExpression(expr) && expr.expression === dotNode)) {
|
|
3105
|
-
for (let cur = expr; cur !== dotNode;) {
|
|
3106
|
-
const next = ts.isCallExpression(cur)
|
|
3107
|
-
? cur.expression
|
|
3108
|
-
: cur.expression;
|
|
3109
|
-
if (next === dotNode)
|
|
3110
|
-
break;
|
|
3111
|
-
if (!lowerer.chainNarrowedType.has(next)) {
|
|
3112
|
-
lowerer.chainNarrowedType.set(next, lowerer.checker.getNonNullableType(lowerer.checker.getTypeAtLocation(next)));
|
|
3113
|
-
tailSteps.push(next);
|
|
3114
|
-
}
|
|
3115
|
-
cur = next;
|
|
3116
|
-
}
|
|
3117
|
-
if (!lowerer.chainNarrowedType.has(dotNode)) {
|
|
3118
|
-
lowerer.chainNarrowedType.set(dotNode, lowerer.checker.getNonNullableType(lowerer.checker.getTypeAtLocation(dotNode)));
|
|
3119
|
-
tailSteps.push(dotNode);
|
|
3120
|
-
}
|
|
3121
|
-
}
|
|
3122
|
-
let body;
|
|
3123
|
-
try {
|
|
3124
|
-
body = lowerer.lowerExpr(expr);
|
|
3125
|
-
}
|
|
3126
|
-
finally {
|
|
3127
|
-
lowerer.chainRecvByNode.delete(recvNode);
|
|
3128
|
-
lowerer.chainNarrowedType.delete(recvNode);
|
|
3129
|
-
lowerer.chainHandled.delete(dotNode);
|
|
3130
|
-
for (const n of tailSteps)
|
|
3131
|
-
lowerer.chainNarrowedType.delete(n);
|
|
3132
|
-
}
|
|
3133
|
-
return lowerer.finishOptionalChain(expr, id, receiver, body, loc);
|
|
3134
|
-
}
|
|
3135
|
-
/** The optChain node around a lowered chain body: void bodies keep the
|
|
3136
|
-
* statement form (`cb?.();` — the checker's `void | undefined` result IS
|
|
3137
|
-
* void here); value bodies wrap into the checker's undefined-armed
|
|
3138
|
-
* result union. */
|
|
3139
|
-
export function finishOptionalChain(lowerer, expr, id, receiver, body, loc) {
|
|
3140
|
-
if (body.type.kind === "void") {
|
|
3141
|
-
return { kind: "optChain", id, receiver, body, type: VOID, loc };
|
|
3142
|
-
}
|
|
3143
|
-
// A dyn body (`pricing?.[key]` — an unknown-valued index-signature
|
|
3144
|
-
// read) stays dyn: `unknown | undefined` IS unknown, and dyn represents
|
|
3145
|
-
// undefined directly (the unit path yields the undefined dyn value).
|
|
3146
|
-
if (body.type.kind === "dyn") {
|
|
3147
|
-
return { kind: "optChain", id, receiver, body, type: DYN, loc };
|
|
3148
|
-
}
|
|
3149
|
-
let type = lowerer.irTypeOf(expr);
|
|
3150
|
-
const hiddenOptionalResult = (type.kind !== "union" || lowerer.armTag(type.unionId, UNDEFINED_T) < 0) &&
|
|
3151
|
-
receiver.type.kind === "union" && lowerer.armTag(receiver.type.unionId, UNDEFINED_T) >= 0 &&
|
|
3152
|
-
body.type.kind !== "generator" && body.type.kind !== "jsval"
|
|
3153
|
-
? lowerer.withUndefinedArmOf(body.type)
|
|
3154
|
-
: null;
|
|
3155
|
-
if (hiddenOptionalResult)
|
|
3156
|
-
type = hiddenOptionalResult;
|
|
3157
|
-
if (type.kind !== "union" || lowerer.armTag(type.unionId, UNDEFINED_T) < 0) {
|
|
3158
|
-
lowerer.unsupported("SC1090", expr, `'?.' where the result '${lowerer.fmt(type)}' has no undefined arm (narrow the receiver with 'if (x !== undefined)' instead)`);
|
|
3159
|
-
}
|
|
3160
|
-
const wrapped = lowerer.coerceToExpected(body, type);
|
|
3161
|
-
if (!typeEquals(wrapped.type, type))
|
|
3162
|
-
lowerer.badType(expr, lowerer.typeOf(expr));
|
|
3163
|
-
return { kind: "optChain", id, receiver, body: wrapped, type, loc };
|
|
3164
|
-
}
|
|
3165
2837
|
/** A CONDITION-position expression: the result is consumed as a bool
|
|
3166
2838
|
* only, so `&&`/`||` descend recursively over ToBoolean'd operands —
|
|
3167
2839
|
* JS-exact (`ToBoolean(a && b)` ≡ `ToBoolean(a) && ToBoolean(b)`), still
|
|
@@ -3171,6 +2843,11 @@ export function lowerCondition(lowerer, expr) {
|
|
|
3171
2843
|
let e = expr;
|
|
3172
2844
|
while (ts.isParenthesizedExpression(e))
|
|
3173
2845
|
e = e.expression;
|
|
2846
|
+
if (ts.isPropertyAccessExpression(e)) {
|
|
2847
|
+
const presence = lowerPromiseThenPresence(lowerer, e, { kind: "boolLit", value: true, type: BOOL, loc: locOf(e) });
|
|
2848
|
+
if (presence !== null)
|
|
2849
|
+
return presence;
|
|
2850
|
+
}
|
|
3174
2851
|
if (ts.isBinaryExpression(e)) {
|
|
3175
2852
|
const op = e.operatorToken.kind;
|
|
3176
2853
|
if (op === ts.SyntaxKind.AmpersandAmpersandToken || op === ts.SyntaxKind.BarBarToken) {
|
|
@@ -3204,6 +2881,47 @@ export function lowerCondition(lowerer, expr) {
|
|
|
3204
2881
|
}
|
|
3205
2882
|
return lowerer.ensureBool(lowerAbsenceProbe(lowerer, expr) ?? lowerer.lowerExpr(expr), expr);
|
|
3206
2883
|
}
|
|
2884
|
+
function lowerPromiseThenPresence(lowerer, access, result) {
|
|
2885
|
+
if (access.name.text !== "then")
|
|
2886
|
+
return null;
|
|
2887
|
+
const mapped = lowerer.mapTypeOf(lowerer.typeOf(access.expression));
|
|
2888
|
+
const promiseType = mapped?.kind === "promise"
|
|
2889
|
+
? mapped
|
|
2890
|
+
: mapped?.kind === "union"
|
|
2891
|
+
? (() => {
|
|
2892
|
+
const arms = lowerer.unions.get(mapped.unionId)?.arms ?? [];
|
|
2893
|
+
const promises = arms.filter((arm) => arm.kind === "promise");
|
|
2894
|
+
return promises.length === 1 && arms.every((arm) => arm.kind === "promise" || isUnitType(arm))
|
|
2895
|
+
? promises[0]
|
|
2896
|
+
: null;
|
|
2897
|
+
})()
|
|
2898
|
+
: null;
|
|
2899
|
+
if (promiseType === null)
|
|
2900
|
+
return null;
|
|
2901
|
+
let receiver = lowerer.lowerExpr(access.expression);
|
|
2902
|
+
if (receiver.type.kind === "union") {
|
|
2903
|
+
const optional = lowerer.runtimeOptionalPropertyReceiver(access.expression, receiver, promiseType, access.name.text);
|
|
2904
|
+
if (optional !== null)
|
|
2905
|
+
receiver = optional;
|
|
2906
|
+
else {
|
|
2907
|
+
const helper = lowerer.narrowedArmHelper(receiver.type.unionId, promiseType, receiver.loc);
|
|
2908
|
+
if (helper === null)
|
|
2909
|
+
return null;
|
|
2910
|
+
receiver = { kind: "call", callee: helper, args: [receiver], type: promiseType, loc: receiver.loc };
|
|
2911
|
+
}
|
|
2912
|
+
}
|
|
2913
|
+
if (receiver.type.kind !== "promise")
|
|
2914
|
+
return null;
|
|
2915
|
+
if (isSafeToDiscard(receiver))
|
|
2916
|
+
return result;
|
|
2917
|
+
return {
|
|
2918
|
+
kind: "seqExpr",
|
|
2919
|
+
stmts: [{ kind: "exprStmt", expr: receiver, loc: receiver.loc }],
|
|
2920
|
+
result,
|
|
2921
|
+
type: result.type,
|
|
2922
|
+
loc: result.loc,
|
|
2923
|
+
};
|
|
2924
|
+
}
|
|
3207
2925
|
function runtimeOptionalLocalOf(lowerer, node) {
|
|
3208
2926
|
let expr = node;
|
|
3209
2927
|
while (ts.isParenthesizedExpression(expr))
|
|
@@ -3248,6 +2966,9 @@ export function ensureBool(lowerer, e, node) {
|
|
|
3248
2966
|
if (e.type.kind === "f64" || e.type.kind === "string") {
|
|
3249
2967
|
return { kind: "toBool", operand: e, type: BOOL, loc: e.loc };
|
|
3250
2968
|
}
|
|
2969
|
+
if (e.type.kind === "bigint") {
|
|
2970
|
+
return { kind: "libCall", fn: "bigint.truthy", args: [e], type: BOOL, loc: e.loc };
|
|
2971
|
+
}
|
|
3251
2972
|
if (e.type.kind === "dyn") {
|
|
3252
2973
|
// ToBoolean over the checked-dynamic tree (`if (pkg)` on a JSON.parse result): every
|
|
3253
2974
|
// dyn kind has a JS-exact answer — the truthy dynTest reads the kind
|
|
@@ -3729,6 +3450,27 @@ export function lowerArrayLiteral(lowerer, expr, expected) {
|
|
|
3729
3450
|
}
|
|
3730
3451
|
}
|
|
3731
3452
|
}
|
|
3453
|
+
// An array literal inside an npm-static implicit-any instance can
|
|
3454
|
+
// inherit the checker's poisoned any[] context even after the
|
|
3455
|
+
// active binding gives every element one concrete lowering. Recover a
|
|
3456
|
+
// static array only when every spelled element lowers to the same
|
|
3457
|
+
// supported type. An explicit checked-dynamic destination still owns
|
|
3458
|
+
// the later slot coercion; heterogeneous literals, holes, and spreads
|
|
3459
|
+
// keep the checked-dynamic fallback below.
|
|
3460
|
+
if (lowerer.implicitParamTypes !== null &&
|
|
3461
|
+
npmStaticPackageOfPath(expr.getSourceFile().fileName) !== null &&
|
|
3462
|
+
(mapped === null || mapped.kind === "dyn" ||
|
|
3463
|
+
(mapped.kind === "array" && mapped.elem.kind === "dyn")) &&
|
|
3464
|
+
expr.elements.length > 0 &&
|
|
3465
|
+
expr.elements.every((element) => !ts.isSpreadElement(element) && !ts.isOmittedExpression(element))) {
|
|
3466
|
+
const elements = expr.elements.map((element) => tryLowerExpression(lowerer, element));
|
|
3467
|
+
const first = elements[0];
|
|
3468
|
+
if (first !== undefined && first !== null &&
|
|
3469
|
+
isSupportedArrayElem(first.type) &&
|
|
3470
|
+
elements.every((element) => element !== null && typeEquals(element.type, first.type))) {
|
|
3471
|
+
mapped = arrayOf(first.type);
|
|
3472
|
+
}
|
|
3473
|
+
}
|
|
3732
3474
|
// An EMPTY literal under a CONST ASSERTION whose type queries panicked
|
|
3733
3475
|
// (tsgo's readonly-[] TupleType conversion — the facade answers `any`,
|
|
3734
3476
|
// which maps to nothing statically and to an island value under
|
|
@@ -3810,7 +3552,7 @@ export function lowerArrayLiteral(lowerer, expr, expected) {
|
|
|
3810
3552
|
}
|
|
3811
3553
|
lowerer.badType(expr, tsType);
|
|
3812
3554
|
}
|
|
3813
|
-
|
|
3555
|
+
const type = mapped;
|
|
3814
3556
|
// Keep the array payload type fixed. Optional reads are stored through
|
|
3815
3557
|
// arrayValueStore, which records UNDEFINED in the state byte while the
|
|
3816
3558
|
// payload remains number/string/etc.; widening the array element here
|
|
@@ -3823,7 +3565,7 @@ export function lowerArrayLiteral(lowerer, expr, expected) {
|
|
|
3823
3565
|
const candidate = expr.elements[i];
|
|
3824
3566
|
if (ts.isSpreadElement(candidate) || ts.isOmittedExpression(candidate))
|
|
3825
3567
|
continue;
|
|
3826
|
-
const probe =
|
|
3568
|
+
const probe = tryLowerExpression(lowerer, candidate);
|
|
3827
3569
|
if (probe && lowerer.runtimeOptionalWidening(probe.type, type.elem) !== null) {
|
|
3828
3570
|
runtimeOptionalElements.add(i);
|
|
3829
3571
|
needsStateBuild = true;
|
|
@@ -4019,315 +3761,6 @@ export function lowerArrayLiteral(lowerer, expr, expected) {
|
|
|
4019
3761
|
loc,
|
|
4020
3762
|
};
|
|
4021
3763
|
}
|
|
4022
|
-
/** `{ a: 1, b: "x" }` → recordLit. The record type comes from the
|
|
4023
|
-
* contextual type when tsc has one (annotated declarations, arguments,
|
|
4024
|
-
* nested literals) and from the literal's own type otherwise — both intern
|
|
4025
|
-
* to the same shapeId unless width subtyping is in play (an `as` cast can
|
|
4026
|
-
* smuggle a wider/narrower literal past tsc's freshness check), which the
|
|
4027
|
-
* exact-shape checks below reject with SC2002. Fields lower IN SOURCE
|
|
4028
|
-
* ORDER: JS evaluates property values in source order. */
|
|
4029
|
-
/** The dropped-field names of the PromiseSettledResult honest subset
|
|
4030
|
-
* (SEMANTICS.md 46): when the literal's target type is (a union
|
|
4031
|
-
* containing) the lib's PromiseFulfilledResult / PromiseRejectedResult,
|
|
4032
|
-
* the record mapping kept only `status` — `value` and `reason` evaluate
|
|
4033
|
-
* for effect and are not stored. Null for every other target. */
|
|
4034
|
-
function settledDropNames(lowerer, tsType) {
|
|
4035
|
-
let names = null;
|
|
4036
|
-
const parts = tsType.isUnionType() ? ts.constituentTypes(tsType) : [tsType];
|
|
4037
|
-
for (const part of parts) {
|
|
4038
|
-
const sym = part.getSymbol();
|
|
4039
|
-
if (!sym ||
|
|
4040
|
-
!lowerer.checker.declarationsOf(sym).some((d) => ts.isInterfaceDeclaration(d) && lowerer.isStdlibFile(d.getSourceFile()))) {
|
|
4041
|
-
continue;
|
|
4042
|
-
}
|
|
4043
|
-
if (sym.name === "PromiseFulfilledResult")
|
|
4044
|
-
(names ??= new Set()).add("value");
|
|
4045
|
-
if (sym.name === "PromiseRejectedResult")
|
|
4046
|
-
(names ??= new Set()).add("reason");
|
|
4047
|
-
}
|
|
4048
|
-
return names;
|
|
4049
|
-
}
|
|
4050
|
-
/** `{ [KEY]: v }` where KEY is a compile-time-known string or number:
|
|
4051
|
-
* the key folds into an ordinary (spelled) property name — the record
|
|
4052
|
-
* shape is exactly what tsc computed for the literal (the late-bound
|
|
4053
|
-
* name IS the checker's literal type), so the fold is just spelling.
|
|
4054
|
-
* PURE key forms only, so skipping the key's evaluation is exact (JS
|
|
4055
|
-
* evaluates the key before the value; effectful expressions keep the
|
|
4056
|
-
* fence): any side-effect-free expression — identifiers, property
|
|
4057
|
-
* chains with no accessor on them (enum members, `other.name` reads),
|
|
4058
|
-
* literals, parenthesized/as-wrapped forms, operators over those —
|
|
4059
|
-
* whose checker type is ONE string or number literal (or a union whose
|
|
4060
|
-
* arms all spell the same name — `E1.x || E2.x` where both are 0).
|
|
4061
|
-
* Number keys take JS's canonical spelling (`{ [E.member]: v }` stores
|
|
4062
|
-
* "0" — ToPropertyKey), exactly the name tsc late-bound. Templates
|
|
4063
|
-
* fold structurally span by span as well (a template of literals whose
|
|
4064
|
-
* checker type widened still spells one string). Symbol-typed and
|
|
4065
|
-
* runtime-valued keys stay out. */
|
|
4066
|
-
function literalComputedKey(lowerer, name) {
|
|
4067
|
-
return foldedStringKeyOf(lowerer, name.expression);
|
|
4068
|
-
}
|
|
4069
|
-
export function foldedStringKeyOf(lowerer, expr) {
|
|
4070
|
-
if (ts.isParenthesizedExpression(expr))
|
|
4071
|
-
return foldedStringKeyOf(lowerer, expr.expression);
|
|
4072
|
-
if (ts.isStringLiteral(expr) || ts.isNoSubstitutionTemplateLiteral(expr))
|
|
4073
|
-
return expr.text;
|
|
4074
|
-
if (ts.isTemplateExpression(expr)) {
|
|
4075
|
-
// Structural fold first — exact even where the checker widened the
|
|
4076
|
-
// template's own type; the general type-directed fold below is the
|
|
4077
|
-
// fallback (a template the checker DID type as one literal).
|
|
4078
|
-
let out = expr.head.text;
|
|
4079
|
-
let folded = true;
|
|
4080
|
-
for (const span of expr.templateSpans) {
|
|
4081
|
-
const part = foldedStringKeyOf(lowerer, span.expression);
|
|
4082
|
-
if (part === null) {
|
|
4083
|
-
folded = false;
|
|
4084
|
-
break;
|
|
4085
|
-
}
|
|
4086
|
-
out += part + span.literal.text;
|
|
4087
|
-
}
|
|
4088
|
-
if (folded)
|
|
4089
|
-
return out;
|
|
4090
|
-
}
|
|
4091
|
-
// The general fold: a PURE expression whose checker type spells one
|
|
4092
|
-
// property name. An as-cast folds by its OWN checker type — that is
|
|
4093
|
-
// the name tsc late-bound the property under, so shape and storage
|
|
4094
|
-
// stay one name (the standard trust-the-checker bet); a cast that
|
|
4095
|
-
// WIDENS to string un-late-binds the property, its type is no literal,
|
|
4096
|
-
// and the fence stays.
|
|
4097
|
-
if (!pureKeyExpr(lowerer, expr))
|
|
4098
|
-
return null;
|
|
4099
|
-
return literalKeySpellingOf(lowerer.typeOf(expr));
|
|
4100
|
-
}
|
|
4101
|
-
/** The property-name spelling of a single-literal checker type: string
|
|
4102
|
-
* literals directly, number literals in JS's canonical ToString spelling
|
|
4103
|
-
* (enum members included — their literal flags carry the value), and
|
|
4104
|
-
* unions whose arms all spell the SAME name (`E1.x || E2.x` — two enum
|
|
4105
|
-
* literal types, one value). Null for everything else. */
|
|
4106
|
-
function literalKeySpellingOf(t) {
|
|
4107
|
-
if (t.isStringLiteralType())
|
|
4108
|
-
return t.value;
|
|
4109
|
-
if (t.isNumberLiteralType())
|
|
4110
|
-
return String(t.value);
|
|
4111
|
-
if (t.isUnionType()) {
|
|
4112
|
-
let out = null;
|
|
4113
|
-
for (const arm of ts.constituentTypes(t)) {
|
|
4114
|
-
const s = arm.isStringLiteralType() ? arm.value : arm.isNumberLiteralType() ? String(arm.value) : null;
|
|
4115
|
-
if (s === null || (out !== null && s !== out))
|
|
4116
|
-
return null;
|
|
4117
|
-
out = s;
|
|
4118
|
-
}
|
|
4119
|
-
return out;
|
|
4120
|
-
}
|
|
4121
|
-
return null;
|
|
4122
|
-
}
|
|
4123
|
-
/** True iff evaluating `expr` can have no observable effect, so a fold
|
|
4124
|
-
* may skip it: literals, identifier reads, property chains whose every
|
|
4125
|
-
* member is a plain field/enum member (an accessor anywhere on the chain
|
|
4126
|
-
* is a call), operators and casts over those. Calls, `new`, assignments,
|
|
4127
|
-
* and element accesses stay impure. */
|
|
4128
|
-
function pureKeyExpr(lowerer, expr) {
|
|
4129
|
-
if (ts.isParenthesizedExpression(expr) || ts.isNonNullExpression(expr))
|
|
4130
|
-
return pureKeyExpr(lowerer, expr.expression);
|
|
4131
|
-
if (ts.isAsExpression(expr) || ts.isTypeAssertion(expr))
|
|
4132
|
-
return pureKeyExpr(lowerer, expr.expression);
|
|
4133
|
-
if (ts.isIdentifier(expr))
|
|
4134
|
-
return true;
|
|
4135
|
-
if (ts.isStringLiteralLike(expr) || ts.isNumericLiteral(expr))
|
|
4136
|
-
return true;
|
|
4137
|
-
if (expr.kind === ts.SyntaxKind.TrueKeyword || expr.kind === ts.SyntaxKind.FalseKeyword ||
|
|
4138
|
-
expr.kind === ts.SyntaxKind.NullKeyword)
|
|
4139
|
-
return true;
|
|
4140
|
-
if (ts.isPropertyAccessExpression(expr)) {
|
|
4141
|
-
const sym = lowerer.checker.getSymbolAtLocation(expr.name);
|
|
4142
|
-
if (sym && sym.flags & (ts.SymbolFlags.GetAccessor | ts.SymbolFlags.SetAccessor))
|
|
4143
|
-
return false;
|
|
4144
|
-
return pureKeyExpr(lowerer, expr.expression);
|
|
4145
|
-
}
|
|
4146
|
-
if (ts.isPrefixUnaryExpression(expr) &&
|
|
4147
|
-
(expr.operator === ts.SyntaxKind.MinusToken || expr.operator === ts.SyntaxKind.PlusToken ||
|
|
4148
|
-
expr.operator === ts.SyntaxKind.ExclamationToken || expr.operator === ts.SyntaxKind.TildeToken)) {
|
|
4149
|
-
return pureKeyExpr(lowerer, expr.operand);
|
|
4150
|
-
}
|
|
4151
|
-
if (ts.isBinaryExpression(expr)) {
|
|
4152
|
-
const op = expr.operatorToken.kind;
|
|
4153
|
-
const pureOp = op === ts.SyntaxKind.AmpersandAmpersandToken || op === ts.SyntaxKind.BarBarToken ||
|
|
4154
|
-
op === ts.SyntaxKind.QuestionQuestionToken ||
|
|
4155
|
-
(op >= ts.SyntaxKind.LessThanToken && op <= ts.SyntaxKind.CaretToken &&
|
|
4156
|
-
op !== ts.SyntaxKind.InstanceOfKeyword && op !== ts.SyntaxKind.InKeyword);
|
|
4157
|
-
return pureOp && pureKeyExpr(lowerer, expr.left) && pureKeyExpr(lowerer, expr.right);
|
|
4158
|
-
}
|
|
4159
|
-
if (ts.isConditionalExpression(expr)) {
|
|
4160
|
-
return pureKeyExpr(lowerer, expr.condition) && pureKeyExpr(lowerer, expr.whenTrue) && pureKeyExpr(lowerer, expr.whenFalse);
|
|
4161
|
-
}
|
|
4162
|
-
if (ts.isTemplateExpression(expr))
|
|
4163
|
-
return expr.templateSpans.every((s) => pureKeyExpr(lowerer, s.expression));
|
|
4164
|
-
return false;
|
|
4165
|
-
}
|
|
4166
|
-
/** A property's field name: identifier/string-literal keys directly,
|
|
4167
|
-
* foldable computed keys through literalComputedKey (the syntax pass
|
|
4168
|
-
* fenced everything else). */
|
|
4169
|
-
function propNameText(lowerer, name) {
|
|
4170
|
-
if (ts.isComputedPropertyName(name)) {
|
|
4171
|
-
const k = literalComputedKey(lowerer, name);
|
|
4172
|
-
if (k === null)
|
|
4173
|
-
throw new InternalCompilerError("lowerer bug: unfoldable computed key past the fence");
|
|
4174
|
-
return k;
|
|
4175
|
-
}
|
|
4176
|
-
// Numeric literals name their canonical string key (JS's ToPropertyKey
|
|
4177
|
-
// — the same name the checker gives the property symbol).
|
|
4178
|
-
if (ts.isNumericLiteral(name))
|
|
4179
|
-
return String(Number(name.text));
|
|
4180
|
-
return name.text;
|
|
4181
|
-
}
|
|
4182
|
-
/** The runtime-keyed JS object literal (a computed key that doesn't fold):
|
|
4183
|
-
* builds a dyn object member-by-member. Keys evaluate before their values,
|
|
4184
|
-
* properties in source order — JS's object-literal evaluation exactly.
|
|
4185
|
-
* Identifier/string keys are compile-time strings; numeric keys take
|
|
4186
|
-
* their canonical number string (`{ 0x10: v }` stores "16" — JS's
|
|
4187
|
-
* ToPropertyKey); computed keys evaluate and pass through ToString (the
|
|
4188
|
-
* dyn's String() for dyn operands — `{ [field]: v }` where field is a
|
|
4189
|
-
* checked-dynamic param). Values convert through the usual dyn boundary
|
|
4190
|
-
* (dynFrom's domain, functions box); a value with no dyn representation
|
|
4191
|
-
* fences per property. Spreads copy through dyn.assign in source order;
|
|
4192
|
-
* accessors stay fenced. */
|
|
4193
|
-
export function lowerDynObjectLiteral(lowerer, expr, boxValue) {
|
|
4194
|
-
const loc = locOf(expr);
|
|
4195
|
-
let fields = [];
|
|
4196
|
-
let acc = null;
|
|
4197
|
-
const flushFields = () => {
|
|
4198
|
-
if (fields.length === 0)
|
|
4199
|
-
return;
|
|
4200
|
-
const chunk = { kind: "dynObjLit", fields, type: DYN, loc };
|
|
4201
|
-
acc = acc === null
|
|
4202
|
-
? chunk
|
|
4203
|
-
: { kind: "libCall", fn: "dyn.assign", args: [acc, chunk], type: DYN, loc };
|
|
4204
|
-
fields = [];
|
|
4205
|
-
};
|
|
4206
|
-
for (const prop of expr.properties) {
|
|
4207
|
-
if (ts.isSpreadAssignment(prop)) {
|
|
4208
|
-
flushFields();
|
|
4209
|
-
const raw = lowerer.lowerExpr(prop.expression);
|
|
4210
|
-
const source = boxValue
|
|
4211
|
-
? boxValue(prop.expression, raw)
|
|
4212
|
-
: lowerer.coerceToExpected(raw, DYN);
|
|
4213
|
-
if (source.type.kind !== "dyn") {
|
|
4214
|
-
lowerer.unsupported("SC1101", prop.expression, `spreading '${lowerer.fmt(source.type)}' into a checked-dynamic object literal`);
|
|
4215
|
-
}
|
|
4216
|
-
acc ??= { kind: "dynObjLit", fields: [], type: DYN, loc };
|
|
4217
|
-
acc = { kind: "libCall", fn: "dyn.assign", args: [acc, source], type: DYN, loc: locOf(prop) };
|
|
4218
|
-
continue;
|
|
4219
|
-
}
|
|
4220
|
-
if (!ts.isPropertyAssignment(prop) &&
|
|
4221
|
-
!ts.isShorthandPropertyAssignment(prop) &&
|
|
4222
|
-
!ts.isMethodDeclaration(prop)) {
|
|
4223
|
-
lowerer.unsupported("SC1090", prop, "accessors in a runtime-keyed (computed-key) object literal");
|
|
4224
|
-
}
|
|
4225
|
-
const name = prop.name;
|
|
4226
|
-
let key;
|
|
4227
|
-
if (ts.isComputedPropertyName(name)) {
|
|
4228
|
-
const folded = literalComputedKey(lowerer, name);
|
|
4229
|
-
if (folded !== null) {
|
|
4230
|
-
key = { kind: "strLit", value: folded, type: STRING, loc: locOf(name) };
|
|
4231
|
-
}
|
|
4232
|
-
else {
|
|
4233
|
-
let k = lowerer.lowerExpr(name.expression);
|
|
4234
|
-
if (k.type.kind === "f64" || k.type.kind === "bool" || k.type.kind === "dyn") {
|
|
4235
|
-
k = { kind: "toString", operand: k, type: STRING, loc: locOf(name) };
|
|
4236
|
-
}
|
|
4237
|
-
if (k.type.kind !== "string") {
|
|
4238
|
-
lowerer.unsupported("SC1090", name, `'${lowerer.fmt(k.type)}'-typed computed property keys (string, number, boolean, and unknown keys stringify)`);
|
|
4239
|
-
}
|
|
4240
|
-
key = k;
|
|
4241
|
-
}
|
|
4242
|
-
}
|
|
4243
|
-
else if (ts.isIdentifier(name) || ts.isStringLiteral(name)) {
|
|
4244
|
-
key = { kind: "strLit", value: name.text, type: STRING, loc: locOf(name) };
|
|
4245
|
-
}
|
|
4246
|
-
else if (ts.isNumericLiteral(name)) {
|
|
4247
|
-
key = { kind: "strLit", value: String(Number(name.text)), type: STRING, loc: locOf(name) };
|
|
4248
|
-
}
|
|
4249
|
-
else {
|
|
4250
|
-
lowerer.unsupported("SC1090", prop, "non-identifier property names");
|
|
4251
|
-
}
|
|
4252
|
-
const valueExpr = ts.isMethodDeclaration(prop)
|
|
4253
|
-
? prop
|
|
4254
|
-
: ts.isShorthandPropertyAssignment(prop)
|
|
4255
|
-
? prop.name
|
|
4256
|
-
: prop.initializer;
|
|
4257
|
-
// Lambda values that fail to lower become trap closures (the
|
|
4258
|
-
// per-call fence granularity — fenceClosureProbe) so the object
|
|
4259
|
-
// still builds; everything else keeps the per-property fence below.
|
|
4260
|
-
let raw;
|
|
4261
|
-
const propDiagsBefore = lowerer.diags.length;
|
|
4262
|
-
try {
|
|
4263
|
-
const lowerValue = () => ts.isMethodDeclaration(prop)
|
|
4264
|
-
? (lowerer.rejectThisInObjectMethod(prop.body ?? prop), lowerer.lowerLambda(prop))
|
|
4265
|
-
: lowerer.lowerExpr(valueExpr);
|
|
4266
|
-
raw =
|
|
4267
|
-
fenceClosureProbe(lowerer, valueExpr, undefined, lowerValue) ??
|
|
4268
|
-
lowerValue();
|
|
4269
|
-
}
|
|
4270
|
-
catch (err) {
|
|
4271
|
-
// A PURE member read a JS file cannot lower (a namespace object
|
|
4272
|
-
// in an export aggregate): the slot takes a boxed fence closure —
|
|
4273
|
-
// the diagnostics defer to the runtime-fence ledger, the object
|
|
4274
|
-
// builds, and only USING the member stops the run.
|
|
4275
|
-
const pureMember = ts.isIdentifier(valueExpr);
|
|
4276
|
-
if (!(err instanceof PoisonError) || !pureMember)
|
|
4277
|
-
throw err;
|
|
4278
|
-
const fence = lowerer.deferToRuntimeFence(propDiagsBefore, prop, {
|
|
4279
|
-
kind: "closure",
|
|
4280
|
-
name: () => `%fn${lowerer.lambdaCounter++}_dynfence`,
|
|
4281
|
-
returnType: VOID,
|
|
4282
|
-
type: { kind: "func", params: [], ret: VOID },
|
|
4283
|
-
});
|
|
4284
|
-
if (!fence)
|
|
4285
|
-
throw err;
|
|
4286
|
-
raw = fence;
|
|
4287
|
-
}
|
|
4288
|
-
let v = boxValue
|
|
4289
|
-
? boxValue(valueExpr, raw)
|
|
4290
|
-
: lowerer.coerceToExpected(raw, DYN);
|
|
4291
|
-
if (v.type.kind !== "dyn") {
|
|
4292
|
-
const convDiagsBefore = lowerer.diags.length;
|
|
4293
|
-
try {
|
|
4294
|
-
lowerer.unsupported("SC1101", valueExpr, `holding '${lowerer.fmt(v.type)}' values in a runtime-keyed (computed-key) object literal`);
|
|
4295
|
-
}
|
|
4296
|
-
catch (err) {
|
|
4297
|
-
// A member value the checked-dynamic tree cannot hold — a func whose signature
|
|
4298
|
-
// cannot box, an island ('any') handle, a record carrying func
|
|
4299
|
-
// fields (the export aggregate's typed utilities and npm-handle
|
|
4300
|
-
// members): the same call-time fence deferral as an unlowerable
|
|
4301
|
-
// member — the slot takes a boxed fence closure; only USING it
|
|
4302
|
-
// stops the run. Probe mode and ICEs keep the poison.
|
|
4303
|
-
if (!(err instanceof PoisonError))
|
|
4304
|
-
throw err;
|
|
4305
|
-
const fence = lowerer.deferToRuntimeFence(convDiagsBefore, prop, {
|
|
4306
|
-
kind: "closure",
|
|
4307
|
-
name: () => `%fn${lowerer.lambdaCounter++}_dynfence`,
|
|
4308
|
-
returnType: VOID,
|
|
4309
|
-
type: { kind: "func", params: [], ret: VOID },
|
|
4310
|
-
});
|
|
4311
|
-
if (!fence)
|
|
4312
|
-
throw err;
|
|
4313
|
-
v = lowerer.coerceToExpected(fence, DYN);
|
|
4314
|
-
}
|
|
4315
|
-
}
|
|
4316
|
-
fields.push({ key, value: v });
|
|
4317
|
-
}
|
|
4318
|
-
flushFields();
|
|
4319
|
-
return acc ?? { kind: "dynObjLit", fields: [], type: DYN, loc };
|
|
4320
|
-
}
|
|
4321
|
-
/** Spreading a source whose shape carries accessor slots: Node's copy
|
|
4322
|
-
* invokes each getter exactly once in insertion order — even for keys a
|
|
4323
|
-
* later contributor overrides — while the field-copy desugar assumes pure
|
|
4324
|
-
* reads it may drop or reorder. Getter-call counts would silently diverge,
|
|
4325
|
-
* so accessor-carrying sources fence by name. */
|
|
4326
|
-
function fenceAccessorSpreadSource(lowerer, prop, srcShape) {
|
|
4327
|
-
if (srcShape && shapeHasAccessorSlots(srcShape)) {
|
|
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)");
|
|
4329
|
-
}
|
|
4330
|
-
}
|
|
4331
3764
|
/** Convert an undefined-armed numeric value at an arithmetic use. Ordinary
|
|
4332
3765
|
* reads preserve their tagged value; JavaScript's ToNumber(undefined) is NaN,
|
|
4333
3766
|
* so compounds and binary arithmetic use this checked one-time extraction. */
|
|
@@ -4347,7 +3780,7 @@ export function lowerOptionalNumber(lowerer, operand, loc, narrowedNode) {
|
|
|
4347
3780
|
return operand;
|
|
4348
3781
|
let stable = operand;
|
|
4349
3782
|
let prefix = [];
|
|
4350
|
-
if (!
|
|
3783
|
+
if (!isSafeToRepeat(operand)) {
|
|
4351
3784
|
const tmp = lowerer.declareHiddenLocal("%arith", operand.type);
|
|
4352
3785
|
stable = varRef(tmp.id, operand.type, loc);
|
|
4353
3786
|
prefix = [{ kind: "varDecl", localId: tmp.id, init: operand, loc }];
|
|
@@ -4364,1499 +3797,6 @@ export function lowerOptionalNumber(lowerer, operand, loc, narrowedNode) {
|
|
|
4364
3797
|
};
|
|
4365
3798
|
return prefix.length === 0 ? value : { kind: "seqExpr", stmts: prefix, result: value, type: F64, loc };
|
|
4366
3799
|
}
|
|
4367
|
-
/** The JS trap-closure fallback for FUNCTION-VALUED object properties: a
|
|
4368
|
-
* lambda whose body fails to lower inside a JS object literal becomes a
|
|
4369
|
-
* closure of the field's exact func type whose body is the runtime fence —
|
|
4370
|
-
* the object still BUILDS (commander's `_outputConfiguration` table: the
|
|
4371
|
-
* driven writeOut/writeErr entries work; an unloweraable color probe traps
|
|
4372
|
-
* only if something CALLS it). The failed lambda's diagnostics move to the
|
|
4373
|
-
* runtime-fence inventory, exactly like a fenced JS statement. Null when
|
|
4374
|
-
* the fallback does not apply (not a JS file, not a lambda, no func slot)
|
|
4375
|
-
* — the caller lowers normally. ICEs (SC9001) never convert. */
|
|
4376
|
-
function fenceClosureProbe(lowerer, node, slotType, attempt) {
|
|
4377
|
-
if (!isJsSourceFile(node.getSourceFile()))
|
|
4378
|
-
return null;
|
|
4379
|
-
let inner = node;
|
|
4380
|
-
while (ts.isParenthesizedExpression(inner))
|
|
4381
|
-
inner = inner.expression;
|
|
4382
|
-
if (!ts.isArrowFunction(inner) && !ts.isFunctionExpression(inner) && !ts.isMethodDeclaration(inner)) {
|
|
4383
|
-
return null;
|
|
4384
|
-
}
|
|
4385
|
-
// The trap's ABI: the field's func slot when one exists; otherwise (the
|
|
4386
|
-
// dyn-object literal path — the value boxes into dyn) the all-dyn
|
|
4387
|
-
// signature of the lambda's own arity, which canBoxFuncIntoDyn always
|
|
4388
|
-
// admits.
|
|
4389
|
-
const fieldType = slotType !== undefined && slotType.kind === "func"
|
|
4390
|
-
? slotType
|
|
4391
|
-
: { kind: "func", params: inner.parameters.map(() => DYN), ret: DYN };
|
|
4392
|
-
if (slotType !== undefined && slotType.kind !== "func")
|
|
4393
|
-
return null;
|
|
4394
|
-
const diagsBefore = lowerer.diags.length;
|
|
4395
|
-
try {
|
|
4396
|
-
return attempt();
|
|
4397
|
-
}
|
|
4398
|
-
catch (e) {
|
|
4399
|
-
if (!(e instanceof PoisonError))
|
|
4400
|
-
throw e;
|
|
4401
|
-
const params = fieldType.params.map((t, i) => ({ localId: `p.${i}`, name: `p${i}`, type: t }));
|
|
4402
|
-
const fence = lowerer.deferToRuntimeFence(diagsBefore, node, {
|
|
4403
|
-
kind: "closure",
|
|
4404
|
-
name: () => `%fence.fn.${lowerer.liftedFns.length}`,
|
|
4405
|
-
params,
|
|
4406
|
-
returnType: fieldType.ret,
|
|
4407
|
-
paramsMutable: true,
|
|
4408
|
-
type: fieldType,
|
|
4409
|
-
fallback: { code: "SC1090", message: "this function's body has no static lowering" },
|
|
4410
|
-
bareMessage: true,
|
|
4411
|
-
allowDiagSink: true,
|
|
4412
|
-
});
|
|
4413
|
-
if (!fence)
|
|
4414
|
-
throw e;
|
|
4415
|
-
return fence;
|
|
4416
|
-
}
|
|
4417
|
-
}
|
|
4418
|
-
/** The union arm an object LITERAL inhabits when its fields must widen
|
|
4419
|
-
* PER FIELD into exactly one record arm — the reducer-action pattern
|
|
4420
|
-
* (`{ kind: "a", parsed: 1 }` into `{ kind: "a"; parsed: number | null }
|
|
4421
|
-
* | { kind: "b" }`). The IR shapes erased the literal types tsc
|
|
4422
|
-
* discriminated on, so the probe runs against the contextual union's
|
|
4423
|
-
* CHECKER members: every literal field must exist on the member
|
|
4424
|
-
* (excess-property freshness — tsc already enforced it), every arm field
|
|
4425
|
-
* missing from the literal must be optional-flavored (an undefined-armed
|
|
4426
|
-
* union, or a dyn slot — the absent-completion rule), and each present
|
|
4427
|
-
* field's LITERAL type must fit the member's field type — literal against
|
|
4428
|
-
* literal decides by VALUE (the discriminant), everything else by the
|
|
4429
|
-
* widened IR pair under the width-lift relation. Exactly ONE fitting arm
|
|
4430
|
-
* answers it; zero or several answer null and the caller keeps its
|
|
4431
|
-
* fences. Plain property-assignment/shorthand literals only — spreads,
|
|
4432
|
-
* accessors, methods, and unfoldable computed keys keep their own paths. */
|
|
4433
|
-
function literalUnionArmOf(lowerer, expr, tsType, recordArms) {
|
|
4434
|
-
if (!tsType.isUnionType())
|
|
4435
|
-
return null;
|
|
4436
|
-
const props = [];
|
|
4437
|
-
for (const p of expr.properties) {
|
|
4438
|
-
if (ts.isPropertyAssignment(p) && !ts.isComputedPropertyName(p.name)) {
|
|
4439
|
-
props.push({ name: propNameText(lowerer, p.name), node: p.initializer });
|
|
4440
|
-
}
|
|
4441
|
-
else if (ts.isShorthandPropertyAssignment(p) && ts.isIdentifier(p.name)) {
|
|
4442
|
-
props.push({ name: p.name.text, node: p.name });
|
|
4443
|
-
}
|
|
4444
|
-
else {
|
|
4445
|
-
return null;
|
|
4446
|
-
}
|
|
4447
|
-
}
|
|
4448
|
-
/** litT fits ftT: unions per arm; literal-vs-literal by value; unit
|
|
4449
|
-
* types only into their own unit; otherwise the widened IR pair must be
|
|
4450
|
-
* equal or width-liftable. */
|
|
4451
|
-
const fits = (litT, ftT) => {
|
|
4452
|
-
if (ftT.isUnionType())
|
|
4453
|
-
return ts.constituentTypes(ftT).some((a) => fits(litT, a));
|
|
4454
|
-
if (ftT.isStringLiteralType())
|
|
4455
|
-
return litT.isStringLiteralType() && litT.value === ftT.value;
|
|
4456
|
-
if (ftT.isNumberLiteralType())
|
|
4457
|
-
return litT.isNumberLiteralType() && litT.value === ftT.value;
|
|
4458
|
-
if (ftT.flags & ts.TypeFlags.BooleanLiteral) {
|
|
4459
|
-
return (litT.flags & ts.TypeFlags.BooleanLiteral) !== 0 && lowerer.checker.typeToString(litT) === lowerer.checker.typeToString(ftT);
|
|
4460
|
-
}
|
|
4461
|
-
if (ftT.flags & ts.TypeFlags.Null)
|
|
4462
|
-
return (litT.flags & ts.TypeFlags.Null) !== 0;
|
|
4463
|
-
if (ftT.flags & ts.TypeFlags.Undefined)
|
|
4464
|
-
return (litT.flags & ts.TypeFlags.Undefined) !== 0;
|
|
4465
|
-
if (litT.flags & (ts.TypeFlags.Null | ts.TypeFlags.Undefined))
|
|
4466
|
-
return false;
|
|
4467
|
-
const li = lowerer.mapTypeOf(lowerer.checker.getBaseTypeOfLiteralType(litT));
|
|
4468
|
-
const fi = lowerer.mapTypeOf(ftT);
|
|
4469
|
-
if (!li || !fi)
|
|
4470
|
-
return false;
|
|
4471
|
-
return typeEquals(li, fi) || lowerer.widthLiftPlan(li, fi) !== null;
|
|
4472
|
-
};
|
|
4473
|
-
const armShapeIds = new Set(recordArms.map((a) => a.shapeId));
|
|
4474
|
-
const candidates = new Set();
|
|
4475
|
-
for (const member of ts.constituentTypes(tsType)) {
|
|
4476
|
-
const mMapped = lowerer.mapTypeOf(member);
|
|
4477
|
-
if (mMapped?.kind !== "record" || !armShapeIds.has(mMapped.shapeId) || candidates.has(mMapped.shapeId))
|
|
4478
|
-
continue;
|
|
4479
|
-
const shape = lowerer.shapes.get(mMapped.shapeId);
|
|
4480
|
-
if (!shape || shape.tuple || shape.indexValue)
|
|
4481
|
-
continue;
|
|
4482
|
-
// Excess-property freshness: every literal field must exist on the
|
|
4483
|
-
// member (tsc rejected the others for a fresh literal).
|
|
4484
|
-
if (!props.every((p) => lowerer.checker.getPropertyOfType(member, p.name) !== undefined))
|
|
4485
|
-
continue;
|
|
4486
|
-
// Arm fields the literal leaves unset must be optional-flavored (the
|
|
4487
|
-
// absent-completion rule: an undefined-armed union or a dyn slot).
|
|
4488
|
-
const names = new Set(props.map((p) => p.name));
|
|
4489
|
-
const absentOk = shape.fields.every((f) => {
|
|
4490
|
-
if (names.has(f.name))
|
|
4491
|
-
return true;
|
|
4492
|
-
if (f.type.kind === "dyn")
|
|
4493
|
-
return true;
|
|
4494
|
-
if (f.type.kind !== "union")
|
|
4495
|
-
return false;
|
|
4496
|
-
return lowerer.unions.get(f.type.unionId)?.arms.some((a) => a.kind === "undefinedT") ?? false;
|
|
4497
|
-
});
|
|
4498
|
-
if (!absentOk)
|
|
4499
|
-
continue;
|
|
4500
|
-
const fieldsFit = props.every((p) => {
|
|
4501
|
-
const sym = lowerer.checker.getPropertyOfType(member, p.name);
|
|
4502
|
-
if (!sym)
|
|
4503
|
-
return false;
|
|
4504
|
-
return fits(lowerer.typeOf(p.node), lowerer.checker.getTypeOfSymbol(sym));
|
|
4505
|
-
});
|
|
4506
|
-
if (fieldsFit)
|
|
4507
|
-
candidates.add(mMapped.shapeId);
|
|
4508
|
-
}
|
|
4509
|
-
if (candidates.size !== 1)
|
|
4510
|
-
return null;
|
|
4511
|
-
const [only] = candidates;
|
|
4512
|
-
return recordArms.find((a) => a.shapeId === only) ?? null;
|
|
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
|
-
}
|
|
4547
|
-
export function lowerObjectLiteral(lowerer, expr) {
|
|
4548
|
-
const loc = locOf(expr);
|
|
4549
|
-
// The RUNTIME-KEYED literal (JS): a computed key that doesn't fold to a
|
|
4550
|
-
// compile-time string means the literal's shape is not a compile-time
|
|
4551
|
-
// fact — no record shape can hold it. The whole literal builds as a dyn
|
|
4552
|
-
// object instead (`{ [field]: criteria, actual: 0, ... }` —
|
|
4553
|
-
// test/common's _mustCallInner context), where keys are runtime string
|
|
4554
|
-
// values. TypeScript keeps the record world and its fence: this shape
|
|
4555
|
-
// only arises in checked-dynamic JS.
|
|
4556
|
-
if (isJsSourceFile(expr.getSourceFile()) &&
|
|
4557
|
-
expr.properties.some((p) => {
|
|
4558
|
-
const n = ts.isSpreadAssignment(p) ? undefined : p.name;
|
|
4559
|
-
return n !== undefined && ts.isComputedPropertyName(n) && literalComputedKey(lowerer, n) === null;
|
|
4560
|
-
})) {
|
|
4561
|
-
return lowerDynObjectLiteral(lowerer, expr);
|
|
4562
|
-
}
|
|
4563
|
-
// Syntax fence FIRST: unsupported member forms get their specific
|
|
4564
|
-
// message even when they also make the literal's type unmappable
|
|
4565
|
-
// (a getter or a `this`-returning method would otherwise surface as an
|
|
4566
|
-
// opaque type fence on the whole literal).
|
|
4567
|
-
for (const prop of expr.properties) {
|
|
4568
|
-
if (ts.isSpreadAssignment(prop)) {
|
|
4569
|
-
// Supported shapes: FULL spreads of known record shapes read as
|
|
4570
|
-
// identifiers or other side-effect-free reads, all BEFORE any
|
|
4571
|
-
// explicit property (the field-copy desugar reads spread fields
|
|
4572
|
-
// first, exactly JS's eager copy order); and CONDITIONAL spreads
|
|
4573
|
-
// `...(c ? {k: v} : {})` — the optional-field idiom tsc types as
|
|
4574
|
-
// `k?: ...` — which desugar to one conditional field at the
|
|
4575
|
-
// spread's own position (any position: they introduce one fresh
|
|
4576
|
-
// name, collision-fenced below). Everything else stays fenced.
|
|
4577
|
-
const cs = conditionalSpreadOf(prop.expression);
|
|
4578
|
-
if (cs === "unsupported" || (cs && cs.props.length !== 1)) {
|
|
4579
|
-
lowerer.unsupported("SC1090", prop, "conditional spreads beyond `...(c ? { field: v } : {})` (exactly one property against an empty arm — spell other shapes as optional fields)");
|
|
4580
|
-
}
|
|
4581
|
-
// Spread ORDER is fenced in the field-by-field desugar below, not
|
|
4582
|
-
// here: the index-signature merge path supports any order (keyed
|
|
4583
|
-
// last-write-wins writes), so `{ K: v, ...extra }` into a pure
|
|
4584
|
-
// Record shape compiles — the buildServiceEnv pattern.
|
|
4585
|
-
continue;
|
|
4586
|
-
}
|
|
4587
|
-
if (ts.isGetAccessorDeclaration(prop) || ts.isSetAccessorDeclaration(prop)) {
|
|
4588
|
-
// JavaScript CJS-export tables reach reads through the lifted-
|
|
4589
|
-
// accessor path (cjsExportAccessorRead) — the literal VALUE
|
|
4590
|
-
// narrows to its plain fields below (accessor names are not
|
|
4591
|
-
// record storage; SEMANTICS.md documents the enumeration
|
|
4592
|
-
// divergence). TypeScript accessors lower into the shape's
|
|
4593
|
-
// reserved closure slots (%get:/%set: — see accessorSlotProp);
|
|
4594
|
-
// `this` in the body is fenced up front: the closure slot has no
|
|
4595
|
-
// receiver (capturing the record under construction would be an
|
|
4596
|
-
// RC cycle), and the generic lexical-this walk would silently
|
|
4597
|
-
// capture an ENCLOSING method's `this` — the object-method rule.
|
|
4598
|
-
if (!isJsSourceFile(expr.getSourceFile())) {
|
|
4599
|
-
rejectThisInObjectAccessor(lowerer, prop.body ?? prop);
|
|
4600
|
-
}
|
|
4601
|
-
}
|
|
4602
|
-
// Unfoldable computed keys are NOT fenced here: an index-signature
|
|
4603
|
-
// target lowers them as runtime keyed writes (the merge path below);
|
|
4604
|
-
// every other target re-fences them after the merge path declined
|
|
4605
|
-
// (fenceUnfoldableComputedKeys).
|
|
4606
|
-
// Identifier keys, STRING-LITERAL keys (`"content-type": v` —
|
|
4607
|
-
// record field names are data, never C identifiers; the mangler
|
|
4608
|
-
// encodes what C can't spell), NUMERIC-LITERAL keys in their
|
|
4609
|
-
// canonical string spelling (`{ 0: v }` stores "0", `{ 0x10: v }`
|
|
4610
|
-
// stores "16" — JS's ToPropertyKey; the checker names the property
|
|
4611
|
-
// symbol the same way), and FOLDABLE computed keys (above).
|
|
4612
|
-
if (!prop.name ||
|
|
4613
|
-
!(ts.isIdentifier(prop.name) || ts.isStringLiteral(prop.name) ||
|
|
4614
|
-
ts.isNumericLiteral(prop.name) || ts.isComputedPropertyName(prop.name))) {
|
|
4615
|
-
lowerer.unsupported("SC1090", prop, "non-identifier property names");
|
|
4616
|
-
}
|
|
4617
|
-
// Shorthand methods are just closure-valued fields — but their `this`
|
|
4618
|
-
// is dynamically bound (typed as the literal by tsc, so noImplicitThis
|
|
4619
|
-
// lets it through) and records don't model it; the generic
|
|
4620
|
-
// lexical-this walk would silently capture an ENCLOSING method's
|
|
4621
|
-
// `this`, so any `this` in the body is rejected up front.
|
|
4622
|
-
if (ts.isMethodDeclaration(prop))
|
|
4623
|
-
lowerer.rejectThisInObjectMethod(prop.body ?? prop);
|
|
4624
|
-
}
|
|
4625
|
-
let tsType = lowerer.checker.getContextualType(expr) ?? lowerer.typeOf(expr);
|
|
4626
|
-
// `lit satisfies T` is TYPE-LEVEL only: the expression's checker type —
|
|
4627
|
-
// and therefore the shape every downstream consumer sees — is the
|
|
4628
|
-
// literal's OWN type (T still contextually types members, so inferred
|
|
4629
|
-
// parameter types flow). Building at T would reshape the value tsc
|
|
4630
|
-
// says has the literal's type: `{...} satisfies Movable &
|
|
4631
|
-
// Record<string, unknown>` must NOT become an index-signature record.
|
|
4632
|
-
// Own type wins whenever it maps to a record; an unmappable own type
|
|
4633
|
-
// keeps the contextual fallback (a bare-null field whose satisfies
|
|
4634
|
-
// target names the wider slot type).
|
|
4635
|
-
{
|
|
4636
|
-
let p = expr.parent;
|
|
4637
|
-
while (ts.isParenthesizedExpression(p))
|
|
4638
|
-
p = p.parent;
|
|
4639
|
-
if (ts.isSatisfiesExpression(p)) {
|
|
4640
|
-
const own = lowerer.typeOf(expr);
|
|
4641
|
-
if (lowerer.mapTypeOf(own)?.kind === "record")
|
|
4642
|
-
tsType = own;
|
|
4643
|
-
}
|
|
4644
|
-
}
|
|
4645
|
-
// An async function's return position types the literal
|
|
4646
|
-
// `T | PromiseLike<T>` (the lib's await-unwrapping contract). The
|
|
4647
|
-
// PromiseLike arm never maps, and the record the return slot actually
|
|
4648
|
-
// holds is exactly the checker's awaited type — strip to it BEFORE the
|
|
4649
|
-
// own-type fallback below: the awaited contextual type carries the
|
|
4650
|
-
// slot's field types (`lanIp: string | null`), which the literal's own
|
|
4651
|
-
// type narrows away (a field written as `lanIp: null` types as bare
|
|
4652
|
-
// `null`, which maps to nothing on its own).
|
|
4653
|
-
if (tsType.isUnionType() && ts.constituentTypes(tsType).some((t) => t.getSymbol()?.name === "PromiseLike")) {
|
|
4654
|
-
tsType = lowerer.checker.getAwaitedType(tsType) ?? tsType;
|
|
4655
|
-
}
|
|
4656
|
-
let mapped = lowerer.mapTypeOf(tsType);
|
|
4657
|
-
// An EMPTY-record context under a NON-empty literal (`Object.keys({
|
|
4658
|
-
// ...process.env })` — the lib's `{}`-typed parameters admit every
|
|
4659
|
-
// object): `{}` carries no shape information, so the literal builds as
|
|
4660
|
-
// its OWN type, exactly the unmappable-context fallback below. An
|
|
4661
|
-
// empty LITERAL keeps the context (the shapes agree).
|
|
4662
|
-
if (mapped?.kind === "record" && expr.properties.length > 0) {
|
|
4663
|
-
const ctxShape = lowerer.shapes.get(mapped.shapeId);
|
|
4664
|
-
if (ctxShape && ctxShape.fields.length === 0 && !ctxShape.indexValue && !ctxShape.tuple) {
|
|
4665
|
-
mapped = lowerer.mapTypeOf(lowerer.typeOf(expr)) ?? mapped;
|
|
4666
|
-
}
|
|
4667
|
-
}
|
|
4668
|
-
// A literal with a property the contextual TYPE ITSELF lacks — only
|
|
4669
|
-
// reachable through type assertions and satisfies (fresh-literal
|
|
4670
|
-
// excess-property checks reject the direct spelling): the context
|
|
4671
|
-
// cannot hold the value, so the literal builds at its OWN type and the
|
|
4672
|
-
// slot's width coercion narrows it (divergence 36's copy stance — the
|
|
4673
|
-
// extra fields drop in the copy). The probe is against the CHECKER's
|
|
4674
|
-
// contextual type, not the mapped shape: a property the contextual
|
|
4675
|
-
// type carries but the shape dropped (settled value/reason,
|
|
4676
|
-
// generic-callable members) belongs to the drop paths below, and
|
|
4677
|
-
// index-signature contexts keep every key (overflow capture).
|
|
4678
|
-
if (mapped?.kind === "record" && expr.properties.length > 0) {
|
|
4679
|
-
const ctxShape = lowerer.shapes.get(mapped.shapeId);
|
|
4680
|
-
if (ctxShape && !ctxShape.indexValue && !ctxShape.tuple) {
|
|
4681
|
-
const names = new Set(ctxShape.fields.map((f) => f.name));
|
|
4682
|
-
const ctxHasProp = (name) => {
|
|
4683
|
-
const members = tsType.isUnionType() ? ts.constituentTypes(tsType) : [tsType];
|
|
4684
|
-
return members.some((m) => lowerer.checker.getPropertyOfType(m, name) !== undefined);
|
|
4685
|
-
};
|
|
4686
|
-
const extraOf = (text) => !names.has(text) && !ctxHasProp(text);
|
|
4687
|
-
const extra = expr.properties.some((p) => {
|
|
4688
|
-
if (ts.isSpreadAssignment(p) || !p.name)
|
|
4689
|
-
return false;
|
|
4690
|
-
if (ts.isIdentifier(p.name) || ts.isStringLiteral(p.name))
|
|
4691
|
-
return extraOf(p.name.text);
|
|
4692
|
-
if (ts.isNumericLiteral(p.name))
|
|
4693
|
-
return extraOf(String(Number(p.name.text)));
|
|
4694
|
-
return false; // computed keys keep their existing paths
|
|
4695
|
-
});
|
|
4696
|
-
if (extra)
|
|
4697
|
-
mapped = lowerer.mapTypeOf(lowerer.typeOf(expr)) ?? mapped;
|
|
4698
|
-
}
|
|
4699
|
-
}
|
|
4700
|
-
// A union-typed slot (`const r: Res = { kind: "ok", ... }`) contextually
|
|
4701
|
-
// types the literal as the WHOLE union; build the literal as its own
|
|
4702
|
-
// (arm) shape and let the slot's coercion wrap it into the union. An
|
|
4703
|
-
// unknown-typed slot (`JSON.stringify({ a: 1 })`) likewise. An
|
|
4704
|
-
// UNMAPPABLE context falls back the same way — the literal's own type
|
|
4705
|
-
// is the plain record the slot coerces. A CLASS-INSTANCE context
|
|
4706
|
-
// (`const p: Point = { x, y }` — tsc's structural view of data
|
|
4707
|
-
// classes) falls back too: the literal builds as its own record and
|
|
4708
|
-
// the slot's width coercion constructs through the trivial
|
|
4709
|
-
// parameter-property constructor (recordToClassPlan), or fences with
|
|
4710
|
-
// the record-shape story.
|
|
4711
|
-
// A jsval-mapped context reaches here only when lowerExpr's island
|
|
4712
|
-
// gate DECLINED it (a project-declared typedef that absorbed to the
|
|
4713
|
-
// island through checker-`any` field residue): the literal builds at
|
|
4714
|
-
// its own type like every unmappable context.
|
|
4715
|
-
if (mapped === null || mapped.kind === "union" || mapped.kind === "dyn" || mapped.kind === "object" || mapped.kind === "jsval") {
|
|
4716
|
-
const ctxUnion = mapped?.kind === "union" ? mapped : null;
|
|
4717
|
-
mapped = lowerer.mapTypeOf(lowerer.typeOf(expr)) ?? mapped;
|
|
4718
|
-
// A literal whose own shape re-tags into NO arm of the contextual
|
|
4719
|
-
// union, where the union has exactly ONE record arm: build AS that
|
|
4720
|
-
// arm — there is no ambiguity (tsc already checked the literal
|
|
4721
|
-
// against the union, and the record arm is the only shape it can
|
|
4722
|
-
// inhabit), and the arm's field types drive every property's
|
|
4723
|
-
// coercion (`{ env: {...spread...}, onCleanup }` against an
|
|
4724
|
-
// optional options param — the env value builds by ITS contextual
|
|
4725
|
-
// index-signature type and wraps into the arm's `| undefined`
|
|
4726
|
-
// field, where the literal's own inferred width would mismatch).
|
|
4727
|
-
// Empty literals (`_env = {}` — the fieldless own shape) and
|
|
4728
|
-
// literals against PURE index-signature arms (`{ OPENSSL_CONF:
|
|
4729
|
-
// candidate }` — keys become overflow entries) are the same rule's
|
|
4730
|
-
// simplest cases. The empty-array-in-union rule, record form.
|
|
4731
|
-
if (ctxUnion) {
|
|
4732
|
-
const def = lowerer.unions.get(ctxUnion.unionId);
|
|
4733
|
-
const recordArms = def?.arms.filter((a) => a.kind === "record") ?? [];
|
|
4734
|
-
if (recordArms.length === 1) {
|
|
4735
|
-
const armShape = lowerer.shapes.get(recordArms[0].shapeId);
|
|
4736
|
-
if ((mapped?.kind !== "record" || mapped.shapeId !== recordArms[0].shapeId) &&
|
|
4737
|
-
!armShape?.tuple) {
|
|
4738
|
-
mapped = recordArms[0];
|
|
4739
|
-
}
|
|
4740
|
-
}
|
|
4741
|
-
else if (recordArms.length > 1) {
|
|
4742
|
-
const ownShapeId = mapped?.kind === "record" ? mapped.shapeId : null;
|
|
4743
|
-
// SEVERAL record arms (the reducer-action / discriminated-message
|
|
4744
|
-
// pattern): the literal's own inferred shape re-tags into no arm
|
|
4745
|
-
// — its field types widened per field (`parsed: 1` against
|
|
4746
|
-
// `parsed: number | null`), so the IR-level candidate probe is
|
|
4747
|
-
// ambiguous (a narrower arm also admits the literal by dropping
|
|
4748
|
-
// fields). The LITERAL types tsc checked carry the discriminant
|
|
4749
|
-
// the shapes erased: match the literal's fields against each
|
|
4750
|
-
// union member's CHECKER types (literal-vs-literal field pairs
|
|
4751
|
-
// decide by value — the `kind: "a"` discriminant), and when
|
|
4752
|
-
// exactly ONE member fits, build AS that arm — its field types
|
|
4753
|
-
// drive every property's coercion, exactly the single-record-arm
|
|
4754
|
-
// rule above. Ambiguous literals keep the SC2003 fence.
|
|
4755
|
-
if (ownShapeId === null || !recordArms.some((a) => a.shapeId === ownShapeId)) {
|
|
4756
|
-
const arm = literalUnionArmOf(lowerer, expr, tsType, recordArms);
|
|
4757
|
-
if (arm)
|
|
4758
|
-
mapped = arm;
|
|
4759
|
-
}
|
|
4760
|
-
}
|
|
4761
|
-
}
|
|
4762
|
-
}
|
|
4763
|
-
// The CJS EXPORT-TABLE literal in VALUE position (JS): importers reach
|
|
4764
|
-
// every member through alias plumbing and accessor lifts — the record
|
|
4765
|
-
// VALUE exists for the module's own reads (Object.keys, internal
|
|
4766
|
-
// member reads), so it keeps exactly the plain fields whose values
|
|
4767
|
-
// lower; accessor entries and members with no value representation
|
|
4768
|
-
// (rest-param function types) narrow away. SEMANTICS.md documents the
|
|
4769
|
-
// enumeration divergence.
|
|
4770
|
-
if (isJsSourceFile(expr.getSourceFile()) &&
|
|
4771
|
-
(!mapped || expr.properties.some((p) => ts.isGetAccessorDeclaration(p) || ts.isSetAccessorDeclaration(p))) &&
|
|
4772
|
-
isCjsExportTableLiteral(expr)) {
|
|
4773
|
-
const fields = [];
|
|
4774
|
-
for (const prop of expr.properties) {
|
|
4775
|
-
if (ts.isGetAccessorDeclaration(prop) || ts.isSetAccessorDeclaration(prop))
|
|
4776
|
-
continue;
|
|
4777
|
-
const propName = ts.isSpreadAssignment(prop) ? undefined : prop.name;
|
|
4778
|
-
if (!propName || !(ts.isIdentifier(propName) || ts.isStringLiteral(propName)))
|
|
4779
|
-
continue;
|
|
4780
|
-
const name = propName.text;
|
|
4781
|
-
let v = null;
|
|
4782
|
-
if (ts.isShorthandPropertyAssignment(prop)) {
|
|
4783
|
-
v = probeLower(lowerer, propName);
|
|
4784
|
-
}
|
|
4785
|
-
else if (ts.isPropertyAssignment(prop)) {
|
|
4786
|
-
v = probeLower(lowerer, prop.initializer);
|
|
4787
|
-
}
|
|
4788
|
-
if (!v || v.type.kind === "void" || v.type.kind === "caught" || v.type.kind === "jsval")
|
|
4789
|
-
continue;
|
|
4790
|
-
if (isUnitType(v.type))
|
|
4791
|
-
continue;
|
|
4792
|
-
fields.push({ name, value: v });
|
|
4793
|
-
}
|
|
4794
|
-
// Canonical (sorted) field order — the shape registry's invariant;
|
|
4795
|
-
// the dropped reads are all pure, so reordering loses nothing.
|
|
4796
|
-
fields.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
|
|
4797
|
-
const shapeId = lowerer.shapes.intern(fields.map((f) => ({ name: f.name, type: f.value.type })));
|
|
4798
|
-
return { kind: "recordLit", fields, type: { kind: "record", shapeId }, loc };
|
|
4799
|
-
}
|
|
4800
|
-
// The JS declaration fallback, literal-side (the checked-dynamic tree-array rule's
|
|
4801
|
-
// object form): a literal whose shape has no static home — an
|
|
4802
|
-
// unmappable contextual type over unmappable own fields (the
|
|
4803
|
-
// PropertyDescriptorMap argument of Object.defineProperties, nested
|
|
4804
|
-
// descriptor records with `any` values) — builds as a dyn OBJECT:
|
|
4805
|
-
// each field converts through the usual dyn boundary, and dynamic
|
|
4806
|
-
// consumers ride the keyed-dyn paths. TypeScript keeps the fence.
|
|
4807
|
-
if ((!mapped || mapped.kind === "dyn") && isJsSourceFile(expr.getSourceFile())) {
|
|
4808
|
-
return lowerDynObjectLiteral(lowerer, expr);
|
|
4809
|
-
}
|
|
4810
|
-
if (!mapped || mapped.kind !== "record")
|
|
4811
|
-
lowerer.badType(expr, tsType);
|
|
4812
|
-
let type = mapped;
|
|
4813
|
-
let shape = lowerer.shapes.get(type.shapeId);
|
|
4814
|
-
// ACCESSOR properties, JS literals only (TS accessors fill the shape's
|
|
4815
|
-
// %get:/%set: closure slots below): no record storage exists for them,
|
|
4816
|
-
// so the literal's shape NARROWS to its plain fields (reads resolve
|
|
4817
|
-
// through the lifted accessors; Object.keys over the value omits
|
|
4818
|
-
// accessor names — the documented divergence).
|
|
4819
|
-
if (isJsSourceFile(expr.getSourceFile())) {
|
|
4820
|
-
const accessorNames = new Set(expr.properties
|
|
4821
|
-
.filter((p) => ts.isGetAccessorDeclaration(p) || ts.isSetAccessorDeclaration(p))
|
|
4822
|
-
.map((p) => (p.name && (ts.isIdentifier(p.name) || ts.isStringLiteral(p.name)) ? p.name.text : ""))
|
|
4823
|
-
.filter((n) => n !== ""));
|
|
4824
|
-
if (accessorNames.size > 0) {
|
|
4825
|
-
const narrowed = shape.fields.filter((f) => !accessorNames.has(f.name));
|
|
4826
|
-
const narrowedId = lowerer.shapes.intern(narrowed.map((f) => ({ name: f.name, type: f.type })), false, shape.indexValue);
|
|
4827
|
-
type = { kind: "record", shapeId: narrowedId };
|
|
4828
|
-
shape = lowerer.shapes.get(narrowedId);
|
|
4829
|
-
}
|
|
4830
|
-
}
|
|
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
|
-
}
|
|
4849
|
-
// The clone probe may lower the leading spread before declining (a
|
|
4850
|
-
// static `as` cast can make the checker-visible shape match while its
|
|
4851
|
-
// erased IR value keeps a wider shape). Reuse that exact value in the
|
|
4852
|
-
// ordinary spread path so lowering still happens once.
|
|
4853
|
-
let leadingSpreadLowered = null;
|
|
4854
|
-
// The overwhelmingly common immutable-update form over a record:
|
|
4855
|
-
// `{ ...model, changed, other: value }`. The historic lowering expanded
|
|
4856
|
-
// the spread into one recordGet per untouched field at EVERY site, then
|
|
4857
|
-
// the backends inlined all of those retains and stores into the caller.
|
|
4858
|
-
// A 178-field model updated hundreds of times consequently produced a
|
|
4859
|
-
// half-million-line LLVM function. Keep the same evaluation/ownership
|
|
4860
|
-
// semantics in one compact IR node: source first, explicit overrides in
|
|
4861
|
-
// source order, and one backend clone helper per shape.
|
|
4862
|
-
//
|
|
4863
|
-
// Stay deliberately narrow. Tuple/index/accessor shapes, conditional or
|
|
4864
|
-
// multiple spreads, spread-after-explicit order, and shape-changing width
|
|
4865
|
-
// copies retain their existing lowering and diagnostics.
|
|
4866
|
-
if (!isJsSourceFile(expr.getSourceFile()) &&
|
|
4867
|
-
!shape.indexValue &&
|
|
4868
|
-
!shape.tuple &&
|
|
4869
|
-
!shapeHasAccessorSlots(shape) &&
|
|
4870
|
-
expr.properties.length >= 2 &&
|
|
4871
|
-
ts.isSpreadAssignment(expr.properties[0]) &&
|
|
4872
|
-
!conditionalSpreadOf(expr.properties[0].expression) &&
|
|
4873
|
-
expr.properties.slice(1).every((p) => (ts.isPropertyAssignment(p) || ts.isShorthandPropertyAssignment(p)) &&
|
|
4874
|
-
p.name !== undefined &&
|
|
4875
|
-
(ts.isIdentifier(p.name) ||
|
|
4876
|
-
ts.isStringLiteral(p.name) ||
|
|
4877
|
-
ts.isNumericLiteral(p.name) ||
|
|
4878
|
-
(ts.isComputedPropertyName(p.name) && literalComputedKey(lowerer, p.name) !== null)))) {
|
|
4879
|
-
const spread = expr.properties[0];
|
|
4880
|
-
const props = expr.properties.slice(1);
|
|
4881
|
-
const names = props.map((p) => propNameText(lowerer, p.name));
|
|
4882
|
-
const unique = new Set(names);
|
|
4883
|
-
const sourceType = lowerer.mapTypeOf(lowerer.typeOf(spread.expression));
|
|
4884
|
-
if (sourceType?.kind === "record" &&
|
|
4885
|
-
sourceType.shapeId === type.shapeId &&
|
|
4886
|
-
unique.size === names.length &&
|
|
4887
|
-
names.every((name) => fieldTypes.has(name))) {
|
|
4888
|
-
const source = lowerer.lowerExpr(spread.expression);
|
|
4889
|
-
if (source.type.kind === "record" && source.type.shapeId === type.shapeId) {
|
|
4890
|
-
const overrides = [];
|
|
4891
|
-
for (let i = 0; i < props.length; i++) {
|
|
4892
|
-
const p = props[i];
|
|
4893
|
-
const name = names[i];
|
|
4894
|
-
const fieldType = fieldTypes.get(name);
|
|
4895
|
-
const valueNode = ts.isPropertyAssignment(p) ? p.initializer : p;
|
|
4896
|
-
let value = ts.isPropertyAssignment(p)
|
|
4897
|
-
? lowerer.lowerExpr(p.initializer)
|
|
4898
|
-
: lowerer.lowerShorthandValue(p);
|
|
4899
|
-
value = lowerer.coerceInto(valueNode, value, fieldType);
|
|
4900
|
-
if (!typeEquals(value.type, fieldType))
|
|
4901
|
-
lowerer.badType(valueNode, lowerer.typeOf(valueNode));
|
|
4902
|
-
overrides.push({ name, value });
|
|
4903
|
-
}
|
|
4904
|
-
return { kind: "recordClone", source, overrides, type, loc };
|
|
4905
|
-
}
|
|
4906
|
-
leadingSpreadLowered = source;
|
|
4907
|
-
}
|
|
4908
|
-
}
|
|
4909
|
-
// A PURE index-signature target with spreads — `{ ...process.env }`,
|
|
4910
|
-
// `{ ...process.env, ...extraEnv }`, `{ ...env, PATH: p }` (the
|
|
4911
|
-
// spawn-env pattern): the field-by-field desugar below cannot
|
|
4912
|
-
// enumerate runtime overflow keys, so the literal lowers as ONE
|
|
4913
|
-
// interned merge-helper call — contributors apply in literal order
|
|
4914
|
-
// with keyed writes (JS last-write-wins), sources and values evaluate
|
|
4915
|
-
// once each in source order (the call's argument order). A CONDITIONAL
|
|
4916
|
-
// spread `...(c ? { k: v } : {})` contributes its one key as a ternary
|
|
4917
|
-
// — cond once, v lazily, the empty arm holding the value slot's
|
|
4918
|
-
// undefined arm (the explicit-undefined-is-absent stance: JSON and
|
|
4919
|
-
// child-env builders drop it, exactly Node's absent key); targets
|
|
4920
|
-
// with declared fields keep the historic desugar below.
|
|
4921
|
-
const isRuntimeComputedKey = (p) => !ts.isSpreadAssignment(p) &&
|
|
4922
|
-
p.name !== undefined &&
|
|
4923
|
-
ts.isComputedPropertyName(p.name) &&
|
|
4924
|
-
literalComputedKey(lowerer, p.name) === null;
|
|
4925
|
-
if (shape.indexValue &&
|
|
4926
|
-
shape.fields.length === 0 &&
|
|
4927
|
-
!shape.tuple &&
|
|
4928
|
-
(expr.properties.some((p) => ts.isSpreadAssignment(p)) || expr.properties.some(isRuntimeComputedKey))) {
|
|
4929
|
-
const contributors = [];
|
|
4930
|
-
let mergeable = true;
|
|
4931
|
-
for (const prop of expr.properties) {
|
|
4932
|
-
if (ts.isSpreadAssignment(prop)) {
|
|
4933
|
-
const cs = conditionalSpreadOf(prop.expression);
|
|
4934
|
-
if (cs === "unsupported" || (cs && cs.props.length !== 1)) {
|
|
4935
|
-
lowerer.unsupported("SC1090", prop, "conditional spreads beyond `...(c ? { field: v } : {})` (exactly one property against an empty arm)");
|
|
4936
|
-
}
|
|
4937
|
-
if (cs) {
|
|
4938
|
-
const absent = lowerer.wrappedUndefined(shape.indexValue, locOf(prop));
|
|
4939
|
-
if (!absent) {
|
|
4940
|
-
lowerer.unsupported("SC1090", prop, `conditional spreads into '${lowerer.fmt(shape.indexValue)}'-valued index-signature keys (the empty arm needs an undefined arm to hold — write the key in an if statement instead)`);
|
|
4941
|
-
}
|
|
4942
|
-
const csProp = cs.props[0];
|
|
4943
|
-
const cond = lowerer.lowerCondition(cs.cond);
|
|
4944
|
-
const vNode = ts.isPropertyAssignment(csProp) ? csProp.initializer : csProp;
|
|
4945
|
-
const v = lowerer.intoIndexValueSlot(ts.isPropertyAssignment(csProp) ? lowerer.lowerExpr(csProp.initializer) : lowerer.lowerShorthandValue(csProp), shape.indexValue, vNode);
|
|
4946
|
-
contributors.push({
|
|
4947
|
-
kind: "field",
|
|
4948
|
-
name: csProp.name.text,
|
|
4949
|
-
value: {
|
|
4950
|
-
kind: "ternary",
|
|
4951
|
-
cond,
|
|
4952
|
-
then: cs.whenTrue ? v : absent,
|
|
4953
|
-
else_: cs.whenTrue ? absent : v,
|
|
4954
|
-
type: shape.indexValue,
|
|
4955
|
-
loc: locOf(prop),
|
|
4956
|
-
},
|
|
4957
|
-
});
|
|
4958
|
-
continue;
|
|
4959
|
-
}
|
|
4960
|
-
let src = lowerer.lowerExpr(prop.expression);
|
|
4961
|
-
src = lowerOptionalIndexSpreadSource(lowerer, src, locOf(prop)) ?? src;
|
|
4962
|
-
if (src.type.kind !== "record") {
|
|
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})`);
|
|
4964
|
-
}
|
|
4965
|
-
fenceAccessorSpreadSource(lowerer, prop, lowerer.shapes.get(src.type.shapeId));
|
|
4966
|
-
contributors.push({ kind: "spread", shapeId: src.type.shapeId, value: src });
|
|
4967
|
-
continue;
|
|
4968
|
-
}
|
|
4969
|
-
if ((ts.isPropertyAssignment(prop) || ts.isShorthandPropertyAssignment(prop)) &&
|
|
4970
|
-
prop.name &&
|
|
4971
|
-
(ts.isIdentifier(prop.name) ||
|
|
4972
|
-
ts.isStringLiteral(prop.name) ||
|
|
4973
|
-
ts.isNumericLiteral(prop.name) ||
|
|
4974
|
-
(ts.isComputedPropertyName(prop.name) && literalComputedKey(lowerer, prop.name) !== null))) {
|
|
4975
|
-
const value = ts.isPropertyAssignment(prop)
|
|
4976
|
-
? lowerer.intoIndexValueSlot(lowerer.lowerExpr(prop.initializer), shape.indexValue, prop.initializer)
|
|
4977
|
-
: lowerer.intoIndexValueSlot(lowerer.lowerShorthandValue(prop), shape.indexValue, prop);
|
|
4978
|
-
contributors.push({ kind: "field", name: propNameText(lowerer, prop.name), value });
|
|
4979
|
-
continue;
|
|
4980
|
-
}
|
|
4981
|
-
// A RUNTIME-keyed property (`{ ...m, ["a" + "b"]: "" }`, `{ [K]:
|
|
4982
|
-
// v }` where K is a runtime string): the key evaluates before its
|
|
4983
|
-
// value (JS's per-property order — both are helper arguments) and
|
|
4984
|
-
// writes through the signature exactly like a spread's keys.
|
|
4985
|
-
// Number/boolean/unknown keys stringify (ToPropertyKey).
|
|
4986
|
-
if (ts.isPropertyAssignment(prop) && ts.isComputedPropertyName(prop.name)) {
|
|
4987
|
-
let k = lowerer.lowerExpr(prop.name.expression);
|
|
4988
|
-
if (k.type.kind === "f64" || k.type.kind === "bool" || k.type.kind === "dyn") {
|
|
4989
|
-
k = { kind: "toString", operand: k, type: STRING, loc: locOf(prop.name) };
|
|
4990
|
-
}
|
|
4991
|
-
if (k.type.kind !== "string") {
|
|
4992
|
-
lowerer.unsupported("SC1090", prop.name, `'${lowerer.fmt(k.type)}'-typed computed property keys (string, number, boolean, and unknown keys stringify)`);
|
|
4993
|
-
}
|
|
4994
|
-
const value = lowerer.intoIndexValueSlot(lowerer.lowerExpr(prop.initializer), shape.indexValue, prop.initializer);
|
|
4995
|
-
contributors.push({ kind: "keyedField", key: k, value });
|
|
4996
|
-
continue;
|
|
4997
|
-
}
|
|
4998
|
-
mergeable = false;
|
|
4999
|
-
break;
|
|
5000
|
-
}
|
|
5001
|
-
if (mergeable) {
|
|
5002
|
-
const helper = lowerIndexMergeHelper(lowerer, type.shapeId, contributors, loc);
|
|
5003
|
-
if (helper === null) {
|
|
5004
|
-
lowerer.unsupported("SC1090", expr, `object spread into '${lowerer.fmt(type)}' from these source shapes (spread sources must be index-signature records whose value type is, or lifts into, the target's)`);
|
|
5005
|
-
}
|
|
5006
|
-
return {
|
|
5007
|
-
kind: "call",
|
|
5008
|
-
callee: helper,
|
|
5009
|
-
args: contributors.flatMap((c) => (c.kind === "keyedField" ? [c.key, c.value] : [c.value])),
|
|
5010
|
-
type,
|
|
5011
|
-
loc,
|
|
5012
|
-
};
|
|
5013
|
-
}
|
|
5014
|
-
}
|
|
5015
|
-
// Unfoldable computed keys outside the index-signature merge path:
|
|
5016
|
-
// no record shape can hold a runtime-decided name.
|
|
5017
|
-
for (const prop of expr.properties) {
|
|
5018
|
-
if (isRuntimeComputedKey(prop)) {
|
|
5019
|
-
lowerer.unsupported("SC1090", prop.name, "computed property keys (compile-time-known keys fold — a pure expression whose checker type is one string or number literal: consts, enum members, quoted keys, templates of those — `{ [MARKER]: v }`; runtime string keys write through an index-signature target; symbol keys stay out)");
|
|
5020
|
-
}
|
|
5021
|
-
}
|
|
5022
|
-
// A DECLARED-fields target built ENTIRELY from index-signature spreads —
|
|
5023
|
-
// `{ ...Object.fromEntries(...), ...Object.fromEntries(...) }` typed
|
|
5024
|
-
// AppConfig (the defaults-merge idiom over runtime-keyed sources): the
|
|
5025
|
-
// field-by-field desugar cannot enumerate runtime keys, so the literal
|
|
5026
|
-
// lowers as ONE interned merge-helper call — sources evaluate once each
|
|
5027
|
-
// in source order (the call's argument order — computed sources
|
|
5028
|
-
// included, no re-read), contributors apply in order with per-key
|
|
5029
|
-
// dispatch onto the declared fields (JS last-write-wins). Divergence 68
|
|
5030
|
-
// has the runtime rules (validated collisions, extra keys dropped).
|
|
5031
|
-
if (!shape.indexValue &&
|
|
5032
|
-
!shape.tuple &&
|
|
5033
|
-
shape.fields.length > 0 &&
|
|
5034
|
-
expr.properties.length > 0 &&
|
|
5035
|
-
expr.properties.every((p) => ts.isSpreadAssignment(p) && !conditionalSpreadOf(p.expression)) &&
|
|
5036
|
-
expr.properties.some((p) => {
|
|
5037
|
-
const t = lowerer.mapTypeOf(lowerer.typeOf(p.expression));
|
|
5038
|
-
return t?.kind === "record" && !!lowerer.shapes.get(t.shapeId)?.indexValue;
|
|
5039
|
-
})) {
|
|
5040
|
-
return lowerDeclaredSpreadMerge(lowerer, expr, type, shape, loc);
|
|
5041
|
-
}
|
|
5042
|
-
const shapeMismatch = (node) => {
|
|
5043
|
-
const own = lowerer.mapTypeOf(lowerer.typeOf(expr));
|
|
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"
|
|
5045
|
-
? (lowerer.describeRecordWidthBlocker(own.shapeId, type.shapeId) ?? undefined)
|
|
5046
|
-
: undefined));
|
|
5047
|
-
throw new PoisonError();
|
|
5048
|
-
};
|
|
5049
|
-
const fields = [];
|
|
5050
|
-
// Field names introduced by conditional spreads: their ternary carries
|
|
5051
|
-
// the spread's whole evaluation (cond once, value lazily), so a LATER
|
|
5052
|
-
// contributor overriding one would silently drop that evaluation —
|
|
5053
|
-
// collisions fence in both directions.
|
|
5054
|
-
const conditionalNames = new Set();
|
|
5055
|
-
// Member names DROPPED from the shape (JS deferral — unlowerable pure
|
|
5056
|
-
// member reads narrow away; see the catch in the property loop).
|
|
5057
|
-
const droppedNames = new Set();
|
|
5058
|
-
for (const prop of expr.properties) {
|
|
5059
|
-
if (ts.isSpreadAssignment(prop)) {
|
|
5060
|
-
const cs = conditionalSpreadOf(prop.expression);
|
|
5061
|
-
if (cs && cs !== "unsupported") {
|
|
5062
|
-
// `...(c ? { k: v } : {})` — ONE conditional field at the
|
|
5063
|
-
// spread's own position: cond evaluates exactly once, `v` only
|
|
5064
|
-
// when the non-empty arm is taken (ternary arms are lazy), and
|
|
5065
|
-
// the empty arm holds the interned undefined arm — exactly the
|
|
5066
|
-
// omitted-optional-field representation, which is also why the
|
|
5067
|
-
// target field must be optional (tsc types the idiom that way).
|
|
5068
|
-
const csProp = cs.props[0]; // single-prop-checked in the fence pass
|
|
5069
|
-
const name = csProp.name.text;
|
|
5070
|
-
const fieldType = fieldTypes.get(name);
|
|
5071
|
-
if (!fieldType) {
|
|
5072
|
-
if (shape.indexValue) {
|
|
5073
|
-
lowerer.unsupported("SC1090", prop, "conditional spreads into index-signature keys (overflow entries model presence — write the key in an if statement instead)");
|
|
5074
|
-
}
|
|
5075
|
-
throw shapeMismatch(prop);
|
|
5076
|
-
}
|
|
5077
|
-
const absent = lowerer.wrappedUndefined(fieldType, locOf(prop));
|
|
5078
|
-
if (!absent) {
|
|
5079
|
-
lowerer.unsupported("SC1090", prop, `conditional spreads onto the required field '${name}' (the empty arm leaves it undefined — declare the field optional)`);
|
|
5080
|
-
}
|
|
5081
|
-
if (fields.some((f) => f.name === name)) {
|
|
5082
|
-
lowerer.unsupported("SC1090", prop, `conditional spread of '${name}' over an earlier '${name}' (the desugar keeps one entry per name — restructure so each name has one contributor)`);
|
|
5083
|
-
}
|
|
5084
|
-
const cond = lowerer.lowerCondition(cs.cond);
|
|
5085
|
-
const valueNode = ts.isPropertyAssignment(csProp) ? csProp.initializer : csProp;
|
|
5086
|
-
let v = ts.isPropertyAssignment(csProp)
|
|
5087
|
-
? lowerer.lowerExpr(csProp.initializer)
|
|
5088
|
-
: lowerer.lowerShorthandValue(csProp);
|
|
5089
|
-
v = lowerer.coerceInto(valueNode, v, fieldType);
|
|
5090
|
-
if (!typeEquals(v.type, fieldType))
|
|
5091
|
-
lowerer.badType(valueNode, lowerer.typeOf(valueNode));
|
|
5092
|
-
conditionalNames.add(name);
|
|
5093
|
-
fields.push({
|
|
5094
|
-
name,
|
|
5095
|
-
value: {
|
|
5096
|
-
kind: "ternary",
|
|
5097
|
-
cond,
|
|
5098
|
-
then: cs.whenTrue ? v : absent,
|
|
5099
|
-
else_: cs.whenTrue ? absent : v,
|
|
5100
|
-
type: fieldType,
|
|
5101
|
-
loc: locOf(prop),
|
|
5102
|
-
},
|
|
5103
|
-
});
|
|
5104
|
-
continue;
|
|
5105
|
-
}
|
|
5106
|
-
// `{ ...base, ... }` — field-by-field copy of a known record
|
|
5107
|
-
// shape. Every source field must land on the target shape with an
|
|
5108
|
-
// equal type (a wider source would silently DROP fields JS keeps —
|
|
5109
|
-
// the width fence, same as literals). Later contributors override
|
|
5110
|
-
// earlier ones (JS last-write-wins; the reads are side-effect-free,
|
|
5111
|
-
// so dropping the earlier read is exact). Identifier sources
|
|
5112
|
-
// re-read per field (historic path); any OTHER source must be a
|
|
5113
|
-
// re-emittable pure read, sharing one lowered node per field.
|
|
5114
|
-
// The desugar's one-entry-per-name list reads spread fields
|
|
5115
|
-
// EAGERLY at the spread's position, so an explicit property
|
|
5116
|
-
// BEFORE a spread would need JS's overwrite — order-fenced (the
|
|
5117
|
-
// index-signature merge path above takes any order).
|
|
5118
|
-
if (expr.properties
|
|
5119
|
-
.slice(0, expr.properties.indexOf(prop))
|
|
5120
|
-
.some((p) => !ts.isSpreadAssignment(p))) {
|
|
5121
|
-
lowerer.unsupported("SC1090", prop, "object spread after explicit properties (spreads must come first — a later spread would overwrite them with JS semantics the desugar does not model)");
|
|
5122
|
-
}
|
|
5123
|
-
let srcNode = prop.expression;
|
|
5124
|
-
while (ts.isParenthesizedExpression(srcNode))
|
|
5125
|
-
srcNode = srcNode.expression;
|
|
5126
|
-
const srcLowered = prop === expr.properties[0] && leadingSpreadLowered !== null
|
|
5127
|
-
? leadingSpreadLowered
|
|
5128
|
-
: ts.isIdentifier(srcNode)
|
|
5129
|
-
? null
|
|
5130
|
-
: lowerer.lowerExpr(srcNode);
|
|
5131
|
-
const srcType = srcLowered ? srcLowered.type : lowerer.mapTypeOf(lowerer.typeOf(srcNode));
|
|
5132
|
-
// `...options.installConfig` — a spread of `Partial<X> | undefined`
|
|
5133
|
-
// (the optional-options merge idiom `{ ...DEFAULTS, ...overrides }`):
|
|
5134
|
-
// JS spreads nothing for the unit arm and copies present keys
|
|
5135
|
-
// otherwise. Per target field the desugar builds
|
|
5136
|
-
// `present ? extracted : earlier` — present tests the source's
|
|
5137
|
-
// record tag AND (optional source fields) the field's own value
|
|
5138
|
-
// arm; absent keeps the earlier contributor's value (both reads
|
|
5139
|
-
// are pure, so the reorder into the ternary is unobservable).
|
|
5140
|
-
if (srcType?.kind === "union") {
|
|
5141
|
-
const def = lowerer.unions.get(srcType.unionId);
|
|
5142
|
-
const recArms = def?.arms.filter((a) => a.kind === "record") ?? [];
|
|
5143
|
-
if (!def ||
|
|
5144
|
-
recArms.length !== 1 ||
|
|
5145
|
-
!def.arms.every((a) => a.kind === "record" || isUnitType(a))) {
|
|
5146
|
-
lowerer.unsupported("SC1090", prop, `object spread of '${lowerer.fmt(srcType)}' sources (only known record shapes spread — ${NARROW_FIRST})`);
|
|
5147
|
-
}
|
|
5148
|
-
if (srcLowered && !pureReemittable(srcLowered)) {
|
|
5149
|
-
lowerer.unsupported("SC1090", prop, "object spread of computed sources (the field copies re-read the source — bind it to a const first)");
|
|
5150
|
-
}
|
|
5151
|
-
const recArm = recArms[0];
|
|
5152
|
-
const recTag = def.arms.indexOf(recArm);
|
|
5153
|
-
// A checked-dynamic VALUE under the union-mapped checker type
|
|
5154
|
-
// (a JS dyn-holding binding): the present/absent desugar tests
|
|
5155
|
-
// union tags a dyn box does not carry — fence honestly instead
|
|
5156
|
-
// of the validator's ICE.
|
|
5157
|
-
const probedSrc = srcLowered ?? probeLower(lowerer, srcNode);
|
|
5158
|
-
if (probedSrc?.type.kind === "dyn") {
|
|
5159
|
-
lowerer.unsupported("SC1090", prop, `object spread of a checked-dynamic '${lowerer.fmt(srcType)}' source (${NARROW_FIRST})`);
|
|
5160
|
-
}
|
|
5161
|
-
const srcShape = lowerer.shapes.get(recArm.shapeId);
|
|
5162
|
-
if (!srcShape)
|
|
5163
|
-
throw new InternalCompilerError(`lowerer bug: spread of unknown shape ${recArm.shapeId}`);
|
|
5164
|
-
fenceAccessorSpreadSource(lowerer, prop, srcShape);
|
|
5165
|
-
if (srcShape.indexValue || shape.indexValue) {
|
|
5166
|
-
lowerer.unsupported("SC1090", prop, "object spread involving index-signature shapes (overflow keys are runtime state — copy the fields you need explicitly)");
|
|
5167
|
-
}
|
|
5168
|
-
const laterNames = new Set();
|
|
5169
|
-
for (const later of expr.properties.slice(expr.properties.indexOf(prop) + 1)) {
|
|
5170
|
-
if (ts.isSpreadAssignment(later)) {
|
|
5171
|
-
if (conditionalSpreadOf(later.expression))
|
|
5172
|
-
continue;
|
|
5173
|
-
const lt = lowerer.mapTypeOf(lowerer.typeOf(later.expression));
|
|
5174
|
-
if (lt?.kind === "record") {
|
|
5175
|
-
for (const lf of lowerer.shapes.get(lt.shapeId)?.fields ?? [])
|
|
5176
|
-
laterNames.add(lf.name);
|
|
5177
|
-
}
|
|
5178
|
-
continue;
|
|
5179
|
-
}
|
|
5180
|
-
if (later.name &&
|
|
5181
|
-
(ts.isIdentifier(later.name) ||
|
|
5182
|
-
ts.isStringLiteral(later.name) ||
|
|
5183
|
-
ts.isNumericLiteral(later.name) ||
|
|
5184
|
-
(ts.isComputedPropertyName(later.name) && literalComputedKey(lowerer, later.name) !== null))) {
|
|
5185
|
-
laterNames.add(propNameText(lowerer, later.name));
|
|
5186
|
-
}
|
|
5187
|
-
}
|
|
5188
|
-
const srcRef = () => srcLowered ?? lowerer.lowerExpr(srcNode);
|
|
5189
|
-
for (const f of srcShape.fields) {
|
|
5190
|
-
if (laterNames.has(f.name))
|
|
5191
|
-
continue;
|
|
5192
|
-
const targetType = fieldTypes.get(f.name);
|
|
5193
|
-
// No slot on the target shape: the copy DROPS the field
|
|
5194
|
-
// (divergence 36's stance, same as the plain-record spread).
|
|
5195
|
-
if (!targetType)
|
|
5196
|
-
continue;
|
|
5197
|
-
if (conditionalNames.has(f.name)) {
|
|
5198
|
-
lowerer.unsupported("SC1090", prop, `spread of '${f.name}' over an earlier conditional spread (the desugar keeps one entry per name — restructure so each name has one contributor)`);
|
|
5199
|
-
}
|
|
5200
|
-
const fRead = () => ({
|
|
5201
|
-
kind: "recordGet",
|
|
5202
|
-
obj: { kind: "unionNarrow", unionId: srcType.unionId, tag: recTag, value: srcRef(), type: recArm, loc: locOf(prop) },
|
|
5203
|
-
shapeId: recArm.shapeId,
|
|
5204
|
-
field: f.name,
|
|
5205
|
-
type: f.type,
|
|
5206
|
-
loc: locOf(prop),
|
|
5207
|
-
});
|
|
5208
|
-
let cond = { kind: "unionIsTag", unionId: srcType.unionId, tag: recTag, negated: false, value: srcRef(), type: BOOL, loc: locOf(prop) };
|
|
5209
|
-
let thenVal;
|
|
5210
|
-
if (typeEquals(f.type, targetType)) {
|
|
5211
|
-
thenVal = fRead();
|
|
5212
|
-
}
|
|
5213
|
-
else if (f.type.kind === "union" &&
|
|
5214
|
-
lowerer.armTag(f.type.unionId, UNDEFINED_T) >= 0 &&
|
|
5215
|
-
typeEquals(lowerer.stripUndefinedArm(f.type), targetType)) {
|
|
5216
|
-
// Optional source field into a required target slot: present
|
|
5217
|
-
// means the source holds the record AND the field its value
|
|
5218
|
-
// arm — the spread-override completion, union-source form.
|
|
5219
|
-
const ftUndef = lowerer.armTag(f.type.unionId, UNDEFINED_T);
|
|
5220
|
-
cond = {
|
|
5221
|
-
kind: "logical",
|
|
5222
|
-
op: "&&",
|
|
5223
|
-
left: cond,
|
|
5224
|
-
right: { kind: "unionIsTag", unionId: f.type.unionId, tag: ftUndef, negated: true, value: fRead(), type: BOOL, loc: locOf(prop) },
|
|
5225
|
-
type: BOOL,
|
|
5226
|
-
loc: locOf(prop),
|
|
5227
|
-
};
|
|
5228
|
-
const ftDef = lowerer.unions.get(f.type.unionId);
|
|
5229
|
-
if (targetType.kind === "union") {
|
|
5230
|
-
const retag = lowerer.unionRetagHelper(f.type.unionId, targetType.unionId, locOf(prop));
|
|
5231
|
-
if (!retag) {
|
|
5232
|
-
lowerer.pushDiag(recordShapeMismatchDiag(lowerer.fmt(type), lowerer.fmt(recArm), locOf(prop), `spread field '${f.name}': '${lowerer.fmt(f.type)}' cannot re-tag into '${lowerer.fmt(targetType)}' behind the present-test`));
|
|
5233
|
-
throw new PoisonError();
|
|
5234
|
-
}
|
|
5235
|
-
thenVal = { kind: "call", callee: retag, args: [fRead()], type: targetType, loc: locOf(prop) };
|
|
5236
|
-
}
|
|
5237
|
-
else if (ftDef && ftDef.arms.length === 2 && lowerer.armTag(f.type.unionId, targetType) >= 0) {
|
|
5238
|
-
thenVal = { kind: "unionNarrow", unionId: f.type.unionId, tag: lowerer.armTag(f.type.unionId, targetType), value: fRead(), type: targetType, loc: locOf(prop) };
|
|
5239
|
-
}
|
|
5240
|
-
else {
|
|
5241
|
-
lowerer.pushDiag(recordShapeMismatchDiag(lowerer.fmt(type), lowerer.fmt(recArm), locOf(prop), `spread field '${f.name}': '${lowerer.fmt(f.type)}' cannot narrow into '${lowerer.fmt(targetType)}' behind the present-test`));
|
|
5242
|
-
throw new PoisonError();
|
|
5243
|
-
}
|
|
5244
|
-
}
|
|
5245
|
-
else {
|
|
5246
|
-
// The width-lift fallback (arm wrap, re-tag, nested
|
|
5247
|
-
// reshape) — the same per-field rule the slot coercion
|
|
5248
|
-
// applies. Runs AFTER the optional-completion branch: a
|
|
5249
|
-
// present-test has its own semantics a re-tag's stranded
|
|
5250
|
-
// undefined trap must not shadow.
|
|
5251
|
-
const lift = lowerer.widthLiftPlan(f.type, targetType);
|
|
5252
|
-
if (!lift) {
|
|
5253
|
-
lowerer.pushDiag(recordShapeMismatchDiag(lowerer.fmt(type), lowerer.fmt(recArm), locOf(prop), `spread field '${f.name}': '${lowerer.fmt(f.type)}' does not lift into '${lowerer.fmt(targetType)}'`));
|
|
5254
|
-
throw new PoisonError();
|
|
5255
|
-
}
|
|
5256
|
-
thenVal = lowerer.applyWidthLift(lift, fRead(), targetType, locOf(prop));
|
|
5257
|
-
}
|
|
5258
|
-
const at = fields.findIndex((x) => x.name === f.name && !x.drop);
|
|
5259
|
-
const elseVal = at >= 0 ? fields[at].value : lowerer.wrappedUndefined(targetType, locOf(prop));
|
|
5260
|
-
if (!elseVal) {
|
|
5261
|
-
lowerer.unsupported("SC1090", prop, `object spread of '${lowerer.fmt(srcType)}' sources where '${f.name}' has no earlier contributor (the absent arm leaves the required field unset — spread defaults first: { ...defaults, ...overrides })`);
|
|
5262
|
-
}
|
|
5263
|
-
const merged = { kind: "ternary", cond, then: thenVal, else_: elseVal, type: targetType, loc: locOf(prop) };
|
|
5264
|
-
if (at >= 0)
|
|
5265
|
-
fields[at] = { name: f.name, value: merged };
|
|
5266
|
-
else
|
|
5267
|
-
fields.push({ name: f.name, value: merged });
|
|
5268
|
-
}
|
|
5269
|
-
continue;
|
|
5270
|
-
}
|
|
5271
|
-
if (srcType?.kind !== "record") {
|
|
5272
|
-
lowerer.unsupported("SC1090", prop, `object spread of '${srcType ? lowerer.fmt(srcType) : lowerer.checker.typeToString(lowerer.typeOf(srcNode))}' sources (only known record shapes spread — ${NARROW_FIRST})`);
|
|
5273
|
-
}
|
|
5274
|
-
if (srcLowered && !pureReemittable(srcLowered)) {
|
|
5275
|
-
lowerer.unsupported("SC1090", prop, "object spread of computed sources (the field copies re-read the source — bind it to a const first)");
|
|
5276
|
-
}
|
|
5277
|
-
const srcShape = lowerer.shapes.get(srcType.shapeId);
|
|
5278
|
-
if (!srcShape)
|
|
5279
|
-
throw new InternalCompilerError(`lowerer bug: spread of unknown shape ${srcType.shapeId}`);
|
|
5280
|
-
fenceAccessorSpreadSource(lowerer, prop, srcShape);
|
|
5281
|
-
// Index-signature shapes carry runtime-keyed overflow entries the
|
|
5282
|
-
// field-by-field desugar cannot enumerate — fenced on either side.
|
|
5283
|
-
if (srcShape.indexValue || shape.indexValue) {
|
|
5284
|
-
lowerer.unsupported("SC1090", prop, "object spread involving index-signature shapes (overflow keys are runtime state — copy the fields you need explicitly)");
|
|
5285
|
-
}
|
|
5286
|
-
// Names a LATER contributor unconditionally defines: copying such a
|
|
5287
|
-
// source field is dead under JS last-write-wins (and spread reads
|
|
5288
|
-
// are side-effect-free), so it neither lowers nor width-checks —
|
|
5289
|
-
// the spread-then-override completion `{ ...config, stateDir }`
|
|
5290
|
-
// narrows an optional source field into a required target slot
|
|
5291
|
-
// exactly like Node does. Conditional spreads don't count (their
|
|
5292
|
-
// empty arm defines nothing) — their own collision fences hold.
|
|
5293
|
-
const laterNames = new Set();
|
|
5294
|
-
for (const later of expr.properties.slice(expr.properties.indexOf(prop) + 1)) {
|
|
5295
|
-
if (ts.isSpreadAssignment(later)) {
|
|
5296
|
-
if (conditionalSpreadOf(later.expression))
|
|
5297
|
-
continue;
|
|
5298
|
-
const lt = lowerer.mapTypeOf(lowerer.typeOf(later.expression));
|
|
5299
|
-
if (lt?.kind === "record") {
|
|
5300
|
-
for (const lf of lowerer.shapes.get(lt.shapeId)?.fields ?? [])
|
|
5301
|
-
laterNames.add(lf.name);
|
|
5302
|
-
}
|
|
5303
|
-
continue;
|
|
5304
|
-
}
|
|
5305
|
-
if (later.name &&
|
|
5306
|
-
(ts.isIdentifier(later.name) ||
|
|
5307
|
-
ts.isStringLiteral(later.name) ||
|
|
5308
|
-
ts.isNumericLiteral(later.name) ||
|
|
5309
|
-
(ts.isComputedPropertyName(later.name) && literalComputedKey(lowerer, later.name) !== null))) {
|
|
5310
|
-
laterNames.add(propNameText(lowerer, later.name));
|
|
5311
|
-
}
|
|
5312
|
-
}
|
|
5313
|
-
for (const f of srcShape.fields) {
|
|
5314
|
-
if (laterNames.has(f.name))
|
|
5315
|
-
continue;
|
|
5316
|
-
const targetType = fieldTypes.get(f.name);
|
|
5317
|
-
// A source field with NO slot on the target shape: the copy
|
|
5318
|
-
// DROPS it — a spread of a wider record into a narrower literal
|
|
5319
|
-
// is width subtyping in spread clothing, divergence 36's stance
|
|
5320
|
-
// (Node's object would keep the key); the read is pure, so
|
|
5321
|
-
// skipping evaluates nothing.
|
|
5322
|
-
if (!targetType)
|
|
5323
|
-
continue;
|
|
5324
|
-
const lift = typeEquals(f.type, targetType)
|
|
5325
|
-
? null
|
|
5326
|
-
: lowerer.widthLiftPlan(f.type, targetType);
|
|
5327
|
-
if (!typeEquals(f.type, targetType) && !lift) {
|
|
5328
|
-
// Print the SOURCE shape, not the literal's own type: the
|
|
5329
|
-
// checker's own type already has later overrides applied, so
|
|
5330
|
-
// it can render identically to the target while the spread
|
|
5331
|
-
// source (the thing that actually mismatches) differs — an
|
|
5332
|
-
// invisible difference is a diagnostics bug.
|
|
5333
|
-
lowerer.pushDiag(recordShapeMismatchDiag(lowerer.fmt(type), lowerer.fmt(srcType), locOf(prop), `spread field '${f.name}': '${lowerer.fmt(f.type)}' does not lift into '${lowerer.fmt(targetType)}'`));
|
|
5334
|
-
throw new PoisonError();
|
|
5335
|
-
}
|
|
5336
|
-
const obj = srcLowered ?? lowerer.lowerExpr(srcNode);
|
|
5337
|
-
// A record-mapped CHECKER type whose VALUE lives in the checked-dynamic tree (a
|
|
5338
|
-
// JS file-scope object-literal global): read each field from
|
|
5339
|
-
// the checked-dynamic tree (dynKeyGet) and VALIDATE it into the source shape's
|
|
5340
|
-
// field type (dynCheck) — the checked-dynamic member-read
|
|
5341
|
-
// discipline. A missing key answers the dyn undefined, exactly
|
|
5342
|
-
// the undefined-armed optional's absent case; a mismatched
|
|
5343
|
-
// runtime value throws the catchable TypeError, never a silent
|
|
5344
|
-
// wrong copy. Runtime-ADDED keys drop — width subtyping in
|
|
5345
|
-
// spread clothing, divergence 36's stance.
|
|
5346
|
-
let value;
|
|
5347
|
-
if (obj.type.kind === "dyn") {
|
|
5348
|
-
if (!canDynCheckTo(f.type, (id) => lowerer.shapes.get(id), (id) => lowerer.unions.get(id))) {
|
|
5349
|
-
lowerer.unsupported("SC1100", prop, `object spread of a checked-dynamic source whose field '${f.name}' ('${lowerer.fmt(f.type)}') cannot validate out of the checked-dynamic tree (copy the fields explicitly)`);
|
|
5350
|
-
}
|
|
5351
|
-
value = {
|
|
5352
|
-
kind: "dynCheck",
|
|
5353
|
-
value: {
|
|
5354
|
-
kind: "dynKeyGet",
|
|
5355
|
-
key: { kind: "strLit", value: f.name, type: STRING, loc: locOf(prop) },
|
|
5356
|
-
value: obj,
|
|
5357
|
-
type: DYN,
|
|
5358
|
-
loc: locOf(prop),
|
|
5359
|
-
},
|
|
5360
|
-
type: f.type,
|
|
5361
|
-
loc: locOf(prop),
|
|
5362
|
-
};
|
|
5363
|
-
}
|
|
5364
|
-
else {
|
|
5365
|
-
value = {
|
|
5366
|
-
kind: "recordGet",
|
|
5367
|
-
obj,
|
|
5368
|
-
shapeId: srcType.shapeId,
|
|
5369
|
-
field: f.name,
|
|
5370
|
-
type: f.type,
|
|
5371
|
-
loc: locOf(prop),
|
|
5372
|
-
};
|
|
5373
|
-
}
|
|
5374
|
-
// A liftable field widens into the target slot (arm wrap,
|
|
5375
|
-
// re-tag, nested reshape) — the same per-field rule the slot
|
|
5376
|
-
// coercion applies.
|
|
5377
|
-
if (lift)
|
|
5378
|
-
value = lowerer.applyWidthLift(lift, value, targetType, locOf(prop));
|
|
5379
|
-
if (conditionalNames.has(f.name)) {
|
|
5380
|
-
lowerer.unsupported("SC1090", prop, `spread of '${f.name}' over an earlier conditional spread (the desugar keeps one entry per name — restructure so each name has one contributor)`);
|
|
5381
|
-
}
|
|
5382
|
-
const at = fields.findIndex((x) => x.name === f.name);
|
|
5383
|
-
if (at >= 0)
|
|
5384
|
-
fields[at] = { name: f.name, value };
|
|
5385
|
-
else
|
|
5386
|
-
fields.push({ name: f.name, value });
|
|
5387
|
-
}
|
|
5388
|
-
continue;
|
|
5389
|
-
}
|
|
5390
|
-
if (ts.isGetAccessorDeclaration(prop) || ts.isSetAccessorDeclaration(prop)) {
|
|
5391
|
-
// JS accessor entries carry no storage (narrowed away above); TS
|
|
5392
|
-
// accessors fill the shape's reserved closure slots — the getter
|
|
5393
|
-
// body lowers as an ordinary zero-arg closure invoked per property
|
|
5394
|
-
// READ, the setter as a one-arg closure invoked per WRITE
|
|
5395
|
-
// (fieldGetExpr/fieldSetStmt dispatch on the slots). Creating the
|
|
5396
|
-
// closures is side-effect-free, so their position among the data
|
|
5397
|
-
// fields' source-order evaluation is unobservable — exactly JS,
|
|
5398
|
-
// where accessor definitions evaluate nothing.
|
|
5399
|
-
if (isJsSourceFile(expr.getSourceFile()))
|
|
5400
|
-
continue;
|
|
5401
|
-
const name = propNameText(lowerer, prop.name); // key-form-checked above
|
|
5402
|
-
const slotName = `${ts.isGetAccessorDeclaration(prop) ? "%get" : "%set"}:${name}`;
|
|
5403
|
-
const slotT = fieldTypes.get(slotName);
|
|
5404
|
-
if (!slotT || slotT.kind !== "func") {
|
|
5405
|
-
// The contextual shape stores a DATA value under this name
|
|
5406
|
-
// (`const p: { x: number } = { get x() {...} }` — tsc lets a
|
|
5407
|
-
// live accessor satisfy a data member): the slot would freeze
|
|
5408
|
-
// one getter answer where Node keeps the accessor live.
|
|
5409
|
-
if (fieldTypes.has(name)) {
|
|
5410
|
-
lowerer.unsupported("SC1090", prop, `a get/set accessor satisfying the data property '${name}' of '${lowerer.fmt(type)}' (the record slot stores a plain value — Node would keep the accessor live through this type)`);
|
|
5411
|
-
}
|
|
5412
|
-
throw shapeMismatch(prop);
|
|
5413
|
-
}
|
|
5414
|
-
const closure = lowerer.coerceInto(prop, lowerer.lowerLambda(prop), slotT);
|
|
5415
|
-
if (!typeEquals(closure.type, slotT))
|
|
5416
|
-
lowerer.badType(prop, lowerer.typeOf(prop));
|
|
5417
|
-
fields.push({ name: slotName, value: closure });
|
|
5418
|
-
continue;
|
|
5419
|
-
}
|
|
5420
|
-
const name = propNameText(lowerer, prop.name); // key-form-checked above
|
|
5421
|
-
let fieldType = fieldTypes.get(name);
|
|
5422
|
-
// An undeclared name against an index-signature shape is an OVERFLOW
|
|
5423
|
-
// entry (tsc typechecked it against the signature's value type);
|
|
5424
|
-
// against a plain shape it is the width mismatch it always was —
|
|
5425
|
-
// EXCEPT the PromiseSettledResult honest subset's dropped fields
|
|
5426
|
-
// (value/reason — SEMANTICS.md 46): those evaluate for effect in
|
|
5427
|
-
// their source-order slot and store nothing.
|
|
5428
|
-
// A GENERIC-callable member (a generic method `m<T>(x: T) {...}` or a
|
|
5429
|
-
// generic arrow/function-expression property): excluded from the
|
|
5430
|
-
// record shape (isGenericCallableMemberType — no single closure slot
|
|
5431
|
-
// can hold it), so the literal stores nothing for it. Pure
|
|
5432
|
-
// function-creating forms skip outright (creating a closure has no
|
|
5433
|
-
// side effects; calls resolve statically against this declaration);
|
|
5434
|
-
// a computed initializer would need its evaluation kept — fenced.
|
|
5435
|
-
if (!fieldType && !ts.isSpreadAssignment(prop)) {
|
|
5436
|
-
const memberSym = prop.name && lowerer.checker.getSymbolAtLocation(prop.name);
|
|
5437
|
-
const memberT = memberSym ? lowerer.checker.getTypeOfSymbol(memberSym) : undefined;
|
|
5438
|
-
if (memberT && isGenericCallableMemberType(memberT, lowerer.checker)) {
|
|
5439
|
-
const pureInit = (() => {
|
|
5440
|
-
if (ts.isMethodDeclaration(prop))
|
|
5441
|
-
return true;
|
|
5442
|
-
if (ts.isShorthandPropertyAssignment(prop))
|
|
5443
|
-
return true; // a pure read
|
|
5444
|
-
if (!ts.isPropertyAssignment(prop))
|
|
5445
|
-
return false;
|
|
5446
|
-
let init = prop.initializer;
|
|
5447
|
-
while (ts.isParenthesizedExpression(init))
|
|
5448
|
-
init = init.expression;
|
|
5449
|
-
return ts.isArrowFunction(init) || ts.isFunctionExpression(init) || ts.isIdentifier(init);
|
|
5450
|
-
})();
|
|
5451
|
-
if (!pureInit) {
|
|
5452
|
-
lowerer.unsupported("SC1090", prop, `generic-function-valued properties with computed initializers ('${name}' has no record slot — its evaluation would be dropped; bind the function to a top-level declaration instead)`);
|
|
5453
|
-
}
|
|
5454
|
-
continue;
|
|
5455
|
-
}
|
|
5456
|
-
}
|
|
5457
|
-
if (!fieldType && !shape.indexValue) {
|
|
5458
|
-
if (settledDropNames(lowerer, tsType)?.has(name)) {
|
|
5459
|
-
// Identifier and shorthand initializers are effect-free reads —
|
|
5460
|
-
// skipped outright. The caught `reason` MUST skip: lowering the
|
|
5461
|
-
// read would hit the catch-binding narrowness fence, and the
|
|
5462
|
-
// snapshot it names needs no evaluation.
|
|
5463
|
-
if (ts.isPropertyAssignment(prop) && !ts.isIdentifier(prop.initializer)) {
|
|
5464
|
-
const v = lowerer.lowerExpr(prop.initializer);
|
|
5465
|
-
// Effect-free lowerings (literals, plain reads) drop at
|
|
5466
|
-
// compile time; anything else — the awaited mapper — runs
|
|
5467
|
-
// (and may throw into the enclosing catch) with its result
|
|
5468
|
-
// released by the statement frame.
|
|
5469
|
-
if (v.kind !== "unitLit" && v.kind !== "numLit" && v.kind !== "strLit" &&
|
|
5470
|
-
v.kind !== "boolLit" && v.kind !== "varRef" && v.kind !== "closure") {
|
|
5471
|
-
fields.push({ name, value: v, drop: true });
|
|
5472
|
-
}
|
|
5473
|
-
}
|
|
5474
|
-
continue;
|
|
5475
|
-
}
|
|
5476
|
-
throw shapeMismatch(prop);
|
|
5477
|
-
}
|
|
5478
|
-
let value;
|
|
5479
|
-
let valueNode = prop;
|
|
5480
|
-
const propDiagsBefore = lowerer.diags.length;
|
|
5481
|
-
try {
|
|
5482
|
-
if (ts.isPropertyAssignment(prop)) {
|
|
5483
|
-
valueNode = prop.initializer;
|
|
5484
|
-
// ARRAY-LITERAL initializers route through the expected-type
|
|
5485
|
-
// lowering: a union field with one array-family arm builds the
|
|
5486
|
-
// literal AS that arm (lowerExprExpecting's IR-directed rule —
|
|
5487
|
-
// the option-table `default: [{ value: [] }]` shape), where the
|
|
5488
|
-
// bare lowering would take the JS dyn fallback and fence.
|
|
5489
|
-
let init = prop.initializer;
|
|
5490
|
-
while (ts.isParenthesizedExpression(init))
|
|
5491
|
-
init = init.expression;
|
|
5492
|
-
value =
|
|
5493
|
-
fenceClosureProbe(lowerer, prop.initializer, fieldType, () => lowerer.lowerExpr(prop.initializer)) ??
|
|
5494
|
-
(fieldType !== undefined && ts.isArrayLiteralExpression(init)
|
|
5495
|
-
? lowerer.lowerExprExpecting(prop.initializer, fieldType)
|
|
5496
|
-
: lowerer.lowerExpr(prop.initializer));
|
|
5497
|
-
}
|
|
5498
|
-
else if (ts.isShorthandPropertyAssignment(prop)) {
|
|
5499
|
-
value = lowerer.lowerShorthandValue(prop);
|
|
5500
|
-
}
|
|
5501
|
-
else if (ts.isMethodDeclaration(prop)) {
|
|
5502
|
-
value =
|
|
5503
|
-
fenceClosureProbe(lowerer, prop, fieldType, () => lowerer.lowerLambda(prop)) ?? lowerer.lowerLambda(prop);
|
|
5504
|
-
}
|
|
5505
|
-
else {
|
|
5506
|
-
lowerer.unsupported("SC1090", prop, `syntax '${ts.SyntaxKind[prop.kind]}'`);
|
|
5507
|
-
}
|
|
5508
|
-
}
|
|
5509
|
-
catch (err) {
|
|
5510
|
-
// A member VALUE a JS file cannot lower (a namespace object in an
|
|
5511
|
-
// export aggregate — the sharedWithCli `errors` member): the
|
|
5512
|
-
// member NARROWS AWAY like a CJS export-table accessor entry —
|
|
5513
|
-
// the diagnostics defer to the runtime-fence ledger, the shape
|
|
5514
|
-
// drops the field, and each READ of it meets its own per-site
|
|
5515
|
-
// fence. Pure member forms only (identifier/shorthand reads);
|
|
5516
|
-
// TypeScript, probe mode, and ICEs keep the poison.
|
|
5517
|
-
const pureMember = ts.isShorthandPropertyAssignment(prop) ||
|
|
5518
|
-
(ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.initializer));
|
|
5519
|
-
if (!(err instanceof PoisonError) ||
|
|
5520
|
-
!pureMember ||
|
|
5521
|
-
!isJsSourceFile(expr.getSourceFile()) ||
|
|
5522
|
-
lowerer.diagSink !== null ||
|
|
5523
|
-
lowerer.diags.length <= propDiagsBefore ||
|
|
5524
|
-
lowerer.diags.slice(propDiagsBefore).some((d) => d.code === "SC9001")) {
|
|
5525
|
-
throw err;
|
|
5526
|
-
}
|
|
5527
|
-
lowerer.runtimeFences.push(...lowerer.diags.splice(propDiagsBefore));
|
|
5528
|
-
droppedNames.add(name);
|
|
5529
|
-
continue;
|
|
5530
|
-
}
|
|
5531
|
-
if (!fieldType) {
|
|
5532
|
-
// Overflow entry: the value flows into the index signature's value
|
|
5533
|
-
// slot — dyn slots take a dyn conversion (dynFrom), typed slots the
|
|
5534
|
-
// ordinary coercion path. Later duplicates override (map semantics
|
|
5535
|
-
// are last-write-wins already; a duplicate literal key is a tsc
|
|
5536
|
-
// error anyway).
|
|
5537
|
-
const slotted = lowerer.intoIndexValueSlot(value, shape.indexValue, valueNode);
|
|
5538
|
-
fields.push({ name, value: slotted, overflow: true });
|
|
5539
|
-
continue;
|
|
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
|
-
}
|
|
5549
|
-
value = lowerer.coerceInto(valueNode, value, fieldType); // union-typed fields wrap arm values
|
|
5550
|
-
if (!typeEquals(value.type, fieldType))
|
|
5551
|
-
lowerer.badType(valueNode, lowerer.typeOf(valueNode));
|
|
5552
|
-
// An explicit property overrides a spread-copied field: JS
|
|
5553
|
-
// last-write-wins. The entry moves to the END so explicit property
|
|
5554
|
-
// values keep their source-order evaluation among themselves. A
|
|
5555
|
-
// conditional-spread entry cannot be overridden (its ternary IS the
|
|
5556
|
-
// spread's evaluation; splicing it out would drop that).
|
|
5557
|
-
if (conditionalNames.has(name)) {
|
|
5558
|
-
lowerer.unsupported("SC1090", prop, `'${name}' after a conditional spread of the same name (the desugar keeps one entry per name — restructure so each name has one contributor)`);
|
|
5559
|
-
}
|
|
5560
|
-
const at = fields.findIndex((x) => x.name === name);
|
|
5561
|
-
if (at >= 0)
|
|
5562
|
-
fields.splice(at, 1);
|
|
5563
|
-
fields.push({ name, value });
|
|
5564
|
-
}
|
|
5565
|
-
// Optional fields (undefined-armed union slots) may be omitted: the
|
|
5566
|
-
// absent field holds the interned undefined arm, exactly like writing
|
|
5567
|
-
// `a: undefined` (without exactOptionalPropertyTypes tsc treats the two
|
|
5568
|
-
// the same, and only optional fields may be omitted — tsc rejects
|
|
5569
|
-
// omission of required fields before lowering, undefined-armed or not).
|
|
5570
|
-
// A REQUIRED missing field keeps the shape-mismatch rejection (possible
|
|
5571
|
-
// through `as`: the cast smuggles a narrower literal past freshness).
|
|
5572
|
-
if (droppedNames.size > 0) {
|
|
5573
|
-
const narrowed = shape.fields.filter((f) => !droppedNames.has(f.name));
|
|
5574
|
-
const narrowedId = lowerer.shapes.intern(narrowed.map((f) => ({ name: f.name, type: f.type })), false, shape.indexValue);
|
|
5575
|
-
type = { kind: "record", shapeId: narrowedId };
|
|
5576
|
-
shape = lowerer.shapes.get(narrowedId);
|
|
5577
|
-
}
|
|
5578
|
-
if (fields.filter((f) => !f.overflow).length !== shape.fields.length) {
|
|
5579
|
-
const provided = new Set(fields.filter((f) => !f.overflow).map((f) => f.name));
|
|
5580
|
-
for (const f of shape.fields) {
|
|
5581
|
-
if (provided.has(f.name))
|
|
5582
|
-
continue;
|
|
5583
|
-
// 'unknown' fields complete with the dyn undefined — the absent
|
|
5584
|
-
// property reads as undefined in Node, and a dyn slot holds
|
|
5585
|
-
// exactly that (the options-record call shape against
|
|
5586
|
-
// `{ plugins: unknown, ... }` — a JS caller the checker admits).
|
|
5587
|
-
const absent = lowerer.wrappedUndefined(f.type, loc) ?? (f.type.kind === "dyn" ? dynUndefinedExpr(loc) : null);
|
|
5588
|
-
if (!absent)
|
|
5589
|
-
throw shapeMismatch(expr); // only optional (undefined-armed) and 'unknown' fields may be omitted
|
|
5590
|
-
fields.push({ name: f.name, value: absent });
|
|
5591
|
-
}
|
|
5592
|
-
}
|
|
5593
|
-
return { kind: "recordLit", fields, type, loc };
|
|
5594
|
-
}
|
|
5595
|
-
/** Parses the conditional-spread idiom `...(c ? { k: v, ... } : {})`
|
|
5596
|
-
* (either orientation). Returns the condition, the non-empty arm's
|
|
5597
|
-
* properties, and which arm carries them; "unsupported" for conditional
|
|
5598
|
-
* sources OUTSIDE the idiom (both arms non-empty, computed/method
|
|
5599
|
-
* members); null when the spread source isn't a conditional at all.
|
|
5600
|
-
* Callers slice their own honest subset (the static record path takes
|
|
5601
|
-
* exactly one property; the island literal takes any number; spawn's
|
|
5602
|
-
* options walk takes a `detached` boolean literal). */
|
|
5603
|
-
export function conditionalSpreadOf(expr) {
|
|
5604
|
-
let e = expr;
|
|
5605
|
-
while (ts.isParenthesizedExpression(e))
|
|
5606
|
-
e = e.expression;
|
|
5607
|
-
if (!ts.isConditionalExpression(e))
|
|
5608
|
-
return null;
|
|
5609
|
-
const unwrap = (a) => {
|
|
5610
|
-
let x = a;
|
|
5611
|
-
while (ts.isParenthesizedExpression(x))
|
|
5612
|
-
x = x.expression;
|
|
5613
|
-
return x;
|
|
5614
|
-
};
|
|
5615
|
-
const whenTrue = unwrap(e.whenTrue);
|
|
5616
|
-
const whenFalse = unwrap(e.whenFalse);
|
|
5617
|
-
if (!ts.isObjectLiteralExpression(whenTrue) || !ts.isObjectLiteralExpression(whenFalse)) {
|
|
5618
|
-
return "unsupported";
|
|
5619
|
-
}
|
|
5620
|
-
const trueCarries = whenFalse.properties.length === 0 && whenTrue.properties.length > 0;
|
|
5621
|
-
const falseCarries = whenTrue.properties.length === 0 && whenFalse.properties.length > 0;
|
|
5622
|
-
if (!trueCarries && !falseCarries)
|
|
5623
|
-
return "unsupported"; // both empty is tsc-unreachable; both non-empty has no single desugar
|
|
5624
|
-
const carrier = trueCarries ? whenTrue : whenFalse;
|
|
5625
|
-
const props = [];
|
|
5626
|
-
for (const p of carrier.properties) {
|
|
5627
|
-
if ((ts.isPropertyAssignment(p) || ts.isShorthandPropertyAssignment(p)) &&
|
|
5628
|
-
p.name && (ts.isIdentifier(p.name) || ts.isStringLiteral(p.name))) {
|
|
5629
|
-
props.push(p);
|
|
5630
|
-
}
|
|
5631
|
-
else {
|
|
5632
|
-
return "unsupported";
|
|
5633
|
-
}
|
|
5634
|
-
}
|
|
5635
|
-
return { cond: e.condition, props, whenTrue: trueCarries };
|
|
5636
|
-
}
|
|
5637
|
-
/** `{ ...idx, ...idx2 }` typed a DECLARED shape (no index signature) — the
|
|
5638
|
-
* defaults-merge idiom over runtime-keyed sources (`{ ...fromEntries(a),
|
|
5639
|
-
* ...fromEntries(b) }` typed AppConfig). Lowers to ONE interned helper
|
|
5640
|
-
* call: sources evaluate once each as arguments (source order — computed
|
|
5641
|
-
* sources included, JS's evaluate-once), the result starts all-undefined
|
|
5642
|
-
* (every target field must be optional — the runtime keys decide
|
|
5643
|
-
* presence), and each source's keys apply in JS own-key order with a
|
|
5644
|
-
* per-key dispatch onto the declared fields: a matching key writes the
|
|
5645
|
-
* field — identity when the source's value slot IS the field type, a
|
|
5646
|
-
* validated extraction otherwise (a dyn slot dynChecks; a union slot
|
|
5647
|
-
* re-tags arm-by-arm, and a value outside the field's arms throws the
|
|
5648
|
-
* catchable TypeError — divergence 34's keyed-write stance, where Node's
|
|
5649
|
-
* untyped copy would store the lie) — and a key naming NO declared field
|
|
5650
|
-
* is DROPPED (the shape cannot represent it; Node keeps it invisibly —
|
|
5651
|
-
* divergence 68). Later contributors overwrite earlier ones
|
|
5652
|
-
* (last-write-wins). Sources must be PURE index-signature records. */
|
|
5653
|
-
function lowerDeclaredSpreadMerge(lowerer, expr, type, shape, loc) {
|
|
5654
|
-
const srcs = [];
|
|
5655
|
-
for (const prop of expr.properties) {
|
|
5656
|
-
const spread = prop; // caller-checked: all spreads
|
|
5657
|
-
const value = lowerer.lowerExpr(spread.expression);
|
|
5658
|
-
const srcShape = value.type.kind === "record" ? lowerer.shapes.get(value.type.shapeId) : undefined;
|
|
5659
|
-
if (value.type.kind !== "record" || !srcShape?.indexValue || srcShape.tuple || srcShape.fields.length > 0) {
|
|
5660
|
-
lowerer.unsupported("SC1090", prop, `object spread of '${lowerer.fmt(value.type)}' into '${lowerer.fmt(type)}' (only PURE index-signature records — Object.fromEntries results, Record<string, T> values — spread into a declared shape)`);
|
|
5661
|
-
}
|
|
5662
|
-
srcs.push({ value, shapeId: value.type.shapeId, iv: srcShape.indexValue });
|
|
5663
|
-
}
|
|
5664
|
-
// Every target field must be optional: absent keys leave the undefined
|
|
5665
|
-
// arm, exactly the unset-optional representation.
|
|
5666
|
-
for (const f of shape.fields) {
|
|
5667
|
-
if (f.type.kind !== "union" || lowerer.armTag(f.type.unionId, UNDEFINED_T) < 0) {
|
|
5668
|
-
lowerer.unsupported("SC1090", expr, `object spread of runtime-keyed sources onto the required field '${f.name}' (the keys decide presence at runtime — declare the field optional or spell it explicitly)`);
|
|
5669
|
-
}
|
|
5670
|
-
}
|
|
5671
|
-
// Per (source value slot → field) conversion, checked up front so the
|
|
5672
|
-
// fence fires at the literal, not inside the interned helper.
|
|
5673
|
-
const conv = (iv, f, v) => {
|
|
5674
|
-
if (typeEquals(iv, f.type))
|
|
5675
|
-
return { value: v };
|
|
5676
|
-
if (iv.kind === "dyn") {
|
|
5677
|
-
return { value: { kind: "dynCheck", value: v, type: f.type, loc } };
|
|
5678
|
-
}
|
|
5679
|
-
if (iv.kind === "union" && f.type.kind === "union") {
|
|
5680
|
-
const fUnion = f.type;
|
|
5681
|
-
const def = lowerer.unions.get(iv.unionId);
|
|
5682
|
-
const fDef = lowerer.unions.get(fUnion.unionId);
|
|
5683
|
-
if (def && fDef && def.arms.some((a) => lowerer.armTag(fUnion.unionId, a) >= 0)) {
|
|
5684
|
-
// Arm-by-arm validated re-tag, inline in the helper: matching
|
|
5685
|
-
// arms map by identity (unit arms re-wrap, value arms narrow and
|
|
5686
|
-
// wrap); anything else throws the catchable TypeError.
|
|
5687
|
-
return {
|
|
5688
|
-
stmts: (write) => {
|
|
5689
|
-
const chain = (i) => {
|
|
5690
|
-
if (i >= def.arms.length) {
|
|
5691
|
-
return [{
|
|
5692
|
-
kind: "throw",
|
|
5693
|
-
value: {
|
|
5694
|
-
kind: "libCall",
|
|
5695
|
-
fn: "error.new",
|
|
5696
|
-
args: [{ kind: "strLit", value: `expected ${lowerer.fmt(fUnion)} at $.${f.name}`, type: STRING, loc }],
|
|
5697
|
-
type: { kind: "object", className: "%TypeError" },
|
|
5698
|
-
loc,
|
|
5699
|
-
},
|
|
5700
|
-
loc,
|
|
5701
|
-
}];
|
|
5702
|
-
}
|
|
5703
|
-
const arm = def.arms[i];
|
|
5704
|
-
const toTag = lowerer.armTag(fUnion.unionId, arm);
|
|
5705
|
-
if (toTag < 0)
|
|
5706
|
-
return chain(i + 1);
|
|
5707
|
-
const extracted = isUnitType(arm)
|
|
5708
|
-
? { kind: "unionWrap", unionId: fUnion.unionId, tag: toTag, value: { kind: "unitLit", unit: arm.kind === "undefinedT" ? "undefined" : "null", type: arm, loc }, type: fUnion, loc }
|
|
5709
|
-
: { kind: "unionWrap", unionId: fUnion.unionId, tag: toTag, value: { kind: "unionNarrow", unionId: iv.unionId, tag: i, value: v, type: arm, loc }, type: fUnion, loc };
|
|
5710
|
-
return [{
|
|
5711
|
-
kind: "if",
|
|
5712
|
-
cond: { kind: "unionIsTag", unionId: iv.unionId, tag: i, negated: false, value: v, type: BOOL, loc },
|
|
5713
|
-
then: [write(extracted)],
|
|
5714
|
-
else_: chain(i + 1),
|
|
5715
|
-
loc,
|
|
5716
|
-
}];
|
|
5717
|
-
};
|
|
5718
|
-
return chain(0);
|
|
5719
|
-
},
|
|
5720
|
-
};
|
|
5721
|
-
}
|
|
5722
|
-
}
|
|
5723
|
-
lowerer.unsupported("SC1090", expr, `object spread into '${lowerer.fmt(type)}' where the source's '${lowerer.fmt(iv)}' values cannot reach the '${lowerer.fmt(f.type)}' field '${f.name}' (the value slot must be the field type, 'unknown', or a union covering the field's arms)`);
|
|
5724
|
-
};
|
|
5725
|
-
const key = `declmerge:${type.shapeId}:${srcs.map((s) => s.shapeId).join(",")}`;
|
|
5726
|
-
let helper = lowerer.widthHelpers.get(key);
|
|
5727
|
-
if (!helper) {
|
|
5728
|
-
helper = `%rec.declmerge.${lowerer.widthHelpers.size}`;
|
|
5729
|
-
const outRef = varRef("out.0", type, loc);
|
|
5730
|
-
const ksT = arrayOf(STRING);
|
|
5731
|
-
const locals = [{ id: "out.0", name: "out", type, mutable: false }];
|
|
5732
|
-
const params = srcs.map((s, j) => {
|
|
5733
|
-
locals.push({ id: `s${j}.0`, name: `s${j}`, type: s.value.type, mutable: true });
|
|
5734
|
-
return { localId: `s${j}.0`, name: `s${j}`, type: s.value.type };
|
|
5735
|
-
});
|
|
5736
|
-
const body = [
|
|
5737
|
-
{
|
|
5738
|
-
kind: "varDecl",
|
|
5739
|
-
localId: "out.0",
|
|
5740
|
-
init: {
|
|
5741
|
-
kind: "recordLit",
|
|
5742
|
-
fields: shape.fields.map((f) => ({ name: f.name, value: lowerer.wrappedUndefined(f.type, loc) })),
|
|
5743
|
-
type,
|
|
5744
|
-
loc,
|
|
5745
|
-
},
|
|
5746
|
-
loc,
|
|
5747
|
-
},
|
|
5748
|
-
];
|
|
5749
|
-
srcs.forEach((s, j) => {
|
|
5750
|
-
const sRef = varRef(`s${j}.0`, s.value.type, loc);
|
|
5751
|
-
const kRef = varRef(`k${j}.0`, STRING, loc);
|
|
5752
|
-
const vRef = varRef(`v${j}.0`, s.iv, loc);
|
|
5753
|
-
locals.push({ id: `ks${j}.0`, name: `ks${j}`, type: ksT, mutable: false }, { id: `i${j}.0`, name: `i${j}`, type: F64, mutable: true }, { id: `k${j}.0`, name: `k${j}`, type: STRING, mutable: false }, { id: `v${j}.0`, name: `v${j}`, type: s.iv, mutable: false });
|
|
5754
|
-
// Per-key dispatch: if (k === "a") { write a } else if ... else drop.
|
|
5755
|
-
const dispatch = shape.fields.reduceRight((rest, f) => {
|
|
5756
|
-
const write = (value) => ({ kind: "recordSet", obj: outRef, shapeId: type.shapeId, field: f.name, value, loc });
|
|
5757
|
-
const c = conv(s.iv, f, vRef);
|
|
5758
|
-
const thenBody = "value" in c ? [write(c.value)] : c.stmts(write);
|
|
5759
|
-
return [{
|
|
5760
|
-
kind: "if",
|
|
5761
|
-
cond: { kind: "strEq", negated: false, left: kRef, right: { kind: "strLit", value: f.name, type: STRING, loc }, type: BOOL, loc },
|
|
5762
|
-
then: thenBody,
|
|
5763
|
-
else_: rest.length > 0 ? rest : null,
|
|
5764
|
-
loc,
|
|
5765
|
-
}];
|
|
5766
|
-
}, []);
|
|
5767
|
-
body.push({ kind: "varDecl", localId: `ks${j}.0`, init: { kind: "recordOvfKeys", obj: sRef, shapeId: s.shapeId, type: ksT, loc }, loc }, {
|
|
5768
|
-
kind: "for",
|
|
5769
|
-
init: { kind: "varDecl", localId: `i${j}.0`, init: numLit(0, loc), loc },
|
|
5770
|
-
cond: { kind: "bin", op: "<", left: varRef(`i${j}.0`, F64, loc), right: { kind: "arrIntrinsic", method: "length", receiver: varRef(`ks${j}.0`, ksT, loc), args: [], type: F64, loc }, type: BOOL, loc },
|
|
5771
|
-
update: { kind: "assign", localId: `i${j}.0`, value: { kind: "bin", op: "+", left: varRef(`i${j}.0`, F64, loc), right: numLit(1, loc), type: F64, loc }, loc },
|
|
5772
|
-
body: [
|
|
5773
|
-
{ kind: "varDecl", localId: `k${j}.0`, init: { kind: "arrayGet", arr: varRef(`ks${j}.0`, ksT, loc), index: varRef(`i${j}.0`, F64, loc), type: STRING, loc }, loc },
|
|
5774
|
-
{ kind: "varDecl", localId: `v${j}.0`, init: { kind: "recordKeyGet", obj: sRef, shapeId: s.shapeId, key: kRef, overflowOnly: true, type: s.iv, loc }, loc },
|
|
5775
|
-
...dispatch,
|
|
5776
|
-
],
|
|
5777
|
-
loc,
|
|
5778
|
-
});
|
|
5779
|
-
});
|
|
5780
|
-
body.push({ kind: "return", value: outRef, loc });
|
|
5781
|
-
lowerer.liftedFns.push({
|
|
5782
|
-
name: helper,
|
|
5783
|
-
params,
|
|
5784
|
-
returnType: type,
|
|
5785
|
-
locals,
|
|
5786
|
-
body,
|
|
5787
|
-
loc,
|
|
5788
|
-
});
|
|
5789
|
-
// Registered only after a fence-free build: a conv() fence mid-build
|
|
5790
|
-
// must not leave a phantom helper behind for the next literal.
|
|
5791
|
-
lowerer.widthHelpers.set(key, helper);
|
|
5792
|
-
}
|
|
5793
|
-
return { kind: "call", callee: helper, args: srcs.map((s) => s.value), type, loc };
|
|
5794
|
-
}
|
|
5795
|
-
/** The value of a shorthand property (`{ x }`): the binding `x` refers to.
|
|
5796
|
-
* The property name's own symbol is the PROPERTY, so resolution goes
|
|
5797
|
-
* through getShorthandAssignmentValueSymbol. */
|
|
5798
|
-
export function lowerShorthandValue(lowerer, prop) {
|
|
5799
|
-
// 7 types the shorthand's name as PropertyName; it is always an
|
|
5800
|
-
// Identifier (the grammar allows nothing else in shorthand position).
|
|
5801
|
-
const propName = prop.name;
|
|
5802
|
-
const loc = locOf(propName);
|
|
5803
|
-
const symbol = lowerer.checker.getShorthandAssignmentValueSymbol(prop);
|
|
5804
|
-
if (symbol) {
|
|
5805
|
-
if (lowerer.ctx.selfSymbol === symbol) {
|
|
5806
|
-
return { kind: "selfRef", type: lowerer.ctx.selfType, loc };
|
|
5807
|
-
}
|
|
5808
|
-
const local = lowerer.resolveKey(symbol, propName);
|
|
5809
|
-
if (local) {
|
|
5810
|
-
return lowerer.maybeNarrow({ kind: "varRef", localId: local.id, type: local.type, loc }, propName);
|
|
5811
|
-
}
|
|
5812
|
-
const resolved = symbol.flags & ts.SymbolFlags.Alias ? lowerer.checker.getAliasedSymbol(symbol) : symbol;
|
|
5813
|
-
lowerer.flushDeferred(resolved);
|
|
5814
|
-
const g = lowerer.globalsBySymbol.get(resolved);
|
|
5815
|
-
if (g) {
|
|
5816
|
-
return lowerer.maybeNarrow({ kind: "varRef", localId: g.id, type: g.type, loc }, propName);
|
|
5817
|
-
}
|
|
5818
|
-
const sig = lowerer.fnSigsBySymbol.get(resolved);
|
|
5819
|
-
const decl = lowerer.checker.declarationsOf(resolved)[0];
|
|
5820
|
-
if (sig && decl && ts.isFunctionDeclaration(decl) &&
|
|
5821
|
-
(ts.isSourceFile(decl.parent) || lowerer.nsBlocks.get(decl.parent) === "flattened")) {
|
|
5822
|
-
lowerer.noteEdge(sig.name);
|
|
5823
|
-
const funcType = {
|
|
5824
|
-
kind: "func",
|
|
5825
|
-
params: sig.params.filter((p) => p.mode !== "dynRest").map((p) => p.type),
|
|
5826
|
-
ret: sig.returnType,
|
|
5827
|
-
...(sig.params.some((p) => p.mode === "dynRest") ? { rest: true } : {}),
|
|
5828
|
-
};
|
|
5829
|
-
lowerer.requireExactArityValue(prop, propName, sig.params, funcType);
|
|
5830
|
-
return { kind: "closure", fnName: sig.name, captures: [], type: funcType, loc };
|
|
5831
|
-
}
|
|
5832
|
-
if (lowerer.genericFnsBySymbol.has(resolved)) {
|
|
5833
|
-
lowerer.unsupported("SC1090", prop, `generic functions as values (call '${propName.text}' directly)`);
|
|
5834
|
-
}
|
|
5835
|
-
}
|
|
5836
|
-
lowerer.rejectUnresolvedSymbol(symbol ? (symbol.flags & ts.SymbolFlags.Alias ? lowerer.checker.getAliasedSymbol(symbol) : symbol) : null, propName.text, prop, `the reference to '${propName.text}' (a binding form with no lowering)`);
|
|
5837
|
-
}
|
|
5838
|
-
/** Rejects any `this` inside an object-literal method body — including in
|
|
5839
|
-
* nested arrows, which inherit the method's `this` (nested function
|
|
5840
|
-
* expressions reset it, but their bare `this` is already a tsc error
|
|
5841
|
-
* under noImplicitThis, so over-rejecting them here changes nothing). */
|
|
5842
|
-
export function rejectThisInObjectMethod(lowerer, node) {
|
|
5843
|
-
if (node.kind === ts.SyntaxKind.ThisKeyword) {
|
|
5844
|
-
lowerer.unsupported("SC1090", node, "references to 'this' in object literal methods");
|
|
5845
|
-
}
|
|
5846
|
-
ts.forEachChild(node, (child) => lowerer.rejectThisInObjectMethod(child));
|
|
5847
|
-
}
|
|
5848
|
-
/** The accessor twin of rejectThisInObjectMethod: a get/set accessor body
|
|
5849
|
-
* referencing `this` (`get x() { return this._x }`). The accessor lowers
|
|
5850
|
-
* as a closure stored IN the record — passing the record as a receiver
|
|
5851
|
-
* would capture the value under construction (an RC cycle), and the
|
|
5852
|
-
* generic lexical-this walk would silently bind an ENCLOSING method's
|
|
5853
|
-
* `this`; the fence names the fix (capture a binding instead). */
|
|
5854
|
-
function rejectThisInObjectAccessor(lowerer, node) {
|
|
5855
|
-
if (node.kind === ts.SyntaxKind.ThisKeyword) {
|
|
5856
|
-
lowerer.unsupported("SC1090", node, "references to 'this' in object literal get/set accessors (the accessor lowers as a captured closure with no receiver — read a captured binding instead)");
|
|
5857
|
-
}
|
|
5858
|
-
ts.forEachChild(node, (child) => rejectThisInObjectAccessor(lowerer, child));
|
|
5859
|
-
}
|
|
5860
3800
|
/** `a[i]` reads. Only f64 indices into array receivers are modeled; JS
|
|
5861
3801
|
* string-key element access (`a["length"]`) and string indexing (`s[0]`
|
|
5862
3802
|
* typechecks against the lib's index signature; use .charAt) stay out. */
|
|
@@ -5868,7 +3808,7 @@ export function lowerElementAccess(lowerer, expr) {
|
|
|
5868
3808
|
}
|
|
5869
3809
|
// `a?.b[i]` — the tail of a chain whose token sits deeper: the whole
|
|
5870
3810
|
// tail short-circuits with the guard.
|
|
5871
|
-
if (!expr.questionDotToken &&
|
|
3811
|
+
if (!expr.questionDotToken && isOptionalChainTail(lowerer, expr)) {
|
|
5872
3812
|
return lowerer.lowerOptionalChain(expr);
|
|
5873
3813
|
}
|
|
5874
3814
|
// Enum accesses in element clothing — `E["A"]` forward reads and
|
|
@@ -5879,6 +3819,9 @@ export function lowerElementAccess(lowerer, expr) {
|
|
|
5879
3819
|
if (en)
|
|
5880
3820
|
return en;
|
|
5881
3821
|
}
|
|
3822
|
+
const cachedModule = lowerRequireCacheElement(lowerer, expr);
|
|
3823
|
+
if (cachedModule)
|
|
3824
|
+
return cachedModule;
|
|
5882
3825
|
// Native Web handles use the same supported surface for bracket and dot
|
|
5883
3826
|
// spellings. Fence a statically-known unsupported key before the generic
|
|
5884
3827
|
// checked-dynamic element-read path can turn it into a runtime missing
|
|
@@ -5975,7 +3918,7 @@ export function lowerElementAccess(lowerer, expr) {
|
|
|
5975
3918
|
? { kind: "toString", operand: rawKey, type: STRING, loc: rawKey.loc }
|
|
5976
3919
|
: null;
|
|
5977
3920
|
if (key) {
|
|
5978
|
-
const opt =
|
|
3921
|
+
const opt = hasOptionalChainGuard(expr.expression);
|
|
5979
3922
|
return lowerer.maybeNarrow({ kind: "dynKeyGet", key, ...(opt ? { optional: true } : {}), value: recv, type: DYN, loc: locOf(expr) }, expr);
|
|
5980
3923
|
}
|
|
5981
3924
|
}
|
|
@@ -6089,7 +4032,7 @@ export function lowerElementAccess(lowerer, expr) {
|
|
|
6089
4032
|
? { kind: "toString", operand: rawKey, type: STRING, loc: rawKey.loc }
|
|
6090
4033
|
: null;
|
|
6091
4034
|
if (key) {
|
|
6092
|
-
const opt =
|
|
4035
|
+
const opt = hasOptionalChainGuard(expr.expression);
|
|
6093
4036
|
return lowerer.maybeNarrow({ kind: "dynKeyGet", key, ...(opt ? { optional: true } : {}), value: obj, type: DYN, loc: locOf(expr) }, expr);
|
|
6094
4037
|
}
|
|
6095
4038
|
}
|
|
@@ -6111,6 +4054,17 @@ export function lowerElementAccess(lowerer, expr) {
|
|
|
6111
4054
|
const value = lowerer.lowerExpr(expr.expression);
|
|
6112
4055
|
if (value.type.kind === "union") {
|
|
6113
4056
|
const key = lowerer.lowerExpr(expr.argumentExpression);
|
|
4057
|
+
if (key.type.kind === "f64") {
|
|
4058
|
+
const def = lowerer.unions.get(value.type.unionId);
|
|
4059
|
+
const presentArms = def?.arms.filter((arm) => !isUnitType(arm)) ?? [];
|
|
4060
|
+
if (presentArms.length === 1 && presentArms[0]?.kind === "string") {
|
|
4061
|
+
const property = runtimeOptionalElementKey(expr.argumentExpression);
|
|
4062
|
+
const receiver = property === null ? null : lowerer.runtimeOptionalPropertyReceiver(expr.expression, value, STRING, property);
|
|
4063
|
+
if (receiver !== null) {
|
|
4064
|
+
return { kind: "strIntrinsic", method: "charAt", receiver, args: [key], type: STRING, loc: locOf(expr) };
|
|
4065
|
+
}
|
|
4066
|
+
}
|
|
4067
|
+
}
|
|
6114
4068
|
if (key.type.kind === "string") {
|
|
6115
4069
|
const lit = ts.isStringLiteral(expr.argumentExpression)
|
|
6116
4070
|
? expr.argumentExpression.text
|
|
@@ -6392,6 +4346,19 @@ export function lowerRecordKeyRead(lowerer, expr, shapeId, shape, includeUndefin
|
|
|
6392
4346
|
if (key.type.kind !== "string") {
|
|
6393
4347
|
lowerer.unsupported("SC1090", keyNode, "indexing records with non-string or non-number keys");
|
|
6394
4348
|
}
|
|
4349
|
+
if (shape.fields.length === 0 && shape.indexValue === undefined) {
|
|
4350
|
+
const result = dynUndefinedExpr(loc);
|
|
4351
|
+
return {
|
|
4352
|
+
kind: "seqExpr",
|
|
4353
|
+
stmts: [
|
|
4354
|
+
{ kind: "exprStmt", expr: obj, loc },
|
|
4355
|
+
{ kind: "exprStmt", expr: key, loc },
|
|
4356
|
+
],
|
|
4357
|
+
result,
|
|
4358
|
+
type: DYN,
|
|
4359
|
+
loc,
|
|
4360
|
+
};
|
|
4361
|
+
}
|
|
6395
4362
|
// The DECLARED result type of the access — the index signature's value
|
|
6396
4363
|
// type (armed with undefined under noUncheckedIndexedAccess), or the
|
|
6397
4364
|
// declared fields' one common type on signature-free shapes (tsc's
|
|
@@ -6483,6 +4450,9 @@ export function lowerAbsenceProbe(lowerer, node) {
|
|
|
6483
4450
|
}
|
|
6484
4451
|
if (!ts.isElementAccessExpression(expr))
|
|
6485
4452
|
return null;
|
|
4453
|
+
const cachedModule = lowerRequireCacheElement(lowerer, expr);
|
|
4454
|
+
if (cachedModule)
|
|
4455
|
+
return cachedModule;
|
|
6486
4456
|
const header = lowerer.lowerHttpHeadersElement(expr);
|
|
6487
4457
|
if (header)
|
|
6488
4458
|
return header;
|
|
@@ -6580,6 +4550,7 @@ function tupleLiteralIndex(node) {
|
|
|
6580
4550
|
* assignment, produce no value in our subset). */
|
|
6581
4551
|
export function lowerElementWrite(lowerer, expr) {
|
|
6582
4552
|
const target = expr.left;
|
|
4553
|
+
fenceNodeModuleMutation(lowerer, target, "assignment");
|
|
6583
4554
|
// `process.env[key] = v` — the computed twin of the dotted env write:
|
|
6584
4555
|
// setenv(3), string keys and string values only.
|
|
6585
4556
|
if (lowerer.isProcessEnv(target.expression) && !target.questionDotToken) {
|
|
@@ -6846,7 +4817,7 @@ export function lowerElementWrite(lowerer, expr) {
|
|
|
6846
4817
|
// probes the receiver's own lowered world: a dyn value takes the same
|
|
6847
4818
|
// write, anything else falls through to the fences.
|
|
6848
4819
|
if (receiverIr?.kind === "dyn" || receiverIr === null) {
|
|
6849
|
-
const obj = receiverIr !== null ? lowerer.lowerExpr(target.expression) :
|
|
4820
|
+
const obj = receiverIr !== null ? lowerer.lowerExpr(target.expression) : tryLowerExpression(lowerer, target.expression);
|
|
6850
4821
|
if (obj !== null && obj.type.kind === "dyn") {
|
|
6851
4822
|
const loc = locOf(expr);
|
|
6852
4823
|
const litKey = recordKeyLiteralText(target.argumentExpression);
|
|
@@ -6904,6 +4875,15 @@ export function lowerElementWrite(lowerer, expr) {
|
|
|
6904
4875
|
export function ensureString(lowerer, e, node) {
|
|
6905
4876
|
if (e.type.kind === "string")
|
|
6906
4877
|
return e;
|
|
4878
|
+
if (e.type.kind === "bigint") {
|
|
4879
|
+
return {
|
|
4880
|
+
kind: "libCall",
|
|
4881
|
+
fn: "bigint.toString",
|
|
4882
|
+
args: [e, { kind: "numLit", value: 10, type: F64, loc: e.loc }],
|
|
4883
|
+
type: STRING,
|
|
4884
|
+
loc: e.loc,
|
|
4885
|
+
};
|
|
4886
|
+
}
|
|
6907
4887
|
if (e.type.kind === "dyn") {
|
|
6908
4888
|
// String(u) / `${u}`: a runtime dispatch over the dyn kind — Node's
|
|
6909
4889
|
// String() exactly (undefined/null texts, JS number formatting,
|
|
@@ -6933,7 +4913,7 @@ export function ensureString(lowerer, e, node) {
|
|
|
6933
4913
|
// JS would print "[object Object]" and friends — narrow first.
|
|
6934
4914
|
const def = lowerer.unions.get(e.type.unionId);
|
|
6935
4915
|
const stringable = def?.arms.every((a) => a.kind === "undefinedT" || a.kind === "nullT" ||
|
|
6936
|
-
a.kind === "string" || a.kind === "f64" || a.kind === "bool");
|
|
4916
|
+
a.kind === "string" || a.kind === "f64" || a.kind === "bigint" || a.kind === "bool");
|
|
6937
4917
|
if (stringable) {
|
|
6938
4918
|
return { kind: "toString", operand: e, type: STRING, loc: e.loc };
|
|
6939
4919
|
}
|
|
@@ -7164,6 +5144,12 @@ export function lowerAsExpression(lowerer, expr) {
|
|
|
7164
5144
|
if (target.kind === "bytes" && target.elem === "u8") {
|
|
7165
5145
|
return { kind: "dynCheck", value: inner, type: target, loc: locOf(expr) };
|
|
7166
5146
|
}
|
|
5147
|
+
// Class instances round-trip only through compiler-owned typed
|
|
5148
|
+
// references. Plain JSON objects fail the runtime class-brand check;
|
|
5149
|
+
// exact capsules unwrap the original object by identity.
|
|
5150
|
+
if (isDynTypedRefType(target)) {
|
|
5151
|
+
return { kind: "dynCheck", value: inner, type: target, loc: locOf(expr) };
|
|
5152
|
+
}
|
|
7167
5153
|
// ADAPTABLE function targets (`u as (x: number) => number` — the
|
|
7168
5154
|
// checked-dynamic function boundary): kind check, then exact unwrap
|
|
7169
5155
|
// or the per-target adapter shim. Non-adaptable signatures keep the
|
|
@@ -7206,6 +5192,9 @@ export function lowerPrefixUnary(lowerer, expr) {
|
|
|
7206
5192
|
if (raw.type.kind === "jsval") {
|
|
7207
5193
|
return { kind: "jsOp", op: "neg", args: [raw], type: JSVAL, loc };
|
|
7208
5194
|
}
|
|
5195
|
+
if (raw.type.kind === "bigint") {
|
|
5196
|
+
return { kind: "libCall", fn: "bigint.neg", args: [raw], type: BIGINT_T, loc };
|
|
5197
|
+
}
|
|
7209
5198
|
const operand = lowerOptionalNumber(lowerer, raw, loc, expr.operand);
|
|
7210
5199
|
if (operand.type.kind !== "f64")
|
|
7211
5200
|
lowerer.unsupported("SC1043", expr);
|
|
@@ -7236,7 +5225,11 @@ export function lowerPrefixUnary(lowerer, expr) {
|
|
|
7236
5225
|
}
|
|
7237
5226
|
case ts.SyntaxKind.TildeToken: {
|
|
7238
5227
|
// `~x`: ToInt32, complement, back to f64 (JS-exact, incl. NaN → -1).
|
|
7239
|
-
const
|
|
5228
|
+
const raw = lowerer.lowerExpr(expr.operand);
|
|
5229
|
+
if (raw.type.kind === "bigint") {
|
|
5230
|
+
return { kind: "libCall", fn: "bigint.not", args: [raw], type: BIGINT_T, loc };
|
|
5231
|
+
}
|
|
5232
|
+
const operand = lowerOptionalNumber(lowerer, raw, loc, expr.operand);
|
|
7240
5233
|
if (operand.type.kind !== "f64")
|
|
7241
5234
|
lowerer.unsupported("SC1043", expr);
|
|
7242
5235
|
return { kind: "unary", op: "~", operand, type: F64, loc };
|
|
@@ -7328,10 +5321,46 @@ function seqExprSafeStmt(s) {
|
|
|
7328
5321
|
return false;
|
|
7329
5322
|
}
|
|
7330
5323
|
}
|
|
5324
|
+
/** Run one supported `any` binary operation in the island. Checked-dynamic
|
|
5325
|
+
* operands marshal in only after lowerBinary's identity-preserving native
|
|
5326
|
+
* cases have had their chance (notably dyn strict equality). */
|
|
5327
|
+
function lowerAnyBinaryInIsland(lowerer, expr, left, right, loc) {
|
|
5328
|
+
const op = expr.operatorToken.kind;
|
|
5329
|
+
const JS_BIN = {
|
|
5330
|
+
[ts.SyntaxKind.PlusToken]: "add",
|
|
5331
|
+
[ts.SyntaxKind.MinusToken]: "sub",
|
|
5332
|
+
[ts.SyntaxKind.AsteriskToken]: "mul",
|
|
5333
|
+
[ts.SyntaxKind.SlashToken]: "div",
|
|
5334
|
+
[ts.SyntaxKind.PercentToken]: "mod",
|
|
5335
|
+
[ts.SyntaxKind.AsteriskAsteriskToken]: "pow",
|
|
5336
|
+
[ts.SyntaxKind.LessThanToken]: "lt",
|
|
5337
|
+
[ts.SyntaxKind.LessThanEqualsToken]: "le",
|
|
5338
|
+
[ts.SyntaxKind.GreaterThanToken]: "gt",
|
|
5339
|
+
[ts.SyntaxKind.GreaterThanEqualsToken]: "ge",
|
|
5340
|
+
[ts.SyntaxKind.EqualsEqualsEqualsToken]: "eq",
|
|
5341
|
+
[ts.SyntaxKind.ExclamationEqualsEqualsToken]: "neq",
|
|
5342
|
+
};
|
|
5343
|
+
const jop = JS_BIN[op];
|
|
5344
|
+
if (jop === undefined) {
|
|
5345
|
+
lowerer.unsupported("SC1090", expr, `operator '${ts.tokenToString(op) ?? ts.SyntaxKind[op]}' on 'any' values`);
|
|
5346
|
+
}
|
|
5347
|
+
const type = jsOpResultKind(jop) === "bool" ? BOOL : JSVAL;
|
|
5348
|
+
return {
|
|
5349
|
+
kind: "jsOp", op: jop,
|
|
5350
|
+
args: [lowerer.jsvalIn(left, expr.left), lowerer.jsvalIn(right, expr.right)],
|
|
5351
|
+
type, loc,
|
|
5352
|
+
};
|
|
5353
|
+
}
|
|
7331
5354
|
export function lowerBinary(lowerer, expr) {
|
|
7332
5355
|
const loc = locOf(expr);
|
|
7333
5356
|
const op = expr.operatorToken.kind;
|
|
5357
|
+
const cacheHas = lowerRequireCacheHas(lowerer, expr);
|
|
5358
|
+
if (cacheHas)
|
|
5359
|
+
return cacheHas;
|
|
7334
5360
|
if (op === ts.SyntaxKind.EqualsToken || (op >= ts.SyntaxKind.FirstCompoundAssignment && op <= ts.SyntaxKind.LastCompoundAssignment)) {
|
|
5361
|
+
if (ts.isPropertyAccessExpression(expr.left) || ts.isElementAccessExpression(expr.left)) {
|
|
5362
|
+
fenceNodeModuleMutation(lowerer, expr.left, "assignment");
|
|
5363
|
+
}
|
|
7335
5364
|
// `x = e` in EXPRESSION position (`while ((idx = s.indexOf("\n")) !== -1)`,
|
|
7336
5365
|
// `f(x = v)`): evaluate e once, write the binding, yield the assigned
|
|
7337
5366
|
// value — JS evaluation order. Variable targets only (locals and module
|
|
@@ -7442,7 +5471,7 @@ export function lowerBinary(lowerer, expr) {
|
|
|
7442
5471
|
// RHS value is the expression's value — its OWN type, so the
|
|
7443
5472
|
// consumer sees exactly what the checker typed.
|
|
7444
5473
|
if (ts.isPropertyAccessExpression(expr.left) && !expr.left.questionDotToken) {
|
|
7445
|
-
const recv =
|
|
5474
|
+
const recv = tryLowerExpression(lowerer, expr.left.expression);
|
|
7446
5475
|
if (recv && recv.type.kind === "dyn") {
|
|
7447
5476
|
const recvTmp = lowerer.declareHiddenLocal("%setRecv", DYN);
|
|
7448
5477
|
const rhsVal = lowerer.lowerExpr(expr.right);
|
|
@@ -7567,8 +5596,74 @@ export function lowerBinary(lowerer, expr) {
|
|
|
7567
5596
|
// Everything else (`u && flag`, whose value would need a wider
|
|
7568
5597
|
// re-tagged union) stays fenced — in CONDITION position those
|
|
7569
5598
|
// shapes lower through lowerCondition's bool descent instead.
|
|
7570
|
-
|
|
5599
|
+
let target = lowerer.mapTypeOf(lowerer.typeOf(expr));
|
|
5600
|
+
// A broad-JSDoc npm-static body can leave the CHECKER result `any`
|
|
5601
|
+
// even after declaration-backed specialization has recovered both
|
|
5602
|
+
// operands (`helpOption && args.find(...)` in commander). When the
|
|
5603
|
+
// left consists only of always-truthy references and falsy units,
|
|
5604
|
+
// the static result is exactly the right operand plus those units.
|
|
5605
|
+
if (target === null &&
|
|
5606
|
+
lowerer.implicitParamTypes !== null &&
|
|
5607
|
+
npmStaticPackageOfPath(expr.getSourceFile().fileName) !== null &&
|
|
5608
|
+
(lowerer.typeOf(expr).flags & ts.TypeFlags.Any) !== 0 &&
|
|
5609
|
+
left.type.kind === "union") {
|
|
5610
|
+
const leftArms = lowerer.unions.get(left.type.unionId)?.arms;
|
|
5611
|
+
const rightArms = right.type.kind === "union"
|
|
5612
|
+
? lowerer.unions.get(right.type.unionId)?.arms
|
|
5613
|
+
: [right.type];
|
|
5614
|
+
const rightIsStatic = rightArms?.every((arm) => arm.kind !== "dyn" && arm.kind !== "caught" && arm.kind !== "jsval" && arm.kind !== "void") ?? false;
|
|
5615
|
+
if (leftArms && rightArms && rightIsStatic &&
|
|
5616
|
+
leftArms.some(isUnitType) &&
|
|
5617
|
+
leftArms.every((arm) => isUnitType(arm) || REF_TRUTHY_KINDS.has(arm.kind))) {
|
|
5618
|
+
const byKey = new Map();
|
|
5619
|
+
for (const arm of [...rightArms, ...leftArms.filter(isUnitType)]) {
|
|
5620
|
+
byKey.set(typeKey(arm), arm);
|
|
5621
|
+
}
|
|
5622
|
+
const arms = [...byKey.values()].sort((a, b) => (typeKey(a) < typeKey(b) ? -1 : 1));
|
|
5623
|
+
if (arms.length > 1) {
|
|
5624
|
+
target = { kind: "union", unionId: lowerer.unions.intern(arms) };
|
|
5625
|
+
}
|
|
5626
|
+
}
|
|
5627
|
+
}
|
|
7571
5628
|
if (target?.kind === "union") {
|
|
5629
|
+
// `&&` whose left does NOT fit the result union: the checker
|
|
5630
|
+
// built that result by DROPPING left arms which are always
|
|
5631
|
+
// truthy (`Option | undefined && string | undefined` answers
|
|
5632
|
+
// `string | undefined`, not `Option | string | undefined`).
|
|
5633
|
+
// Evaluate the left once, test it in its own union, and retag it
|
|
5634
|
+
// only on the falsy path. Any stranded reference arms are
|
|
5635
|
+
// unreachable there because every JS object is truthy.
|
|
5636
|
+
if (op === ts.SyntaxKind.AmpersandAmpersandToken && left.type.kind === "union" && !typeEquals(left.type, target)) {
|
|
5637
|
+
const def = lowerer.unions.get(left.type.unionId);
|
|
5638
|
+
const trappable = new Set();
|
|
5639
|
+
def?.arms.forEach((arm, tag) => {
|
|
5640
|
+
if (lowerer.armTag(target.unionId, arm) < 0 && REF_TRUTHY_KINDS.has(arm.kind)) {
|
|
5641
|
+
trappable.add(tag);
|
|
5642
|
+
}
|
|
5643
|
+
});
|
|
5644
|
+
const retag = trappable.size === 0
|
|
5645
|
+
? null
|
|
5646
|
+
: lowerer.unionRetagHelper(left.type.unionId, target.unionId, loc, trappable);
|
|
5647
|
+
if (retag) {
|
|
5648
|
+
lowerer.requireTruthyUnion(left.type.unionId, expr);
|
|
5649
|
+
const stmts = [];
|
|
5650
|
+
let stable = left;
|
|
5651
|
+
if (!isSafeToRepeat(left)) {
|
|
5652
|
+
const local = lowerer.declareHiddenLocal("%and", left.type);
|
|
5653
|
+
stmts.push({ kind: "varDecl", localId: local.id, init: left, loc });
|
|
5654
|
+
stable = varRef(local.id, left.type, loc);
|
|
5655
|
+
}
|
|
5656
|
+
const result = {
|
|
5657
|
+
kind: "ternary",
|
|
5658
|
+
cond: lowerer.ensureBool(stable, expr.left),
|
|
5659
|
+
then: lowerer.coerceInto(expr.right, right, target),
|
|
5660
|
+
else_: { kind: "call", callee: retag, args: [stable], type: target, loc },
|
|
5661
|
+
type: target,
|
|
5662
|
+
loc,
|
|
5663
|
+
};
|
|
5664
|
+
return stmts.length === 0 ? result : { kind: "seqExpr", stmts, result, type: target, loc };
|
|
5665
|
+
}
|
|
5666
|
+
}
|
|
7572
5667
|
// `||` whose left does NOT fit the result union: the checker built
|
|
7573
5668
|
// that result by DROPPING the left's falsy arms (`process.env.X ||
|
|
7574
5669
|
// null` is `string | null`, `|| 3000` is `string | number` — the
|
|
@@ -7576,9 +5671,7 @@ export function lowerBinary(lowerer, expr) {
|
|
|
7576
5671
|
// the shared shape below does, retags an arm the test is about to
|
|
7577
5672
|
// rule out and throws where Node yields the default. Single-eval
|
|
7578
5673
|
// instead: test the left in its OWN union and retag only on the
|
|
7579
|
-
// truthy side, where the dropped arms are unreachable.
|
|
7580
|
-
// the eager shape — there the falsy left IS the result, so the
|
|
7581
|
-
// checker keeps those arms in the target and they must coerce.
|
|
5674
|
+
// truthy side, where the dropped arms are unreachable.
|
|
7582
5675
|
if (op === ts.SyntaxKind.BarBarToken && left.type.kind === "union" && !typeEquals(left.type, target)) {
|
|
7583
5676
|
const retag = lowerer.unionRetagHelper(left.type.unionId, target.unionId, loc);
|
|
7584
5677
|
if (retag) {
|
|
@@ -7791,6 +5884,14 @@ export function lowerBinary(lowerer, expr) {
|
|
|
7791
5884
|
// retry lifts the site. Genuine unknown keeps the SC1100 story.
|
|
7792
5885
|
if ((left.type.kind === "dyn" && lowerer.anyOrigin(expr.left)) ||
|
|
7793
5886
|
(right.type.kind === "dyn" && lowerer.anyOrigin(expr.right))) {
|
|
5887
|
+
// `JSON.parse` and other checked-dynamic producers deliberately
|
|
5888
|
+
// remain dyn when assigned to an `any` local: eagerly converting
|
|
5889
|
+
// the binding would deep-copy its data and sever aliases. At an
|
|
5890
|
+
// operator the dynamic build can cross only the operands and let
|
|
5891
|
+
// the engine apply JS's exact coercion semantics. Static builds
|
|
5892
|
+
// retain the SC2011 promise that --dynamic lifts this site.
|
|
5893
|
+
if (lowerer.dynamic)
|
|
5894
|
+
return lowerAnyBinaryInIsland(lowerer, expr, left, right, loc);
|
|
7794
5895
|
lowerer.anyOpFence(`the '${ts.tokenToString(op) ?? ts.SyntaxKind[op]}' operator`, expr);
|
|
7795
5896
|
}
|
|
7796
5897
|
// tsc allows ===/!== on unknown (arithmetic/comparisons it rejects
|
|
@@ -7802,30 +5903,74 @@ export function lowerBinary(lowerer, expr) {
|
|
|
7802
5903
|
// computes. Comparisons come back as static bools; arithmetic stays
|
|
7803
5904
|
// an island value ('1 as any + "x"' is a string over there).
|
|
7804
5905
|
if (left.type.kind === "jsval" || right.type.kind === "jsval") {
|
|
7805
|
-
|
|
7806
|
-
|
|
7807
|
-
|
|
7808
|
-
|
|
7809
|
-
[ts.SyntaxKind.
|
|
7810
|
-
[ts.SyntaxKind.
|
|
7811
|
-
[ts.SyntaxKind.
|
|
7812
|
-
[ts.SyntaxKind.
|
|
7813
|
-
[ts.SyntaxKind.
|
|
7814
|
-
[ts.SyntaxKind.
|
|
7815
|
-
[ts.SyntaxKind.
|
|
7816
|
-
[ts.SyntaxKind.
|
|
7817
|
-
[ts.SyntaxKind.
|
|
5906
|
+
return lowerAnyBinaryInIsland(lowerer, expr, left, right, loc);
|
|
5907
|
+
}
|
|
5908
|
+
if (left.type.kind === "bigint" && right.type.kind === "bigint") {
|
|
5909
|
+
const binary = {
|
|
5910
|
+
[ts.SyntaxKind.PlusToken]: "bigint.add",
|
|
5911
|
+
[ts.SyntaxKind.MinusToken]: "bigint.sub",
|
|
5912
|
+
[ts.SyntaxKind.AsteriskToken]: "bigint.mul",
|
|
5913
|
+
[ts.SyntaxKind.SlashToken]: "bigint.div",
|
|
5914
|
+
[ts.SyntaxKind.PercentToken]: "bigint.mod",
|
|
5915
|
+
[ts.SyntaxKind.AsteriskAsteriskToken]: "bigint.pow",
|
|
5916
|
+
[ts.SyntaxKind.AmpersandToken]: "bigint.and",
|
|
5917
|
+
[ts.SyntaxKind.BarToken]: "bigint.or",
|
|
5918
|
+
[ts.SyntaxKind.CaretToken]: "bigint.xor",
|
|
5919
|
+
[ts.SyntaxKind.LessThanLessThanToken]: "bigint.shl",
|
|
5920
|
+
[ts.SyntaxKind.GreaterThanGreaterThanToken]: "bigint.shr",
|
|
7818
5921
|
};
|
|
7819
|
-
const
|
|
7820
|
-
if (
|
|
7821
|
-
|
|
5922
|
+
const fn = binary[op];
|
|
5923
|
+
if (fn)
|
|
5924
|
+
return { kind: "libCall", fn, args: [left, right], type: BIGINT_T, loc };
|
|
5925
|
+
if (op === ts.SyntaxKind.EqualsEqualsEqualsToken ||
|
|
5926
|
+
op === ts.SyntaxKind.ExclamationEqualsEqualsToken) {
|
|
5927
|
+
const equal = { kind: "libCall", fn: "bigint.eq", args: [left, right], type: BOOL, loc };
|
|
5928
|
+
return op === ts.SyntaxKind.ExclamationEqualsEqualsToken
|
|
5929
|
+
? { kind: "unary", op: "!", operand: equal, type: BOOL, loc }
|
|
5930
|
+
: equal;
|
|
5931
|
+
}
|
|
5932
|
+
if (op === ts.SyntaxKind.LessThanToken || op === ts.SyntaxKind.LessThanEqualsToken ||
|
|
5933
|
+
op === ts.SyntaxKind.GreaterThanToken || op === ts.SyntaxKind.GreaterThanEqualsToken) {
|
|
5934
|
+
const cmp = { kind: "libCall", fn: "bigint.cmp", args: [left, right], type: F64, loc };
|
|
5935
|
+
const zero = { kind: "numLit", value: 0, type: F64, loc };
|
|
5936
|
+
const cmpOp = op === ts.SyntaxKind.LessThanToken ? "<"
|
|
5937
|
+
: op === ts.SyntaxKind.LessThanEqualsToken ? "<="
|
|
5938
|
+
: op === ts.SyntaxKind.GreaterThanToken ? ">" : ">=";
|
|
5939
|
+
return { kind: "bin", op: cmpOp, left: cmp, right: zero, type: BOOL, loc };
|
|
5940
|
+
}
|
|
5941
|
+
}
|
|
5942
|
+
if ((left.type.kind === "bigint" && right.type.kind === "f64") ||
|
|
5943
|
+
(left.type.kind === "f64" && right.type.kind === "bigint")) {
|
|
5944
|
+
if (op === ts.SyntaxKind.LessThanToken || op === ts.SyntaxKind.LessThanEqualsToken ||
|
|
5945
|
+
op === ts.SyntaxKind.GreaterThanToken || op === ts.SyntaxKind.GreaterThanEqualsToken) {
|
|
5946
|
+
const big = left.type.kind === "bigint" ? left : right;
|
|
5947
|
+
const num = left.type.kind === "f64" ? left : right;
|
|
5948
|
+
const cmp = { kind: "libCall", fn: "bigint.cmpNumber", args: [big, num], type: F64, loc };
|
|
5949
|
+
const cmpLocal = lowerer.declareHiddenLocal("%bigcmp", F64);
|
|
5950
|
+
const cmpRef = { kind: "varRef", localId: cmpLocal.id, type: F64, loc };
|
|
5951
|
+
const unordered = {
|
|
5952
|
+
kind: "bin", op: "===", left: cmpRef,
|
|
5953
|
+
right: { kind: "numLit", value: 2, type: F64, loc }, type: BOOL, loc,
|
|
5954
|
+
};
|
|
5955
|
+
const zero = { kind: "numLit", value: 0, type: F64, loc };
|
|
5956
|
+
const leftBig = left.type.kind === "bigint";
|
|
5957
|
+
const cmpOp = leftBig
|
|
5958
|
+
? op === ts.SyntaxKind.LessThanToken ? "<" : op === ts.SyntaxKind.LessThanEqualsToken ? "<=" : op === ts.SyntaxKind.GreaterThanToken ? ">" : ">="
|
|
5959
|
+
: op === ts.SyntaxKind.LessThanToken ? ">" : op === ts.SyntaxKind.LessThanEqualsToken ? ">=" : op === ts.SyntaxKind.GreaterThanToken ? "<" : "<=";
|
|
5960
|
+
const result = {
|
|
5961
|
+
kind: "logical", op: "&&",
|
|
5962
|
+
left: { kind: "unary", op: "!", operand: unordered, type: BOOL, loc },
|
|
5963
|
+
right: { kind: "bin", op: cmpOp, left: cmpRef, right: zero, type: BOOL, loc },
|
|
5964
|
+
type: BOOL, loc,
|
|
5965
|
+
};
|
|
5966
|
+
return {
|
|
5967
|
+
kind: "seqExpr",
|
|
5968
|
+
stmts: [{ kind: "varDecl", localId: cmpLocal.id, init: cmp, loc }],
|
|
5969
|
+
result,
|
|
5970
|
+
type: BOOL,
|
|
5971
|
+
loc,
|
|
5972
|
+
};
|
|
7822
5973
|
}
|
|
7823
|
-
const type = jsOpResultKind(jop) === "bool" ? BOOL : JSVAL;
|
|
7824
|
-
return {
|
|
7825
|
-
kind: "jsOp", op: jop,
|
|
7826
|
-
args: [lowerer.jsvalIn(left, expr.left), lowerer.jsvalIn(right, expr.right)],
|
|
7827
|
-
type, loc,
|
|
7828
|
-
};
|
|
7829
5974
|
}
|
|
7830
5975
|
// An unchecked array read keeps its undefined arm in the IR even when
|
|
7831
5976
|
// the checker typed the binding as `string`. An optional string is not
|
|
@@ -7860,7 +6005,7 @@ export function lowerBinary(lowerer, expr) {
|
|
|
7860
6005
|
// with observable getters/calls.
|
|
7861
6006
|
const prefix = [];
|
|
7862
6007
|
const stable = (operand) => {
|
|
7863
|
-
if (
|
|
6008
|
+
if (isSafeToRepeat(operand) || isSafeToDiscard(operand))
|
|
7864
6009
|
return operand;
|
|
7865
6010
|
const tmp = lowerer.declareHiddenLocal("%plus", operand.type);
|
|
7866
6011
|
prefix.push({ kind: "varDecl", localId: tmp.id, init: operand, loc });
|
|
@@ -8340,6 +6485,7 @@ function typeofAnswer(arm) {
|
|
|
8340
6485
|
case "undefinedT": return "undefined";
|
|
8341
6486
|
case "func": return "function";
|
|
8342
6487
|
case "symbol": return "symbol";
|
|
6488
|
+
case "bigint": return "bigint";
|
|
8343
6489
|
case "nullT":
|
|
8344
6490
|
case "array":
|
|
8345
6491
|
case "map":
|
|
@@ -8359,7 +6505,10 @@ function typeofAnswer(arm) {
|
|
|
8359
6505
|
case "httpClientReq":
|
|
8360
6506
|
case "secureCtx":
|
|
8361
6507
|
case "fsWatcher":
|
|
6508
|
+
case "cryptoHash":
|
|
6509
|
+
case "cryptoHmac":
|
|
8362
6510
|
case "childStream":
|
|
6511
|
+
case "childWriter":
|
|
8363
6512
|
case "procStream":
|
|
8364
6513
|
case "object":
|
|
8365
6514
|
case "record":
|
|
@@ -8518,7 +6667,7 @@ function lowerUnionTypeofTest(lowerer, expr, loc) {
|
|
|
8518
6667
|
continue;
|
|
8519
6668
|
const tags = answers.flatMap((s, i) => (s === b.text ? [i] : []));
|
|
8520
6669
|
if (tags.length === 0 || tags.length === def.arms.length) {
|
|
8521
|
-
if (!
|
|
6670
|
+
if (!isSafeToRepeat(value)) {
|
|
8522
6671
|
lowerer.unsupported("SC1090", expr, "statically-decided 'typeof' tests over effectful operands (bind the value to a const first)");
|
|
8523
6672
|
}
|
|
8524
6673
|
return { kind: "boolLit", value: (tags.length !== 0) !== negated, type: BOOL, loc };
|
|
@@ -8528,7 +6677,7 @@ function lowerUnionTypeofTest(lowerer, expr, loc) {
|
|
|
8528
6677
|
});
|
|
8529
6678
|
if (tags.length === 1)
|
|
8530
6679
|
return isTag(tags[0]);
|
|
8531
|
-
if (!
|
|
6680
|
+
if (!isSafeToRepeat(value)) {
|
|
8532
6681
|
lowerer.unsupported("SC1090", expr, "'typeof' tests matching several union arms over operands that aren't plain reads (bind the value to a const first)");
|
|
8533
6682
|
}
|
|
8534
6683
|
// Tag-in-set as a short-circuit chain (De Morgan for `!==`).
|
|
@@ -8583,7 +6732,7 @@ function lowerDynTypeofTest(lowerer, expr, loc) {
|
|
|
8583
6732
|
const recv = unparen(pn.expression);
|
|
8584
6733
|
if (ts.isAsExpression(recv) || ts.isTypeAssertion(recv)) {
|
|
8585
6734
|
const inner = unparen(recv.expression);
|
|
8586
|
-
const obj =
|
|
6735
|
+
const obj = tryLowerExpression(lowerer, inner);
|
|
8587
6736
|
if (obj?.type.kind === "dyn") {
|
|
8588
6737
|
keyed = {
|
|
8589
6738
|
kind: "dynKeyGet",
|
|
@@ -8606,7 +6755,7 @@ function lowerDynTypeofTest(lowerer, expr, loc) {
|
|
|
8606
6755
|
// "string") ... }`, the validateConfig idiom). Probe the lowering
|
|
8607
6756
|
// and claim exactly the dyn results; anything else keeps its
|
|
8608
6757
|
// sibling lowerings and fences untouched.
|
|
8609
|
-
const probed =
|
|
6758
|
+
const probed = tryLowerExpression(lowerer, a.expression);
|
|
8610
6759
|
if (probed?.type.kind !== "dyn")
|
|
8611
6760
|
continue;
|
|
8612
6761
|
value = probed;
|
|
@@ -8770,33 +6919,6 @@ function lowerCjsExportTableThisMember(lowerer, expr) {
|
|
|
8770
6919
|
return null;
|
|
8771
6920
|
return cjsAccessorCall(lowerer, sibling, locOf(expr));
|
|
8772
6921
|
}
|
|
8773
|
-
/** Lowers an expression, converting a fence (PoisonError) into a null
|
|
8774
|
-
* DECLINE — the probing dispatch for shapes where only the lowered
|
|
8775
|
-
* value's type can say whether a lowering applies. unsupported() pushes
|
|
8776
|
-
* its diagnostic BEFORE throwing, so the probe runs behind a diagnostic
|
|
8777
|
-
* sink: a declined probe's diagnostics vanish (the sibling lowerings
|
|
8778
|
-
* own the site), a successful probe's flush through afterwards. */
|
|
8779
|
-
export function probeLower(lowerer, node) {
|
|
8780
|
-
const saved = lowerer.diagSink;
|
|
8781
|
-
const captured = [];
|
|
8782
|
-
lowerer.diagSink = captured;
|
|
8783
|
-
let result;
|
|
8784
|
-
try {
|
|
8785
|
-
result = lowerer.lowerExpr(node);
|
|
8786
|
-
}
|
|
8787
|
-
catch (e) {
|
|
8788
|
-
if (e instanceof PoisonError) {
|
|
8789
|
-
lowerer.diagSink = saved;
|
|
8790
|
-
return null;
|
|
8791
|
-
}
|
|
8792
|
-
lowerer.diagSink = saved;
|
|
8793
|
-
throw e;
|
|
8794
|
-
}
|
|
8795
|
-
lowerer.diagSink = saved;
|
|
8796
|
-
for (const d of captured)
|
|
8797
|
-
lowerer.pushDiag(d);
|
|
8798
|
-
return result;
|
|
8799
|
-
}
|
|
8800
6922
|
/** A read of a catch binding — the caught analog of maybeNarrow. Where
|
|
8801
6923
|
* tsc's control-flow narrowing has proven a supported test (`instanceof`
|
|
8802
6924
|
* over a hierarchy class, `typeof` over a primitive), the read bridges
|
|
@@ -9276,7 +7398,7 @@ function lowerInExpression(lowerer, expr, loc) {
|
|
|
9276
7398
|
// stringified like every property key (o[k] is o[String(k)] in JS;
|
|
9277
7399
|
// `in` shares the coercion).
|
|
9278
7400
|
{
|
|
9279
|
-
const probed =
|
|
7401
|
+
const probed = tryLowerExpression(lowerer, expr.right);
|
|
9280
7402
|
if (probed?.type.kind === "dyn") {
|
|
9281
7403
|
let k = lowerer.lowerExpr(expr.left); // JS order: the key evaluates first
|
|
9282
7404
|
if (k.type.kind === "f64" || k.type.kind === "string" || k.type.kind === "dyn") {
|
|
@@ -10106,6 +8228,34 @@ export function lowerUnionProperty(lowerer, expr) {
|
|
|
10106
8228
|
if (!def)
|
|
10107
8229
|
throw new InternalCompilerError(`lowerer bug: unknown union ${value.type.unionId}`);
|
|
10108
8230
|
const field = expr.name.text;
|
|
8231
|
+
if (field === "length") {
|
|
8232
|
+
const presentArms = def.arms.filter((arm) => !isUnitType(arm));
|
|
8233
|
+
const present = presentArms.length === 1 ? presentArms[0] : null;
|
|
8234
|
+
if (present?.kind === "string" || present?.kind === "array") {
|
|
8235
|
+
const receiver = lowerer.runtimeOptionalPropertyReceiver(expr.expression, value, present, field);
|
|
8236
|
+
if (receiver !== null) {
|
|
8237
|
+
return present.kind === "string"
|
|
8238
|
+
? { kind: "strIntrinsic", method: "length", receiver, args: [], type: F64, loc: locOf(expr) }
|
|
8239
|
+
: { kind: "arrIntrinsic", method: "length", receiver, args: [], type: F64, loc: locOf(expr) };
|
|
8240
|
+
}
|
|
8241
|
+
}
|
|
8242
|
+
}
|
|
8243
|
+
const nonUnitArms = def.arms.filter((arm) => !isUnitType(arm));
|
|
8244
|
+
if (nonUnitArms.length === 1 && nonUnitArms[0]?.kind === "object") {
|
|
8245
|
+
const objectType = nonUnitArms[0];
|
|
8246
|
+
const fieldType = lowerer.classes.get(objectType.className)?.fields.get(field);
|
|
8247
|
+
if (fieldType !== undefined) {
|
|
8248
|
+
let receiver = lowerer.runtimeOptionalPropertyReceiver(expr.expression, value, objectType, field);
|
|
8249
|
+
if (receiver === null) {
|
|
8250
|
+
const helper = lowerer.narrowedArmHelper(value.type.unionId, objectType, locOf(expr.expression));
|
|
8251
|
+
if (helper !== null)
|
|
8252
|
+
receiver = { kind: "call", callee: helper, args: [value], type: objectType, loc: locOf(expr.expression) };
|
|
8253
|
+
}
|
|
8254
|
+
if (receiver !== null) {
|
|
8255
|
+
return { kind: "fieldGet", obj: receiver, className: objectType.className, field, type: fieldType, loc: locOf(expr) };
|
|
8256
|
+
}
|
|
8257
|
+
}
|
|
8258
|
+
}
|
|
10109
8259
|
let common = null;
|
|
10110
8260
|
for (const arm of def.arms) {
|
|
10111
8261
|
let ft;
|
|
@@ -10770,7 +8920,7 @@ export function lowerFieldCompound(lowerer, access, op, rhsNode, loc) {
|
|
|
10770
8920
|
// receiver is an identifier (checked above), so evaluating it for
|
|
10771
8921
|
// read and write matches JS's once-evaluation observably.
|
|
10772
8922
|
if (ts.isPropertyAccessExpression(access)) {
|
|
10773
|
-
const probed =
|
|
8923
|
+
const probed = tryLowerExpression(lowerer, access.expression);
|
|
10774
8924
|
if (probed?.type.kind === "dyn") {
|
|
10775
8925
|
const key = { kind: "strLit", value: access.name.text, type: STRING, loc: locOf(access.name) };
|
|
10776
8926
|
const read = { kind: "dynKeyGet", key, value: probed, type: DYN, loc };
|