@scriptc/compiler 0.0.36 → 0.1.2
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 +52 -22
- package/ambient/scriptc-overrides.d.ts +0 -7
- package/ambient/scriptc.d.ts +3 -2
- package/dist/backend/build-cache.js.map +1 -1
- package/dist/backend/c/async.js +24 -2
- package/dist/backend/c/async.js.map +1 -1
- package/dist/backend/c/c-emitter.d.ts +22 -10
- package/dist/backend/c/c-emitter.js +60 -15
- package/dist/backend/c/c-emitter.js.map +1 -1
- package/dist/backend/c/exprs.js +289 -55
- 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 +3 -0
- package/dist/backend/c/may-throw.js.map +1 -1
- package/dist/backend/c/stmts.d.ts +3 -5
- package/dist/backend/c/stmts.js +76 -17
- package/dist/backend/c/stmts.js.map +1 -1
- package/dist/backend/c/types.js.map +1 -1
- package/dist/backend/c/walkers.js +36 -12
- package/dist/backend/c/walkers.js.map +1 -1
- package/dist/backend/external-c.d.ts +33 -0
- package/dist/backend/external-c.js +51 -0
- package/dist/backend/external-c.js.map +1 -0
- package/dist/backend/link-plan.d.ts +37 -0
- package/dist/backend/link-plan.js +26 -0
- package/dist/backend/link-plan.js.map +1 -0
- package/dist/backend/linker.d.ts +22 -0
- package/dist/backend/linker.js +236 -0
- package/dist/backend/linker.js.map +1 -0
- package/dist/backend/llvm/blocks.js.map +1 -1
- package/dist/backend/llvm/dyn.js +63 -3
- package/dist/backend/llvm/dyn.js.map +1 -1
- package/dist/backend/llvm/emitter.js +132 -26
- 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 +68 -54
- package/dist/backend/llvm/expr-containers.js.map +1 -1
- package/dist/backend/llvm/expr-context.d.ts +2 -1
- package/dist/backend/llvm/expr-control.js.map +1 -1
- package/dist/backend/llvm/expr-dispatch.js +2 -0
- package/dist/backend/llvm/expr-dispatch.js.map +1 -1
- package/dist/backend/llvm/expr-dynamic.js +6 -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.d.ts +1 -1
- package/dist/backend/llvm/expr-primitives.js +20 -0
- package/dist/backend/llvm/expr-primitives.js.map +1 -1
- package/dist/backend/llvm/expr-records.js.map +1 -1
- package/dist/backend/llvm/expr-stream-bridges.js +245 -10
- package/dist/backend/llvm/expr-stream-bridges.js.map +1 -1
- package/dist/backend/llvm/lib-dispatch.js +2 -0
- package/dist/backend/llvm/lib-dispatch.js.map +1 -1
- package/dist/backend/llvm/lib-shared.js +18 -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/split.js.map +1 -1
- package/dist/backend/llvm/unsupported.js.map +1 -1
- package/dist/backend/llvm/walkers.js +31 -4
- package/dist/backend/llvm/walkers.js.map +1 -1
- package/dist/backend/native-codegen.d.ts +8 -1
- package/dist/backend/native-codegen.js +39 -25
- package/dist/backend/native-codegen.js.map +1 -1
- package/dist/backend/native-codegen.test.js +44 -4
- package/dist/backend/native-codegen.test.js.map +1 -1
- package/dist/backend/native-link-info.d.ts +1 -1
- package/dist/backend/native-link-info.js +42 -18
- package/dist/backend/native-link-info.js.map +1 -1
- package/dist/backend/native-toolchain.js.map +1 -1
- package/dist/backend/native-toolchain.test.js +5 -1
- package/dist/backend/native-toolchain.test.js.map +1 -1
- package/dist/backend/runtime-pack-targets.test.d.ts +1 -0
- package/dist/backend/runtime-pack-targets.test.js +54 -0
- package/dist/backend/runtime-pack-targets.test.js.map +1 -0
- package/dist/backend/runtime-pack.d.ts +5 -30
- package/dist/backend/runtime-pack.js +46 -141
- package/dist/backend/runtime-pack.js.map +1 -1
- package/dist/backend/runtime-pack.test.js +49 -14
- package/dist/backend/runtime-pack.test.js.map +1 -1
- package/dist/backend/targets.d.ts +63 -17
- package/dist/backend/targets.js +251 -35
- package/dist/backend/targets.js.map +1 -1
- package/dist/backend/targets.test.js +27 -5
- package/dist/backend/targets.test.js.map +1 -1
- package/dist/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 +22 -1
- package/dist/coverage/surface-manifest.js.map +1 -1
- package/dist/diagnostics/diagnostic.d.ts +6 -5
- package/dist/diagnostics/diagnostic.js +7 -6
- package/dist/diagnostics/diagnostic.js.map +1 -1
- package/dist/diagnostics/render.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 +12 -0
- package/dist/frontend/lowering/array-values.js +76 -0
- package/dist/frontend/lowering/array-values.js.map +1 -0
- package/dist/frontend/lowering/callback-arg.js.map +1 -1
- package/dist/frontend/lowering/containers/bytes.d.ts +35 -0
- package/dist/frontend/lowering/containers/bytes.js +1021 -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-array-sort.d.ts +22 -0
- package/dist/frontend/lowering/lower-array-sort.js +619 -0
- package/dist/frontend/lowering/lower-array-sort.js.map +1 -0
- package/dist/frontend/lowering/lower-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 +18 -0
- package/dist/frontend/lowering/lower-builtins.js +379 -48
- package/dist/frontend/lowering/lower-builtins.js.map +1 -1
- package/dist/frontend/lowering/lower-calls.d.ts +16 -8
- package/dist/frontend/lowering/lower-calls.js +667 -91
- 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 +779 -2038
- 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 +4 -78
- package/dist/frontend/lowering/lower-exprs.js +1128 -2409
- 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 +50 -36
- package/dist/frontend/lowering/lower-inspect.js.map +1 -1
- package/dist/frontend/lowering/lower-island.d.ts +11 -4
- package/dist/frontend/lowering/lower-island.js +47 -10
- package/dist/frontend/lowering/lower-island.js.map +1 -1
- package/dist/frontend/lowering/lower-modules.js +52 -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 +11 -1
- package/dist/frontend/lowering/lower-server.js.map +1 -1
- package/dist/frontend/lowering/lower-stmts.d.ts +5 -24
- package/dist/frontend/lowering/lower-stmts.js +811 -314
- 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 +109 -34
- package/dist/frontend/lowering/lowerer.js +1225 -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 +10 -2
- package/dist/frontend/lowering/surfaces.js +15 -3
- 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 +506 -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 +122 -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 +495 -68
- package/dist/frontend/program.js.map +1 -1
- package/dist/frontend/resolve.d.ts +10 -2
- package/dist/frontend/resolve.js +90 -14
- package/dist/frontend/resolve.js.map +1 -1
- package/dist/frontend/resolve.test.js +4 -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 +120 -7
- package/dist/frontend/type-mapper.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +50 -28
- package/dist/index.js.map +1 -1
- package/dist/ir/analysis.js.map +1 -1
- package/dist/ir/ir.d.ts +125 -41
- package/dist/ir/ir.js +82 -31
- 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 +127 -95
- 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/fence-eval.js +3 -1
- package/dist/library/fence-eval.js.map +1 -1
- package/dist/library/int-infer.js +12 -1
- package/dist/library/int-infer.js.map +1 -1
- package/dist/library/int-infer.test.js +4 -4
- package/dist/library/int-infer.test.js.map +1 -1
- package/dist/library/library-cache.test.js +5 -5
- 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.d.ts +9 -1
- package/dist/startup-cache.js +16 -1
- package/dist/startup-cache.js.map +1 -1
- package/dist/wasi-paths.js.map +1 -1
- package/package.json +18 -5
- package/surface-manifest.json +64 -8
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { InternalCompilerError } from "../../errors.js";
|
|
2
2
|
/* Statement lowering: the statement dispatch (lowerStmt), variable
|
|
3
3
|
* declarations including destructuring patterns, scoped blocks, control
|
|
4
|
-
* flow (if/while/for/for-of/do, switch, try/catch,
|
|
5
|
-
*
|
|
4
|
+
* flow (if/while/for/for-of/do, switch, try/catch, abrupt completions),
|
|
5
|
+
* explicit resource management, and blocked-binding poisoning. */
|
|
6
6
|
import * as ts from "../ts7/adapter.js";
|
|
7
|
-
import {
|
|
7
|
+
import { arrayValueRead, arrayValueStore, arrayValueType } from "./array-values.js";
|
|
8
|
+
import { lowerForAwaitGenerator, lowerForOfGenerator, lowerYieldStarStatement } from "./lower-generators.js";
|
|
9
|
+
import { lowerForAwaitBuiltin } from "./lower-async-iteration.js";
|
|
8
10
|
import { BOOL, BYTES_U8, CAUGHT, DYN, F64, JSVAL, STRING, UNDEFINED_T, VOID, arrayOf, isUnitType, shapeHasAccessorSlots, typeEquals } from "../../ir/ir.js";
|
|
9
11
|
import { PoisonError, boundIdentifiersOf, dynFallbackType, dynUndefinedExpr, importCallHandleType, neverTaintedJsType, stmtUsesIsland, uncheckedOverloadHandleCall } from "./lowerer.js";
|
|
10
12
|
import { enforceLibBoundary } from "./lib-boundary.js";
|
|
@@ -13,21 +15,263 @@ import { COMPOUND_ASSIGN_OPS, STR_METHODS, UNSUPPORTED_STMT, isStdlibMember, sid
|
|
|
13
15
|
import { isProvenanceSourceFile } from "../provenance-registry.js";
|
|
14
16
|
import { ambientUndefVarRootOf, lowerImportEquals, nsUndefRead, nsWritableTarget, trapDeclRootOf } from "./lower-namespaces.js";
|
|
15
17
|
import { expandoWritableTarget, lowerExpandoAssignStmt } from "./lower-expando.js";
|
|
16
|
-
import { lowerForOfArrayIter, lowerForOfMap, lowerForOfSearchParams, lowerForOfSet, objectIterOverIndexShape, strCharsCall } from "./lower-containers.js";
|
|
17
|
-
import { bindingContextualGenericFnNodeOf, bindingGenericFnAliasInfoOf, bindingGenericFnInfoOf, bindingGenericFnNodeOf, deadUnmappableBinding, implicitLocalFnInfoOf, implicitLocalFnNodeOf, nullishExprUnitOf, nullishGenericBindingUnitOf, recordKeysArrayCall } from "./lower-calls.js";
|
|
18
|
+
import { lowerForOfArrayIter, lowerForOfMap, lowerForOfSearchParams, lowerForOfSet, lowerSafeIndexRead, objectIterOverIndexShape, strCharsCall } from "./lower-containers.js";
|
|
19
|
+
import { bindingContextualGenericFnNodeOf, bindingGenericFnAliasInfoOf, bindingGenericFnInfoOf, bindingGenericFnNodeOf, deadUnmappableBinding, implicitLocalFnInfoOf, implicitLocalFnNodeOf, implicitMethodCallInfersReturn, nullishExprUnitOf, nullishGenericBindingUnitOf, recordKeysArrayCall } from "./lower-calls.js";
|
|
18
20
|
import { isMixinFnBinding, mixinResultBindingClassOf } from "./lower-mixins.js";
|
|
19
21
|
import { genericIfaceBindingKeepsClass } from "./lower-classes.js";
|
|
20
|
-
import { lowerStreamUnderscoreAssign, streamClassAliasDecl
|
|
22
|
+
import { lowerStreamUnderscoreAssign, streamClassAliasDecl } from "./lower-stream.js";
|
|
21
23
|
import { lowerHttpResPropertyAssignment, lowerHttpServerTimeoutAssignment, lowerServerCloseOverrideAssignment } from "./lower-server.js";
|
|
22
24
|
import { builtinMemberRequireDecl, builtinNamespaceDestructureModuleOf, createRequireBindingDecl, createRequireCalleeFileOf, createRequireNamespaceDecl, textCodecBindingDecl } from "./lower-builtins.js";
|
|
23
25
|
import { lowerEnumDeclaration } from "./lower-enums.js";
|
|
24
|
-
import { abstractPropertyDeclOf, aliasTypeofNarrows, isMatchSliceType, lowerAbsenceProbe, lowerGroupsProjection,
|
|
26
|
+
import { abstractPropertyDeclOf, aliasTypeofNarrows, isMatchSliceType, lowerAbsenceProbe, lowerGroupsProjection, lowerOptionalNumber, matchResultNamedGroupsOf, runtimeOptionalTrueIds, symbolFieldInfo, withRuntimeOptionalNarrowed } from "./lower-exprs.js";
|
|
27
|
+
import { isSafeToRepeat } from "./expressions/evaluation-safety.js";
|
|
28
|
+
import { tryLowerExpression } from "./expressions/try-lower-expression.js";
|
|
25
29
|
import { checkerPanicDiag, isCheckerPanic, requiresDynamicDiag } from "../../diagnostics/diagnostic.js";
|
|
26
30
|
import { isParseArgsDynTypeName, isUnitOnlyTsType, unitOnlyUnion } from "../type-mapper.js";
|
|
27
31
|
import { canonicalBuiltinModule } from "../builtin-modules.js";
|
|
28
32
|
import { isRelativeSpecifier } from "../workspace-registry.js";
|
|
29
33
|
import { probeNodeRequireRefusal } from "../npm.js";
|
|
30
|
-
import { varRef } from "../../ir/build.js";
|
|
34
|
+
import { numLit, varRef } from "../../ir/build.js";
|
|
35
|
+
import { constituentTypes } from "../ts7/checker.js";
|
|
36
|
+
import { npmStaticPackageOfPath } from "../npm-static.js";
|
|
37
|
+
import { fenceNodeModuleMutation } from "./lower-node-module.js";
|
|
38
|
+
/** An opted package's directly-returned empty array may take the function's
|
|
39
|
+
* explicit static return element type. This is the common JS accumulator
|
|
40
|
+
* shape (`const result = []; result.push(value); return result`) and is
|
|
41
|
+
* narrower than treating every evolving array in the function alike. */
|
|
42
|
+
function npmStaticReturnedEmptyArrayType(lowerer, decl) {
|
|
43
|
+
if (!ts.isIdentifier(decl.name) ||
|
|
44
|
+
decl.initializer === undefined ||
|
|
45
|
+
!ts.isArrayLiteralExpression(decl.initializer) ||
|
|
46
|
+
decl.initializer.elements.length !== 0 ||
|
|
47
|
+
!isJsSourceFile(decl.getSourceFile()) ||
|
|
48
|
+
npmStaticPackageOfPath(decl.getSourceFile().fileName) === null ||
|
|
49
|
+
lowerer.ctx.returnType.kind !== "array") {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
const symbol = lowerer.checker.getSymbolAtLocation(decl.name);
|
|
53
|
+
if (symbol === undefined)
|
|
54
|
+
return null;
|
|
55
|
+
let owner = decl.parent;
|
|
56
|
+
while (owner !== undefined && !ts.isFunctionLike(owner))
|
|
57
|
+
owner = owner.parent;
|
|
58
|
+
const ownerBody = owner !== undefined && "body" in owner ? owner.body : undefined;
|
|
59
|
+
if (ownerBody === undefined || !ts.isBlock(ownerBody))
|
|
60
|
+
return null;
|
|
61
|
+
let returned = false;
|
|
62
|
+
ts.walkPreorder(ownerBody, (node) => {
|
|
63
|
+
if (node !== ownerBody && ts.isFunctionLike(node))
|
|
64
|
+
return "skip";
|
|
65
|
+
if (!ts.isReturnStatement(node) || node.expression === undefined)
|
|
66
|
+
return undefined;
|
|
67
|
+
let expression = node.expression;
|
|
68
|
+
while (ts.isParenthesizedExpression(expression))
|
|
69
|
+
expression = expression.expression;
|
|
70
|
+
if (ts.isIdentifier(expression) && lowerer.checker.getSymbolAtLocation(expression) === symbol) {
|
|
71
|
+
returned = true;
|
|
72
|
+
return "stop";
|
|
73
|
+
}
|
|
74
|
+
return undefined;
|
|
75
|
+
});
|
|
76
|
+
return returned ? lowerer.ctx.returnType : null;
|
|
77
|
+
}
|
|
78
|
+
/** A JS loop cursor in opted package source can be initialized with `T`
|
|
79
|
+
* and later assigned a nullable self link (`cursor = cursor.parent`). Keep
|
|
80
|
+
* that optional value in the local so the next loop condition, rather than
|
|
81
|
+
* the assignment, narrows it. */
|
|
82
|
+
function npmStaticOptionalSelfWriteType(lowerer, decl, initial) {
|
|
83
|
+
if (!ts.isIdentifier(decl.name) ||
|
|
84
|
+
!isJsSourceFile(decl.getSourceFile()) ||
|
|
85
|
+
npmStaticPackageOfPath(decl.getSourceFile().fileName) === null ||
|
|
86
|
+
initial.kind === "union") {
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
const symbol = lowerer.checker.getSymbolAtLocation(decl.name);
|
|
90
|
+
if (symbol === undefined)
|
|
91
|
+
return null;
|
|
92
|
+
let owner = decl.parent;
|
|
93
|
+
while (owner !== undefined && !ts.isFunctionLike(owner))
|
|
94
|
+
owner = owner.parent;
|
|
95
|
+
const ownerBody = owner !== undefined && "body" in owner ? owner.body : undefined;
|
|
96
|
+
if (ownerBody === undefined || !ts.isBlock(ownerBody))
|
|
97
|
+
return null;
|
|
98
|
+
let candidate = null;
|
|
99
|
+
let invalid = false;
|
|
100
|
+
ts.walkPreorder(ownerBody, (node) => {
|
|
101
|
+
if (node !== ownerBody && ts.isFunctionLike(node))
|
|
102
|
+
return "skip";
|
|
103
|
+
if (ts.isBinaryExpression(node)) {
|
|
104
|
+
let left = node.left;
|
|
105
|
+
while (ts.isParenthesizedExpression(left))
|
|
106
|
+
left = left.expression;
|
|
107
|
+
if (ts.isIdentifier(left) && lowerer.checker.getSymbolAtLocation(left) === symbol) {
|
|
108
|
+
if (node.operatorToken.kind !== ts.SyntaxKind.EqualsToken) {
|
|
109
|
+
invalid = true;
|
|
110
|
+
return "stop";
|
|
111
|
+
}
|
|
112
|
+
const written = lowerer.mapTypeOf(lowerer.typeOf(node.right));
|
|
113
|
+
if (written !== null && typeEquals(written, initial))
|
|
114
|
+
return undefined;
|
|
115
|
+
const arms = written?.kind === "union" ? lowerer.unions.get(written.unionId)?.arms : undefined;
|
|
116
|
+
if (written?.kind !== "union" ||
|
|
117
|
+
arms === undefined ||
|
|
118
|
+
!arms.some((arm) => typeEquals(arm, initial)) ||
|
|
119
|
+
!arms.every((arm) => typeEquals(arm, initial) || isUnitType(arm)) ||
|
|
120
|
+
(candidate !== null && !typeEquals(candidate, written))) {
|
|
121
|
+
invalid = true;
|
|
122
|
+
return "stop";
|
|
123
|
+
}
|
|
124
|
+
candidate = written;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
if ((ts.isPrefixUnaryExpression(node) || ts.isPostfixUnaryExpression(node)) &&
|
|
128
|
+
ts.isIdentifier(node.operand) &&
|
|
129
|
+
lowerer.checker.getSymbolAtLocation(node.operand) === symbol) {
|
|
130
|
+
invalid = true;
|
|
131
|
+
return "stop";
|
|
132
|
+
}
|
|
133
|
+
return undefined;
|
|
134
|
+
});
|
|
135
|
+
return invalid ? null : candidate;
|
|
136
|
+
}
|
|
137
|
+
function isAwaitUsing(list) {
|
|
138
|
+
return (list.flags & ts.NodeFlags.AwaitUsing) === ts.NodeFlags.AwaitUsing;
|
|
139
|
+
}
|
|
140
|
+
function stdlibTypeNamed(lowerer, node, name) {
|
|
141
|
+
const visit = (t) => {
|
|
142
|
+
if (t.flags & ts.TypeFlags.Union)
|
|
143
|
+
return constituentTypes(t).some(visit);
|
|
144
|
+
const sym = t.getAliasSymbol() ?? t.getSymbol();
|
|
145
|
+
return sym?.name === name && lowerer.checker.declarationsOf(sym).some((d) => lowerer.isStdlibFile(d.getSourceFile()));
|
|
146
|
+
};
|
|
147
|
+
return visit(lowerer.typeOf(node));
|
|
148
|
+
}
|
|
149
|
+
function cleanupCall(lowerer, node, value, awaited) {
|
|
150
|
+
const loc = locOf(node);
|
|
151
|
+
if (value.type.kind === "object") {
|
|
152
|
+
const info = lowerer.classes.get(value.type.className);
|
|
153
|
+
if (!info)
|
|
154
|
+
lowerer.flushDeferredClass(value.type.className);
|
|
155
|
+
const asyncFound = awaited && info ? lowerer.findMethodOn(info, "sym:asyncDispose") : null;
|
|
156
|
+
const syncFound = info ? lowerer.findMethodOn(info, "sym:dispose") : null;
|
|
157
|
+
const member = asyncFound ? "sym:asyncDispose" : syncFound ? "sym:dispose" : null;
|
|
158
|
+
const found = asyncFound ?? syncFound;
|
|
159
|
+
if (!info || !member || !found)
|
|
160
|
+
return null;
|
|
161
|
+
if (found.sig.params.length !== 0) {
|
|
162
|
+
lowerer.unsupported("SC1090", node, `${member === "sym:asyncDispose" ? "[Symbol.asyncDispose]" : "[Symbol.dispose]"} methods with parameters`);
|
|
163
|
+
}
|
|
164
|
+
return {
|
|
165
|
+
call: lowerer.accessorCall(value.type.className, member, value, [], found.sig.ret, loc),
|
|
166
|
+
async: member === "sym:asyncDispose",
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
if (value.type.kind === "fileHandle") {
|
|
170
|
+
if (!awaited)
|
|
171
|
+
lowerer.unsupported("SC1090", node, "synchronous 'using' with an async-only FileHandle (use 'await using')");
|
|
172
|
+
return {
|
|
173
|
+
call: { kind: "libCall", fn: "fileHandle.close", args: [value], type: { kind: "promise", inner: VOID }, loc },
|
|
174
|
+
async: true,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
if (value.type.kind === "child") {
|
|
178
|
+
return {
|
|
179
|
+
call: {
|
|
180
|
+
kind: "libCall",
|
|
181
|
+
fn: "child.kill",
|
|
182
|
+
args: [value, { kind: "strLit", value: "SIGTERM", type: STRING, loc }],
|
|
183
|
+
type: BOOL,
|
|
184
|
+
loc,
|
|
185
|
+
},
|
|
186
|
+
async: false,
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
if (value.type.kind === "f64") {
|
|
190
|
+
if (stdlibTypeNamed(lowerer, node, "Timeout")) {
|
|
191
|
+
return { call: { kind: "libCall", fn: "timers.clearTimeout", args: [value], type: VOID, loc }, async: false };
|
|
192
|
+
}
|
|
193
|
+
if (stdlibTypeNamed(lowerer, node, "Immediate")) {
|
|
194
|
+
return { call: { kind: "libCall", fn: "timers.clearImmediate", args: [value], type: VOID, loc }, async: false };
|
|
195
|
+
}
|
|
196
|
+
if (stdlibTypeNamed(lowerer, node, "Interface")) {
|
|
197
|
+
return { call: { kind: "libCall", fn: "rl.close", args: [value], type: VOID, loc }, async: false };
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
return null;
|
|
201
|
+
}
|
|
202
|
+
function cleanupForValue(lowerer, node, value, awaited) {
|
|
203
|
+
const loc = locOf(node);
|
|
204
|
+
if (isUnitType(value.type))
|
|
205
|
+
return [];
|
|
206
|
+
if (value.type.kind === "union") {
|
|
207
|
+
const def = lowerer.unions.get(value.type.unionId);
|
|
208
|
+
if (!def)
|
|
209
|
+
throw new InternalCompilerError(`lowerer bug: unknown using union ${value.type.unionId}`);
|
|
210
|
+
const out = [];
|
|
211
|
+
for (let tag = 0; tag < def.arms.length; tag++) {
|
|
212
|
+
const arm = def.arms[tag];
|
|
213
|
+
if (isUnitType(arm))
|
|
214
|
+
continue;
|
|
215
|
+
const narrowed = { kind: "unionNarrow", unionId: value.type.unionId, tag, value, type: arm, loc };
|
|
216
|
+
const body = cleanupForValue(lowerer, node, narrowed, awaited);
|
|
217
|
+
out.push({
|
|
218
|
+
kind: "if",
|
|
219
|
+
cond: { kind: "unionIsTag", unionId: value.type.unionId, tag, negated: false, value, type: BOOL, loc },
|
|
220
|
+
then: body,
|
|
221
|
+
else_: null,
|
|
222
|
+
loc,
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
return out;
|
|
226
|
+
}
|
|
227
|
+
const cleanup = cleanupCall(lowerer, node, value, awaited);
|
|
228
|
+
if (!cleanup) {
|
|
229
|
+
lowerer.unsupported("SC1090", node, `disposing values of type '${lowerer.fmt(value.type)}' (use a class with a zero-parameter [Symbol.dispose] or [Symbol.asyncDispose] method, or a supported Node resource)`);
|
|
230
|
+
}
|
|
231
|
+
const call = cleanup.call;
|
|
232
|
+
if (cleanup.async) {
|
|
233
|
+
if (call.type.kind !== "promise" || call.type.inner.kind !== "void") {
|
|
234
|
+
lowerer.unsupported("SC1090", node, "[Symbol.asyncDispose] methods whose result is not Promise<void>");
|
|
235
|
+
}
|
|
236
|
+
return [{ kind: "exprStmt", expr: { kind: "awaitExpr", value: call, type: VOID, loc }, loc }];
|
|
237
|
+
}
|
|
238
|
+
const out = [{ kind: "exprStmt", expr: call, loc }];
|
|
239
|
+
if (awaited)
|
|
240
|
+
out.push({ kind: "exprStmt", expr: { kind: "libCall", fn: "async.hop", args: [], type: VOID, loc }, loc });
|
|
241
|
+
return out;
|
|
242
|
+
}
|
|
243
|
+
function lowerUsingStatement(lowerer, stmt) {
|
|
244
|
+
if (ts.isSourceFile(stmt.parent)) {
|
|
245
|
+
lowerer.unsupported("SC1090", stmt, "top-level 'using' declarations (wrap the resource lifetime in a block or function)");
|
|
246
|
+
}
|
|
247
|
+
if (ts.isCaseClause(stmt.parent) || ts.isDefaultClause(stmt.parent)) {
|
|
248
|
+
lowerer.unsupported("SC1090", stmt, "'using' declarations directly in switch clauses (wrap the clause body in a block)");
|
|
249
|
+
}
|
|
250
|
+
const awaited = isAwaitUsing(stmt.declarationList);
|
|
251
|
+
if (awaited && !lowerer.ctx.isAsync) {
|
|
252
|
+
lowerer.unsupported("SC1090", stmt, "'await using' outside an async function or top-level-await module");
|
|
253
|
+
}
|
|
254
|
+
const registrations = [];
|
|
255
|
+
for (const decl of stmt.declarationList.declarations) {
|
|
256
|
+
if (!ts.isIdentifier(decl.name))
|
|
257
|
+
lowerer.unsupported("SC1031", decl.name);
|
|
258
|
+
if (decl.initializer === undefined)
|
|
259
|
+
lowerer.unsupported("SC1090", decl, "a 'using' declaration without an initializer");
|
|
260
|
+
const acquire = lowerer.lowerVarDecl(decl, false);
|
|
261
|
+
if (!acquire)
|
|
262
|
+
throw new InternalCompilerError("lowerer bug: using declaration emitted no acquisition");
|
|
263
|
+
const symbol = lowerer.checker.getSymbolAtLocation(decl.name);
|
|
264
|
+
if (!symbol)
|
|
265
|
+
throw new InternalCompilerError("lowerer bug: using declaration has no symbol");
|
|
266
|
+
const binding = lowerer.bindingIn(lowerer.ctx, symbol) ?? lowerer.globalsBySymbol.get(symbol) ?? null;
|
|
267
|
+
if (!binding)
|
|
268
|
+
throw new InternalCompilerError("lowerer bug: using declaration has no runtime binding");
|
|
269
|
+
const value = varRef(binding.id, binding.type, locOf(decl.name));
|
|
270
|
+
const cleanup = cleanupForValue(lowerer, decl.name, value, awaited);
|
|
271
|
+
registrations.push({ acquire, cleanup, loc: locOf(decl) });
|
|
272
|
+
}
|
|
273
|
+
return registrations;
|
|
274
|
+
}
|
|
31
275
|
/** `const X = /* @__PURE__ *\/ makeX()` at a module's top level: every
|
|
32
276
|
* declarator initialized by a call (or `new`) carrying the bundler PURE
|
|
33
277
|
* annotation in its leading trivia — the shape --provenance-sources may
|
|
@@ -112,6 +356,7 @@ export function provenanceElidedConstDecl(lowerer, decl) {
|
|
|
112
356
|
* `let a = 1, b = a + 1;`) but is counted once. */
|
|
113
357
|
export function lowerStmts(lowerer, stmts) {
|
|
114
358
|
const out = [];
|
|
359
|
+
const usingMarkers = [];
|
|
115
360
|
// JAVASCRIPT sources defer their compile fences to runtime (the
|
|
116
361
|
// JS-input design: no annotations exist to change what lowers, so a
|
|
117
362
|
// statement whose construct has no static lowering compiles to a
|
|
@@ -143,7 +388,25 @@ export function lowerStmts(lowerer, stmts) {
|
|
|
143
388
|
}
|
|
144
389
|
const diagsBefore = lowerer.diags.length;
|
|
145
390
|
try {
|
|
146
|
-
|
|
391
|
+
let lowered;
|
|
392
|
+
let appended = false;
|
|
393
|
+
if (ts.isVariableStatement(stmt) &&
|
|
394
|
+
(stmt.declarationList.flags & ts.NodeFlags.Using) !== 0) {
|
|
395
|
+
const registrations = lowerUsingStatement(lowerer, stmt);
|
|
396
|
+
const accounting = [];
|
|
397
|
+
for (const registration of registrations) {
|
|
398
|
+
out.push(registration.acquire);
|
|
399
|
+
accounting.push(registration.acquire, ...registration.cleanup);
|
|
400
|
+
if (registration.cleanup.length > 0) {
|
|
401
|
+
usingMarkers.push({ start: out.length, cleanup: registration.cleanup, loc: registration.loc });
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
lowered = accounting;
|
|
405
|
+
appended = true;
|
|
406
|
+
}
|
|
407
|
+
else {
|
|
408
|
+
lowered = lowerer.lowerStmt(stmt);
|
|
409
|
+
}
|
|
147
410
|
// The lib-boundary chokepoint (lib-boundary.ts): checked-dynamic
|
|
148
411
|
// arguments that reached builtin-call slots without the checked
|
|
149
412
|
// coercion get their dynCheck here, and the uncoercible ones
|
|
@@ -152,10 +415,12 @@ export function lowerStmts(lowerer, stmts) {
|
|
|
152
415
|
// deferred rejection.
|
|
153
416
|
if (lowered)
|
|
154
417
|
enforceLibBoundary(lowerer, lowered);
|
|
155
|
-
if (
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
418
|
+
if (!appended) {
|
|
419
|
+
if (Array.isArray(lowered))
|
|
420
|
+
out.push(...lowered);
|
|
421
|
+
else if (lowered)
|
|
422
|
+
out.push(lowered);
|
|
423
|
+
}
|
|
159
424
|
// Island accounting (--dynamic coverage): a statement that lowered
|
|
160
425
|
// but carries island constructs compiles DYNAMICALLY — its work
|
|
161
426
|
// runs in the embedded engine, and the report says so instead of
|
|
@@ -214,6 +479,23 @@ export function lowerStmts(lowerer, stmts) {
|
|
|
214
479
|
}
|
|
215
480
|
}
|
|
216
481
|
}
|
|
482
|
+
// Each acquisition protects the remainder of its lexical statement
|
|
483
|
+
// list. Folding from the last registration outward produces the
|
|
484
|
+
// spec's LIFO nesting and ensures a later initializer failure only
|
|
485
|
+
// disposes resources acquired before it.
|
|
486
|
+
for (let i = usingMarkers.length - 1; i >= 0; i--) {
|
|
487
|
+
const marker = usingMarkers[i];
|
|
488
|
+
const body = out.splice(marker.start);
|
|
489
|
+
out.push({
|
|
490
|
+
kind: "tryCatch",
|
|
491
|
+
tryBody: body,
|
|
492
|
+
catchBody: null,
|
|
493
|
+
catchLocalId: null,
|
|
494
|
+
finallyBody: marker.cleanup,
|
|
495
|
+
suppressFinallyErrors: true,
|
|
496
|
+
loc: marker.loc,
|
|
497
|
+
});
|
|
498
|
+
}
|
|
217
499
|
}
|
|
218
500
|
finally {
|
|
219
501
|
lowerer.activeStmtLists.pop();
|
|
@@ -588,44 +870,6 @@ export function lowerScopedBlock(lowerer, stmt) {
|
|
|
588
870
|
lowerer.scopes.pop();
|
|
589
871
|
}
|
|
590
872
|
}
|
|
591
|
-
/** The finally fences. `return` crossing OUT of a try/catch body guarded
|
|
592
|
-
* by a finally is SUPPORTED now (the backend's pending-return path runs
|
|
593
|
-
* every crossed finally inner-to-outer before the function returns, the
|
|
594
|
-
* value snapshotted first — Node-exact); what stays rejected is (a) any
|
|
595
|
-
* jump out of a finally BLOCK itself (a return there would REPLACE a
|
|
596
|
-
* pending completion — clearing a still-propagating exception or
|
|
597
|
-
* abandoning a pending return, a model the emitter doesn't implement),
|
|
598
|
-
* and (b) break/continue crossing a try-with-finally (their targets bind
|
|
599
|
-
* inside the function; the pending-action plumbing exists only for
|
|
600
|
-
* return, the shape real code needs). Rejected, not miscompiled. Plain
|
|
601
|
-
* try/catch is transparent to jumps (it never appears in ctl). A LABELED
|
|
602
|
-
* jump walks outward past every construct (labeled targets can be
|
|
603
|
-
* arbitrarily far out) until the entry carrying its label, applying the
|
|
604
|
-
* same finally fences to everything crossed on the way. */
|
|
605
|
-
export function rejectJumpCrossingFinally(lowerer, kw, stmt, label) {
|
|
606
|
-
const ctl = lowerer.ctx.ctl;
|
|
607
|
-
for (let i = ctl.length - 1; i >= 0; i--) {
|
|
608
|
-
const c = ctl[i];
|
|
609
|
-
if (c.kind === "finallyBlock") {
|
|
610
|
-
lowerer.unsupported("SC1090", stmt, `'${kw}' out of a 'finally' block (it would replace the pending completion — restructure so the finally only cleans up)`);
|
|
611
|
-
}
|
|
612
|
-
if (c.kind === "tryFinally") {
|
|
613
|
-
if (kw !== "return") {
|
|
614
|
-
lowerer.unsupported("SC1090", stmt, `'${kw}' crossing a 'finally' block`);
|
|
615
|
-
}
|
|
616
|
-
continue; // return runs the finally on the way out — supported
|
|
617
|
-
}
|
|
618
|
-
if (kw === "return")
|
|
619
|
-
continue; // return crosses every construct
|
|
620
|
-
if (label !== undefined) {
|
|
621
|
-
if (c.labels?.includes(label))
|
|
622
|
-
return; // the labeled target — binds here
|
|
623
|
-
continue; // an inner construct the labeled jump exits
|
|
624
|
-
}
|
|
625
|
-
if (c.kind === "loop" || (kw === "break" && c.kind === "switch"))
|
|
626
|
-
return; // binds inside the region
|
|
627
|
-
}
|
|
628
|
-
}
|
|
629
873
|
/** `lbl: stmt` — labeled statements. A label chain collapses to one name
|
|
630
874
|
* list (`a: b: while ...` — both names target the same loop, JS-exact).
|
|
631
875
|
* Loops and switches take the labels DIRECTLY (a labeled continue needs
|
|
@@ -719,7 +963,6 @@ export function lowerStmt(lowerer, stmt) {
|
|
|
719
963
|
if (ts.isLabeledStatement(stmt))
|
|
720
964
|
return lowerLabeled(lowerer, stmt);
|
|
721
965
|
if (ts.isReturnStatement(stmt)) {
|
|
722
|
-
lowerer.rejectJumpCrossingFinally("return", stmt);
|
|
723
966
|
// Implicit-any instance RETURN INFERENCE (lower-calls'
|
|
724
967
|
// resolveInferredReturn): the value lowers BARE and the statement
|
|
725
968
|
// records itself — the post-pass settles the instance's return type
|
|
@@ -783,10 +1026,8 @@ export function lowerStmt(lowerer, stmt) {
|
|
|
783
1026
|
if (!target || (kw === "continue" && target.kind !== "loop")) {
|
|
784
1027
|
lowerer.unsupported("SC1050", stmt, `'${kw} ${label}' targeting this statement form (the labeled statement lowers through a desugar that has no label point)`);
|
|
785
1028
|
}
|
|
786
|
-
lowerer.rejectJumpCrossingFinally(kw, stmt, label);
|
|
787
1029
|
return { kind: kw, label, loc: locOf(stmt) };
|
|
788
1030
|
}
|
|
789
|
-
lowerer.rejectJumpCrossingFinally(kw, stmt);
|
|
790
1031
|
// tsc rejects break outside loops/switches and continue outside loops,
|
|
791
1032
|
// so the context is always valid here (the validator re-checks).
|
|
792
1033
|
return { kind: kw, loc: locOf(stmt) };
|
|
@@ -1264,6 +1505,11 @@ export function lowerDestructuringDecl(lowerer, decl, isLet) {
|
|
|
1264
1505
|
/** One pattern level: emits a declaration (or global assignment) per
|
|
1265
1506
|
* bound name, recursing through hidden temps for nested patterns. */
|
|
1266
1507
|
export function lowerBindingPattern(lowerer, pattern, srcRef, srcType, isLet, out, dynSpell, allowDynObject = false) {
|
|
1508
|
+
const present = patternSourceValue(lowerer, srcRef(), ts.isArrayBindingPattern(pattern), out, locOf(pattern), dynSpell);
|
|
1509
|
+
if (!typeEquals(present.type, srcType)) {
|
|
1510
|
+
lowerer.lowerBindingPattern(pattern, () => present, present.type, isLet, out, dynSpell, allowDynObject);
|
|
1511
|
+
return;
|
|
1512
|
+
}
|
|
1267
1513
|
if (ts.isArrayBindingPattern(pattern)) {
|
|
1268
1514
|
lowerer.fenceStaticHeadersIteration(pattern);
|
|
1269
1515
|
// Tuple sources: each position is a field read of the tuple's record
|
|
@@ -1500,29 +1746,22 @@ export function lowerBindingPattern(lowerer, pattern, srcRef, srcType, isLet, ou
|
|
|
1500
1746
|
if (ts.isOmittedExpression(el) || el.name === undefined)
|
|
1501
1747
|
return; // hole: position skipped, like JS over an array
|
|
1502
1748
|
const loc = locOf(el);
|
|
1503
|
-
//
|
|
1504
|
-
//
|
|
1505
|
-
//
|
|
1506
|
-
// and a NESTED rest pattern (`[...[a, b]]`) destructures the copy.
|
|
1749
|
+
// Array rest drains values from the iterator, so holes become
|
|
1750
|
+
// present undefined in the fresh tail. Nested rest patterns then
|
|
1751
|
+
// destructure that materialized copy.
|
|
1507
1752
|
if (el.dotDotDotToken) {
|
|
1508
1753
|
if (el.initializer) {
|
|
1509
1754
|
lowerer.unsupported("SC1031", el, "defaults on rest elements"); // tsc rejects; defensive
|
|
1510
1755
|
}
|
|
1511
|
-
const value =
|
|
1512
|
-
kind: "arrIntrinsic",
|
|
1513
|
-
method: "slice",
|
|
1514
|
-
receiver: srcRef(),
|
|
1515
|
-
args: [{ kind: "numLit", value: i, type: F64, loc }],
|
|
1516
|
-
type: srcType,
|
|
1517
|
-
loc,
|
|
1518
|
-
};
|
|
1756
|
+
const value = arrayRestValue(srcRef(), i, srcType, loc);
|
|
1519
1757
|
lowerer.bindPatternTarget(el.name, value, isLet, out);
|
|
1520
1758
|
return;
|
|
1521
1759
|
}
|
|
1522
|
-
|
|
1760
|
+
const position = { kind: "numLit", value: i, type: F64, loc };
|
|
1761
|
+
let value = lowerSafeIndexRead(lowerer, srcRef(), position, loc) ?? {
|
|
1523
1762
|
kind: "arrayGet",
|
|
1524
1763
|
arr: srcRef(),
|
|
1525
|
-
index:
|
|
1764
|
+
index: position,
|
|
1526
1765
|
type: srcType.elem,
|
|
1527
1766
|
loc,
|
|
1528
1767
|
};
|
|
@@ -1842,6 +2081,33 @@ export function lowerBindingPattern(lowerer, pattern, srcRef, srcType, isLet, ou
|
|
|
1842
2081
|
lowerer.bindPatternTarget(el.name, value, isLet, out);
|
|
1843
2082
|
}
|
|
1844
2083
|
}
|
|
2084
|
+
/** Destructuring a missing array element throws before reading members.
|
|
2085
|
+
* Only unwrap a union after testing every nullish arm; this also keeps
|
|
2086
|
+
* dense record/tuple callbacks on their existing payload representation. */
|
|
2087
|
+
function patternSourceValue(lowerer, value, arrayPattern, out, loc, spelling) {
|
|
2088
|
+
if (value.type.kind !== "union")
|
|
2089
|
+
return value;
|
|
2090
|
+
const unionId = value.type.unionId;
|
|
2091
|
+
const arms = lowerer.unions.get(unionId)?.arms;
|
|
2092
|
+
const present = arms?.filter((arm) => !isUnitType(arm));
|
|
2093
|
+
if (!arms || present?.length !== 1 || arms.length === 1)
|
|
2094
|
+
return value;
|
|
2095
|
+
for (const [tag, arm] of arms.entries()) {
|
|
2096
|
+
if (!isUnitType(arm))
|
|
2097
|
+
continue;
|
|
2098
|
+
const unit = arm.kind === "undefinedT" ? "undefined" : "null";
|
|
2099
|
+
const message = arrayPattern
|
|
2100
|
+
? spelling ?? `${unit} is not iterable (cannot read property Symbol(Symbol.iterator))`
|
|
2101
|
+
: `Cannot destructure ${unit}`;
|
|
2102
|
+
out.push({
|
|
2103
|
+
kind: "if",
|
|
2104
|
+
cond: { kind: "unionIsTag", unionId, tag, value, negated: false, type: BOOL, loc },
|
|
2105
|
+
then: [{ kind: "throw", value: { kind: "libCall", fn: "error.new", args: [{ kind: "strLit", value: message, type: STRING, loc }], type: { kind: "object", className: "%TypeError" }, loc }, loc }],
|
|
2106
|
+
else_: null, loc,
|
|
2107
|
+
});
|
|
2108
|
+
}
|
|
2109
|
+
return { kind: "unionNarrow", unionId, tag: lowerer.armTag(unionId, present[0]), value, type: present[0], loc };
|
|
2110
|
+
}
|
|
1845
2111
|
/** The type a pattern element binds at — the checker's type of the bound
|
|
1846
2112
|
* identifier, or of the nested pattern itself (its implied type). Null
|
|
1847
2113
|
* when the type has no static mapping (the caller fences). */
|
|
@@ -2138,13 +2404,9 @@ function undefArmDefault(lowerer, blame, init, value, bodyT) {
|
|
|
2138
2404
|
const dflt = lowerer.lowerExprExpecting(init, bodyT);
|
|
2139
2405
|
return { kind: "ternary", cond: isUndef, then: dflt, else_: present, type: bodyT, loc };
|
|
2140
2406
|
}
|
|
2141
|
-
/**
|
|
2142
|
-
*
|
|
2143
|
-
*
|
|
2144
|
-
* where the plain read would trap (the array divergence's carve-out).
|
|
2145
|
-
* Elements with an undefined arm land in a hidden temp first (in-bounds
|
|
2146
|
-
* undefined and past-the-end collapse to the same arm), then the
|
|
2147
|
-
* ordinary undefined-arm default applies. */
|
|
2407
|
+
/** An array binding default tests the value produced by the read. Holes,
|
|
2408
|
+
* out-of-range positions, and explicit undefined share the same arm.
|
|
2409
|
+
* Typed-array reads retain the separate bounds check below. */
|
|
2148
2410
|
function arrayPositionDefault(lowerer, el, read, srcRef, index, elemT, out) {
|
|
2149
2411
|
const name = el.name; // callers skip nameless elisions
|
|
2150
2412
|
const bodyT = patternBindingType(lowerer, name);
|
|
@@ -2157,6 +2419,11 @@ function arrayPositionDefault(lowerer, el, read, srcRef, index, elemT, out) {
|
|
|
2157
2419
|
* arrives from the existing binding instead of the checker. */
|
|
2158
2420
|
function arrayPositionDefaultValue(lowerer, blame, init, read, srcRef, index, elemT, bodyT, out) {
|
|
2159
2421
|
const loc = read.loc;
|
|
2422
|
+
if (read.type.kind === "union" && lowerer.armTag(read.type.unionId, UNDEFINED_T) >= 0) {
|
|
2423
|
+
const tmp = lowerer.declareHiddenLocal("%delem", read.type);
|
|
2424
|
+
out.push({ kind: "varDecl", localId: tmp.id, init: read, loc });
|
|
2425
|
+
return undefArmDefault(lowerer, blame, init, varRef(tmp.id, read.type, loc), bodyT);
|
|
2426
|
+
}
|
|
2160
2427
|
// The unit-only element (a declared-empty-tuple source riding the
|
|
2161
2428
|
// array representation): every read is undefined — the default IS the
|
|
2162
2429
|
// binding.
|
|
@@ -2213,6 +2480,15 @@ function arrayPositionDefaultValue(lowerer, blame, init, read, srcRef, index, el
|
|
|
2213
2480
|
loc,
|
|
2214
2481
|
};
|
|
2215
2482
|
}
|
|
2483
|
+
/** Array rest copies values and materializes holes. Removing the prefix
|
|
2484
|
+
* through the copying splice primitive preserves the payload ABI. */
|
|
2485
|
+
function arrayRestValue(receiver, from, type, loc) {
|
|
2486
|
+
return {
|
|
2487
|
+
kind: "arrIntrinsic", method: "toSpliced", receiver,
|
|
2488
|
+
args: [numLit(0, loc), numLit(from, loc), { kind: "arrayLit", elems: [], type, loc }],
|
|
2489
|
+
type, loc,
|
|
2490
|
+
};
|
|
2491
|
+
}
|
|
2216
2492
|
/** The fresh tail for `[head, ...rest]` over a TUPLE source, packed under
|
|
2217
2493
|
* the checker's own rest type: an ARRAY of the remaining positions (each
|
|
2218
2494
|
* read coercing into the element type) or a tuple RECORD when the
|
|
@@ -2790,12 +3066,31 @@ export function bindPatternTarget(lowerer, name, value, isLet, out, allowDynObje
|
|
|
2790
3066
|
const mapped = lowerer.mapTypeOf(lowerer.typeOf(name));
|
|
2791
3067
|
return mapped === null || (lowerer.dynamic && jsvalFlavoredType(mapped));
|
|
2792
3068
|
})();
|
|
2793
|
-
|
|
3069
|
+
let type = dynStays ? DYN : lowerer.runtimeOptionalBindingType(name, lowerer.irTypeOf(name)) ?? lowerer.irTypeOf(name);
|
|
2794
3070
|
if (type.kind === "void")
|
|
2795
3071
|
lowerer.badType(name, lowerer.typeOf(name));
|
|
3072
|
+
// Array destructuring positions are ordinary indexed reads. When the
|
|
3073
|
+
// checker was built without noUncheckedIndexedAccess it still spells
|
|
3074
|
+
// the binding as the bare element type, but the runtime value carries
|
|
3075
|
+
// the same `T | undefined` possibility as a direct array read. Adopt
|
|
3076
|
+
// that union for an unannotated pattern binding so undefined survives
|
|
3077
|
+
// through later locals, templates, operators, and narrowing instead
|
|
3078
|
+
// of being extracted with a checked arm read.
|
|
3079
|
+
let runtimeOptional = false;
|
|
3080
|
+
if (!dynStays && value.type.kind === "union" &&
|
|
3081
|
+
lowerer.armTag(value.type.unionId, UNDEFINED_T) >= 0 &&
|
|
3082
|
+
typeEquals(lowerer.stripUndefinedArm(value.type), type)) {
|
|
3083
|
+
type = value.type;
|
|
3084
|
+
runtimeOptional = true;
|
|
3085
|
+
}
|
|
2796
3086
|
const coerced = lowerer.coerceInto(name, value, type);
|
|
2797
3087
|
const local = lowerer.declareLocal(name, name.text, type, isLet);
|
|
2798
3088
|
out.push({ kind: "varDecl", localId: local.id, init: coerced, loc });
|
|
3089
|
+
if (runtimeOptional) {
|
|
3090
|
+
const runtimeOptionalRoot = lowerer.runtimeOptionalRootOf(local);
|
|
3091
|
+
lowerer.runtimeOptionalLocals.add(runtimeOptionalRoot);
|
|
3092
|
+
lowerer.runtimeOptionalStorageLocals.add(runtimeOptionalRoot);
|
|
3093
|
+
}
|
|
2799
3094
|
return;
|
|
2800
3095
|
}
|
|
2801
3096
|
const tmp = lowerer.declareHiddenLocal("%destr", value.type);
|
|
@@ -2849,6 +3144,95 @@ export function lowerVarDeclList(lowerer, list) {
|
|
|
2849
3144
|
throw new InternalCompilerError("lowerer bug: for-init declarator resolved to a global");
|
|
2850
3145
|
return lowered;
|
|
2851
3146
|
}
|
|
3147
|
+
/** A single static type for an initializer-less JavaScript `let` whose
|
|
3148
|
+
* later plain writes all agree. An evolving JS local reads as `undefined`
|
|
3149
|
+
* until control flow proves a write, so a scalar slot is sound only when
|
|
3150
|
+
* every ordinary use already has that concrete flow type. Any closure
|
|
3151
|
+
* capture stays checked-dynamic: it can observe the local before an outer
|
|
3152
|
+
* assignment, and the checker deliberately leaves captured evolving lets
|
|
3153
|
+
* broad. */
|
|
3154
|
+
function inferredEvolvingLetType(lowerer, decl) {
|
|
3155
|
+
if (!ts.isIdentifier(decl.name))
|
|
3156
|
+
return null;
|
|
3157
|
+
const symbol = lowerer.checker.getSymbolAtLocation(decl.name);
|
|
3158
|
+
const scope = decl.parent.parent.parent;
|
|
3159
|
+
if (symbol === undefined || (!ts.isBlock(scope) && !ts.isSourceFile(scope)))
|
|
3160
|
+
return null;
|
|
3161
|
+
const isPlainAssignmentTarget = (node) => {
|
|
3162
|
+
let target = node;
|
|
3163
|
+
while (ts.isParenthesizedExpression(target.parent) && target.parent.expression === target) {
|
|
3164
|
+
target = target.parent;
|
|
3165
|
+
}
|
|
3166
|
+
const parent = target.parent;
|
|
3167
|
+
return ts.isBinaryExpression(parent) &&
|
|
3168
|
+
parent.operatorToken.kind === ts.SyntaxKind.EqualsToken &&
|
|
3169
|
+
parent.left === target;
|
|
3170
|
+
};
|
|
3171
|
+
const captures = (fn) => {
|
|
3172
|
+
let captured = false;
|
|
3173
|
+
ts.walkPreorder(fn, (node) => {
|
|
3174
|
+
if (node !== fn &&
|
|
3175
|
+
ts.isIdentifier(node) &&
|
|
3176
|
+
lowerer.checker.getSymbolAtLocation(node) === symbol) {
|
|
3177
|
+
captured = true;
|
|
3178
|
+
return "stop";
|
|
3179
|
+
}
|
|
3180
|
+
return undefined;
|
|
3181
|
+
});
|
|
3182
|
+
return captured;
|
|
3183
|
+
};
|
|
3184
|
+
const writes = [];
|
|
3185
|
+
const reads = [];
|
|
3186
|
+
let invalid = false;
|
|
3187
|
+
ts.walkPreorder(scope, (node) => {
|
|
3188
|
+
if (node !== scope && ts.isFunctionLike(node)) {
|
|
3189
|
+
if (captures(node))
|
|
3190
|
+
invalid = true;
|
|
3191
|
+
return "skip";
|
|
3192
|
+
}
|
|
3193
|
+
if (ts.isBinaryExpression(node)) {
|
|
3194
|
+
let left = node.left;
|
|
3195
|
+
while (ts.isParenthesizedExpression(left))
|
|
3196
|
+
left = left.expression;
|
|
3197
|
+
if (ts.isIdentifier(left) && lowerer.checker.getSymbolAtLocation(left) === symbol) {
|
|
3198
|
+
if (node.operatorToken.kind !== ts.SyntaxKind.EqualsToken) {
|
|
3199
|
+
invalid = true;
|
|
3200
|
+
return "stop";
|
|
3201
|
+
}
|
|
3202
|
+
if (ts.isCallExpression(node.right) && implicitMethodCallInfersReturn(lowerer, node.right)) {
|
|
3203
|
+
invalid = true;
|
|
3204
|
+
return "stop";
|
|
3205
|
+
}
|
|
3206
|
+
const mapped = lowerer.mapTypeOf(lowerer.typeOf(node.right));
|
|
3207
|
+
if (!mapped || mapped.kind === "dyn" || mapped.kind === "jsval" || mapped.kind === "void" || isUnitType(mapped)) {
|
|
3208
|
+
invalid = true;
|
|
3209
|
+
return "stop";
|
|
3210
|
+
}
|
|
3211
|
+
writes.push(mapped);
|
|
3212
|
+
}
|
|
3213
|
+
}
|
|
3214
|
+
if ((ts.isPrefixUnaryExpression(node) || ts.isPostfixUnaryExpression(node)) &&
|
|
3215
|
+
ts.isIdentifier(node.operand) &&
|
|
3216
|
+
lowerer.checker.getSymbolAtLocation(node.operand) === symbol) {
|
|
3217
|
+
invalid = true;
|
|
3218
|
+
return "stop";
|
|
3219
|
+
}
|
|
3220
|
+
if (ts.isIdentifier(node) &&
|
|
3221
|
+
node !== decl.name &&
|
|
3222
|
+
lowerer.checker.getSymbolAtLocation(node) === symbol &&
|
|
3223
|
+
!isPlainAssignmentTarget(node)) {
|
|
3224
|
+
reads.push(node);
|
|
3225
|
+
}
|
|
3226
|
+
return undefined;
|
|
3227
|
+
});
|
|
3228
|
+
const first = writes[0];
|
|
3229
|
+
if (invalid || first === undefined || !writes.every((write) => typeEquals(write, first)))
|
|
3230
|
+
return null;
|
|
3231
|
+
return reads.every((read) => {
|
|
3232
|
+
const mapped = lowerer.mapTypeOf(lowerer.typeOf(read));
|
|
3233
|
+
return mapped !== null && typeEquals(mapped, first);
|
|
3234
|
+
}) ? first : null;
|
|
3235
|
+
}
|
|
2852
3236
|
export function lowerVarDecl(lowerer, decl, isLet) {
|
|
2853
3237
|
// --provenance-sources: an elided pure-annotated dead const emits
|
|
2854
3238
|
// nothing (collectGlobals registered no global by the same test —
|
|
@@ -3026,8 +3410,23 @@ export function lowerVarDecl(lowerer, decl, isLet) {
|
|
|
3026
3410
|
// (statics are zero/NULL-initialized; uninitialized globals need no
|
|
3027
3411
|
// statement at all).
|
|
3028
3412
|
const declSymbol = lowerer.checker.getSymbolAtLocation(decl.name);
|
|
3029
|
-
const g =
|
|
3413
|
+
const g = ts.isIdentifier(decl.name)
|
|
3414
|
+
? lowerer.globalOf(decl.name)
|
|
3415
|
+
: (declSymbol ? lowerer.globalsBySymbol.get(declSymbol) : undefined);
|
|
3030
3416
|
if (g) {
|
|
3417
|
+
let promotedGlobal = lowerer.runtimeOptionalBindingType(decl.name, g.type);
|
|
3418
|
+
if (g.type.kind === "func" && decl.initializer !== undefined && ts.isIdentifier(decl.initializer)) {
|
|
3419
|
+
const sig = lowerer.fnSigOf(decl.initializer);
|
|
3420
|
+
if (sig) {
|
|
3421
|
+
promotedGlobal = {
|
|
3422
|
+
...g.type,
|
|
3423
|
+
params: sig.params.filter((p) => p.mode !== "dynRest").map((p) => p.type),
|
|
3424
|
+
ret: sig.returnType,
|
|
3425
|
+
};
|
|
3426
|
+
}
|
|
3427
|
+
}
|
|
3428
|
+
if (!typeEquals(promotedGlobal, g.type))
|
|
3429
|
+
g.type = promotedGlobal;
|
|
3031
3430
|
if (!decl.initializer) {
|
|
3032
3431
|
// `let x: string | undefined;` at file scope: READABLE before any
|
|
3033
3432
|
// assignment — JS gives undefined, so the slot must hold the
|
|
@@ -3048,7 +3447,16 @@ export function lowerVarDecl(lowerer, decl, isLet) {
|
|
|
3048
3447
|
return { kind: "assign", localId: g.id, value: wrapped, loc: locOf(decl) };
|
|
3049
3448
|
return null;
|
|
3050
3449
|
}
|
|
3051
|
-
const
|
|
3450
|
+
const raw = lowerVariableInitializer(lowerer, decl.initializer);
|
|
3451
|
+
const arithmetic = raw.type.kind === "union"
|
|
3452
|
+
? lowerer.unions.get(raw.type.unionId)?.arms
|
|
3453
|
+
: undefined;
|
|
3454
|
+
if (g.type.kind === "string" && arithmetic?.length === 2 &&
|
|
3455
|
+
arithmetic.some((a) => a.kind === "f64") && arithmetic.some((a) => a.kind === "string")) {
|
|
3456
|
+
g.type = raw.type;
|
|
3457
|
+
lowerer.runtimeOptionalArithmeticGlobals.add(g);
|
|
3458
|
+
}
|
|
3459
|
+
const init = lowerer.coerceInto(decl.initializer, raw, g.type);
|
|
3052
3460
|
return { kind: "assign", localId: g.id, value: init, loc: locOf(decl) };
|
|
3053
3461
|
}
|
|
3054
3462
|
// A forward-captured const pre-declared as a TDZ box (an earlier
|
|
@@ -3084,6 +3492,9 @@ export function lowerVarDecl(lowerer, decl, isLet) {
|
|
|
3084
3492
|
// immediately (the type covers the unassigned state), so they
|
|
3085
3493
|
// initialize to the interned undefined arm like an omitted optional.
|
|
3086
3494
|
let type = lowerer.irTypeOf(decl.name);
|
|
3495
|
+
if (type.kind === "dyn" && isLet && isJsSourceFile(decl.getSourceFile())) {
|
|
3496
|
+
type = inferredEvolvingLetType(lowerer, decl) ?? type;
|
|
3497
|
+
}
|
|
3087
3498
|
// `var v: void;` / `let x: undefined;` — a unit-only binding rides
|
|
3088
3499
|
// the unit-only union (its undefined arm is the unassigned state,
|
|
3089
3500
|
// which is also the only state).
|
|
@@ -3108,7 +3519,7 @@ export function lowerVarDecl(lowerer, decl, isLet) {
|
|
|
3108
3519
|
try {
|
|
3109
3520
|
init = immediatelyGuardedAbsenceProbe(lowerer, decl)
|
|
3110
3521
|
? (lowerAbsenceProbe(lowerer, decl.initializer) ?? lowerer.lowerExpr(decl.initializer))
|
|
3111
|
-
: lowerer
|
|
3522
|
+
: lowerVariableInitializer(lowerer, decl.initializer);
|
|
3112
3523
|
}
|
|
3113
3524
|
catch (e) {
|
|
3114
3525
|
if (e instanceof PoisonError) {
|
|
@@ -3154,6 +3565,12 @@ export function lowerVarDecl(lowerer, decl, isLet) {
|
|
|
3154
3565
|
(lowerer.dynamic && init.type.kind === "dyn" && jsvalFlavoredType(lowerer.mapTypeOf(lowerer.typeOf(decl.name)) ?? DYN) ? DYN : null) ??
|
|
3155
3566
|
(bindingTainted ? null : lowerer.mapTypeOf(lowerer.typeOf(decl.name))) ??
|
|
3156
3567
|
(init.type.kind === "dyn" ? DYN : null);
|
|
3568
|
+
if (lowerer.implicitParamTypes !== null &&
|
|
3569
|
+
init.type.kind === "dyn" &&
|
|
3570
|
+
isJsSourceFile(decl.getSourceFile()) &&
|
|
3571
|
+
npmStaticPackageOfPath(decl.getSourceFile().fileName) !== null) {
|
|
3572
|
+
type = DYN;
|
|
3573
|
+
}
|
|
3157
3574
|
// A JS `let x = {}`: TS's empty-object-literal type admits ANY later
|
|
3158
3575
|
// non-nullish assignment (`envs = {}`, later `envs =
|
|
3159
3576
|
// Object.fromEntries(...)` — tsc accepts every such write, since
|
|
@@ -3166,6 +3583,11 @@ export function lowerVarDecl(lowerer, decl, isLet) {
|
|
|
3166
3583
|
if (shape && shape.fields.length === 0 && !shape.indexValue && !shape.tuple)
|
|
3167
3584
|
type = DYN;
|
|
3168
3585
|
}
|
|
3586
|
+
const returnedEmptyArrayType = npmStaticReturnedEmptyArrayType(lowerer, decl);
|
|
3587
|
+
if (returnedEmptyArrayType !== null) {
|
|
3588
|
+
type = returnedEmptyArrayType;
|
|
3589
|
+
init = lowerer.lowerExprExpecting(decl.initializer, returnedEmptyArrayType);
|
|
3590
|
+
}
|
|
3169
3591
|
// A JS `const leaked = [];` (the evolving-array idiom — test/common's
|
|
3170
3592
|
// leak ledger): tsc types the binding by its LATER pushes, but this
|
|
3171
3593
|
// frontend answers the declaration name with the uninhabited never[],
|
|
@@ -3175,7 +3597,9 @@ export function lowerVarDecl(lowerer, decl, isLet) {
|
|
|
3175
3597
|
// checked-dynamic instead (the `let x = {}` stance, and lower-modules'
|
|
3176
3598
|
// file-scope evolving-array rule): pushes ride the dyn array, typed
|
|
3177
3599
|
// exits dynCheck.
|
|
3178
|
-
if (
|
|
3600
|
+
if (returnedEmptyArrayType === null &&
|
|
3601
|
+
(type === null || type.kind === "array") &&
|
|
3602
|
+
isJsSourceFile(decl.getSourceFile())) {
|
|
3179
3603
|
let initExpr = decl.initializer;
|
|
3180
3604
|
while (ts.isParenthesizedExpression(initExpr))
|
|
3181
3605
|
initExpr = initExpr.expression;
|
|
@@ -3194,12 +3618,30 @@ export function lowerVarDecl(lowerer, decl, isLet) {
|
|
|
3194
3618
|
// and TypeScript's fresh-array probe idioms; an explicit annotation keeps
|
|
3195
3619
|
// the caller's checked slot contract.
|
|
3196
3620
|
let runtimeOptional = false;
|
|
3197
|
-
if (
|
|
3621
|
+
if (type !== null && init.type.kind === "union" &&
|
|
3622
|
+
lowerer.armTag(init.type.unionId, UNDEFINED_T) >= 0) {
|
|
3623
|
+
const present = lowerer.stripUndefinedArm(init.type);
|
|
3624
|
+
if (!typeEquals(present, type) && lowerer.widthLiftPlan(present, type) !== null) {
|
|
3625
|
+
const widened = type.kind === "union" ? lowerer.withUndefinedArmOf(type) : lowerer.withUndefinedArm(type);
|
|
3626
|
+
if (widened && lowerer.widthLiftPlan(init.type, widened) !== null) {
|
|
3627
|
+
type = widened;
|
|
3628
|
+
runtimeOptional = true;
|
|
3629
|
+
}
|
|
3630
|
+
}
|
|
3631
|
+
}
|
|
3632
|
+
if (type !== null && init.type.kind === "union" &&
|
|
3198
3633
|
lowerer.armTag(init.type.unionId, UNDEFINED_T) >= 0 &&
|
|
3199
3634
|
typeEquals(lowerer.stripUndefinedArm(init.type), type)) {
|
|
3200
3635
|
type = init.type;
|
|
3201
3636
|
runtimeOptional = true;
|
|
3202
3637
|
}
|
|
3638
|
+
if (type?.kind === "array" && init.type.kind === "array" &&
|
|
3639
|
+
init.type.elem.kind === "union" &&
|
|
3640
|
+
lowerer.armTag(init.type.elem.unionId, UNDEFINED_T) >= 0 &&
|
|
3641
|
+
typeEquals(lowerer.stripUndefinedArm(init.type.elem), type.elem)) {
|
|
3642
|
+
type = init.type;
|
|
3643
|
+
runtimeOptional = true;
|
|
3644
|
+
}
|
|
3203
3645
|
// A JS binding whose checker type spells a FUNCTION but whose VALUE is
|
|
3204
3646
|
// already checked-dynamic (`const cb = mustCall(fn)` — the helper's
|
|
3205
3647
|
// return rides its own box): keep the box. Extracting into the spelled
|
|
@@ -3219,14 +3661,15 @@ export function lowerVarDecl(lowerer, decl, isLet) {
|
|
|
3219
3661
|
// their tuple record likewise). Const only — an evolving-`any`
|
|
3220
3662
|
// `let` may be reassigned a different shape later; genuine `any`
|
|
3221
3663
|
// only — every other unmappable keeps its own diagnostic.
|
|
3222
|
-
|
|
3223
|
-
|
|
3664
|
+
const checkerAny = (lowerer.typeOf(decl.name).flags & ts.TypeFlags.Any) !== 0;
|
|
3665
|
+
if ((type === null || (type.kind === "dyn" && checkerAny)) && !isLet &&
|
|
3666
|
+
(checkerAny ||
|
|
3224
3667
|
(lowerer.checkerAnyArray(decl.name) && lowerer.isArrayValueType(init.type)) ||
|
|
3225
3668
|
// JS declarations carry no annotations: an unmappable inferred
|
|
3226
3669
|
// type (a union with a fence-folded arm — the typeof-'bigint'
|
|
3227
3670
|
// dual-mode ternary) adopts the initializer's static type, the
|
|
3228
3671
|
// same rule genuine `any` consts take.
|
|
3229
|
-
isJsSourceFile(decl.getSourceFile())) &&
|
|
3672
|
+
(type === null && isJsSourceFile(decl.getSourceFile()))) &&
|
|
3230
3673
|
init.type.kind !== "void" && init.type.kind !== "caught" && init.type.kind !== "jsval" &&
|
|
3231
3674
|
!isUnitType(init.type)) {
|
|
3232
3675
|
type = init.type;
|
|
@@ -3241,29 +3684,51 @@ export function lowerVarDecl(lowerer, decl, isLet) {
|
|
|
3241
3684
|
}
|
|
3242
3685
|
if (!type)
|
|
3243
3686
|
lowerer.badType(decl.name, lowerer.typeOf(decl.name));
|
|
3687
|
+
let settledType = type;
|
|
3688
|
+
const arithmeticType = decl.initializer ? lowerer.runtimeOptionalArithmeticTypes.get(decl.initializer) : undefined;
|
|
3689
|
+
const isStringArithmeticUnion = (t) => {
|
|
3690
|
+
if (t.kind !== "union")
|
|
3691
|
+
return false;
|
|
3692
|
+
const arms = lowerer.unions.get(t.unionId)?.arms;
|
|
3693
|
+
return !!arms && arms.length === 2 &&
|
|
3694
|
+
arms.some((a) => a.kind === "f64") && arms.some((a) => a.kind === "string");
|
|
3695
|
+
};
|
|
3696
|
+
const runtimeStringArithmetic = settledType.kind === "string" &&
|
|
3697
|
+
((arithmeticType && typeEquals(arithmeticType, init.type)) || isStringArithmeticUnion(init.type));
|
|
3698
|
+
if (runtimeStringArithmetic) {
|
|
3699
|
+
settledType = init.type;
|
|
3700
|
+
}
|
|
3701
|
+
const optionalSelfWriteType = isLet
|
|
3702
|
+
? npmStaticOptionalSelfWriteType(lowerer, decl, settledType)
|
|
3703
|
+
: null;
|
|
3704
|
+
settledType = lowerer.runtimeOptionalBindingType(decl.name, settledType);
|
|
3705
|
+
if (optionalSelfWriteType !== null)
|
|
3706
|
+
settledType = optionalSelfWriteType;
|
|
3707
|
+
if (returnedEmptyArrayType !== null)
|
|
3708
|
+
settledType = returnedEmptyArrayType;
|
|
3244
3709
|
// `const x: void = undefined` / `let y: undefined = undefined`: the
|
|
3245
3710
|
// unit-only union — the initializer's unit literal wraps into its arm
|
|
3246
3711
|
// like any optional completion. Non-literal void initializers (a
|
|
3247
3712
|
// void CALL's result) keep their fence at the coercion below.
|
|
3248
|
-
if (
|
|
3249
|
-
|
|
3713
|
+
if (settledType.kind === "void" && isUnitOnlyTsType(lowerer.typeOf(decl.name))) {
|
|
3714
|
+
settledType = unitOnlyUnion(lowerer.unions);
|
|
3250
3715
|
}
|
|
3251
|
-
if (
|
|
3716
|
+
if (settledType.kind === "void")
|
|
3252
3717
|
lowerer.badType(decl.name, lowerer.typeOf(decl.name));
|
|
3253
3718
|
// A PACKAGE promise stored in a local stays an island HANDLE — the
|
|
3254
3719
|
// engine's promise has no static promise value to coerce into. Awaits
|
|
3255
3720
|
// and .catch/.finally chains on the local bridge per use site (each
|
|
3256
3721
|
// bridge is an independent observer of the same settlement).
|
|
3257
|
-
if (init.type.kind === "jsval" &&
|
|
3258
|
-
|
|
3722
|
+
if (init.type.kind === "jsval" && settledType.kind === "promise")
|
|
3723
|
+
settledType = JSVAL;
|
|
3259
3724
|
// An island value under an `any[]`-declared LOCAL spelling stays an
|
|
3260
3725
|
// island HANDLE too (the runtime-world rule): the jsval-element-array
|
|
3261
3726
|
// exit is a CALL-boundary conversion (the loadPlugins param ABI) —
|
|
3262
3727
|
// converting a local would strand the engine's own prototypes
|
|
3263
3728
|
// (join/map/... on a native handle-element array), where the handle
|
|
3264
3729
|
// routes every use engine-side.
|
|
3265
|
-
if (init.type.kind === "jsval" &&
|
|
3266
|
-
|
|
3730
|
+
if (init.type.kind === "jsval" && settledType.kind === "array" && settledType.elem.kind === "jsval") {
|
|
3731
|
+
settledType = JSVAL;
|
|
3267
3732
|
}
|
|
3268
3733
|
// A STATIC array of island HANDLES under an unannotated declared type
|
|
3269
3734
|
// spelling evolved elements (`const kept = fns.filter(...)` over an
|
|
@@ -3276,8 +3741,8 @@ export function lowerVarDecl(lowerer, decl, isLet) {
|
|
|
3276
3741
|
// validated-boundary fence.
|
|
3277
3742
|
if (!decl.type &&
|
|
3278
3743
|
init.type.kind === "array" && init.type.elem.kind === "jsval" &&
|
|
3279
|
-
|
|
3280
|
-
|
|
3744
|
+
settledType.kind === "array" && settledType.elem.kind !== "jsval") {
|
|
3745
|
+
settledType = init.type;
|
|
3281
3746
|
}
|
|
3282
3747
|
// `const runner = options.runner || defaultRunner`: the checker types
|
|
3283
3748
|
// the || as the union/merge of two structurally-compatible function
|
|
@@ -3287,13 +3752,13 @@ export function lowerVarDecl(lowerer, decl, isLet) {
|
|
|
3287
3752
|
// when the parameters agree and the declared return either contains
|
|
3288
3753
|
// the init's return as a union arm or is the opaque spawnRes the
|
|
3289
3754
|
// adapter converts (nothing coerces INTO either declared form).
|
|
3290
|
-
if (
|
|
3755
|
+
if (settledType.kind === "func" && init.type.kind === "func" && !typeEquals(settledType, init.type)) {
|
|
3291
3756
|
const initFn = init.type;
|
|
3292
|
-
const paramsAgree =
|
|
3293
|
-
|
|
3294
|
-
const retUnionArm =
|
|
3295
|
-
if (paramsAgree && (retUnionArm || lowerer.spawnResFnAdapterPlan(
|
|
3296
|
-
|
|
3757
|
+
const paramsAgree = settledType.params.length === initFn.params.length &&
|
|
3758
|
+
settledType.params.every((p, i) => typeEquals(p, initFn.params[i]));
|
|
3759
|
+
const retUnionArm = settledType.ret.kind === "union" && lowerer.armTag(settledType.ret.unionId, initFn.ret) >= 0;
|
|
3760
|
+
if (paramsAgree && (retUnionArm || lowerer.spawnResFnAdapterPlan(settledType, initFn) !== null)) {
|
|
3761
|
+
settledType = init.type;
|
|
3297
3762
|
}
|
|
3298
3763
|
}
|
|
3299
3764
|
// The downstream twin: `const result = runner(cmd, args)` where the
|
|
@@ -3302,12 +3767,12 @@ export function lowerVarDecl(lowerer, decl, isLet) {
|
|
|
3302
3767
|
// can narrow (spawnRes has no discriminant). The VALUE is statically
|
|
3303
3768
|
// the record arm, so the local adopts it; flows into the union slot
|
|
3304
3769
|
// re-wrap via the ordinary arm coercion.
|
|
3305
|
-
if (
|
|
3306
|
-
const arms = lowerer.unions.get(
|
|
3770
|
+
if (settledType.kind === "union" && init.type.kind === "record") {
|
|
3771
|
+
const arms = lowerer.unions.get(settledType.unionId)?.arms ?? [];
|
|
3307
3772
|
if (arms.length === 2 &&
|
|
3308
3773
|
arms.some((a) => a.kind === "spawnRes") &&
|
|
3309
3774
|
arms.some((a) => typeEquals(a, init.type))) {
|
|
3310
|
-
|
|
3775
|
+
settledType = init.type;
|
|
3311
3776
|
}
|
|
3312
3777
|
}
|
|
3313
3778
|
// `const r: Repo = new MemRepo()` over an all-generic-method
|
|
@@ -3315,8 +3780,8 @@ export function lowerVarDecl(lowerer, decl, isLet) {
|
|
|
3315
3780
|
// (the record shape maps empty and the width copy would drop the
|
|
3316
3781
|
// class the generic-method calls monomorphize against) — see
|
|
3317
3782
|
// genericIfaceBindingKeepsClass.
|
|
3318
|
-
if (
|
|
3319
|
-
|
|
3783
|
+
if (settledType.kind === "record" && init.type.kind === "object" && genericIfaceBindingKeepsClass(lowerer, decl, settledType)) {
|
|
3784
|
+
settledType = init.type;
|
|
3320
3785
|
}
|
|
3321
3786
|
// A TDZ box minted DURING this very initializer (a callback inside it
|
|
3322
3787
|
// captured this const — predeclareForwardCapture's current-statement
|
|
@@ -3331,8 +3796,11 @@ export function lowerVarDecl(lowerer, decl, isLet) {
|
|
|
3331
3796
|
}
|
|
3332
3797
|
// Slot coercion: `const r: A | B = bValue;` wraps implicitly; width
|
|
3333
3798
|
// subtyping (`const p: {a: number} = wider;`) is rejected, not coerced.
|
|
3334
|
-
init = lowerer.coerceInto(decl.initializer, init,
|
|
3335
|
-
const local = lowerer.declareLocal(decl.name, decl.name.text,
|
|
3799
|
+
init = lowerer.coerceInto(decl.initializer, init, settledType);
|
|
3800
|
+
const local = lowerer.declareLocal(decl.name, decl.name.text, settledType, isLet);
|
|
3801
|
+
if (runtimeStringArithmetic && isStringArithmeticUnion(init.type)) {
|
|
3802
|
+
lowerer.runtimeOptionalArithmeticLocals.add(lowerer.runtimeOptionalRootOf(local));
|
|
3803
|
+
}
|
|
3336
3804
|
if (runtimeOptional) {
|
|
3337
3805
|
const runtimeOptionalRoot = lowerer.runtimeOptionalRootOf(local);
|
|
3338
3806
|
lowerer.runtimeOptionalLocals.add(runtimeOptionalRoot);
|
|
@@ -3340,6 +3808,40 @@ export function lowerVarDecl(lowerer, decl, isLet) {
|
|
|
3340
3808
|
}
|
|
3341
3809
|
return { kind: "varDecl", localId: local.id, init, loc: locOf(decl) };
|
|
3342
3810
|
}
|
|
3811
|
+
function lowerVariableInitializer(lowerer, node) {
|
|
3812
|
+
let value = node;
|
|
3813
|
+
while (ts.isParenthesizedExpression(value) ||
|
|
3814
|
+
ts.isAsExpression(value) ||
|
|
3815
|
+
ts.isTypeAssertion(value) ||
|
|
3816
|
+
ts.isSatisfiesExpression(value)) {
|
|
3817
|
+
value = value.expression;
|
|
3818
|
+
}
|
|
3819
|
+
if (!ts.isObjectLiteralExpression(value))
|
|
3820
|
+
return lowerer.lowerExpr(node);
|
|
3821
|
+
const previous = new Map();
|
|
3822
|
+
for (const property of value.properties) {
|
|
3823
|
+
if (!ts.isPropertyAssignment(property) || !ts.isComputedPropertyName(property.name))
|
|
3824
|
+
continue;
|
|
3825
|
+
const keyNode = property.name.expression;
|
|
3826
|
+
const optional = lowerer.runtimeOptionalIdentifierValue(keyNode)?.value ??
|
|
3827
|
+
lowerAbsenceProbe(lowerer, keyNode);
|
|
3828
|
+
if (!optional || lowerer.runtimeOptionalWidening(optional.type, STRING) === null)
|
|
3829
|
+
continue;
|
|
3830
|
+
previous.set(keyNode, lowerer.chainRecvByNode.get(keyNode));
|
|
3831
|
+
lowerer.chainRecvByNode.set(keyNode, lowerer.ensureString(optional, keyNode));
|
|
3832
|
+
}
|
|
3833
|
+
try {
|
|
3834
|
+
return lowerer.lowerExpr(node);
|
|
3835
|
+
}
|
|
3836
|
+
finally {
|
|
3837
|
+
for (const [keyNode, prior] of previous) {
|
|
3838
|
+
if (prior)
|
|
3839
|
+
lowerer.chainRecvByNode.set(keyNode, prior);
|
|
3840
|
+
else
|
|
3841
|
+
lowerer.chainRecvByNode.delete(keyNode);
|
|
3842
|
+
}
|
|
3843
|
+
}
|
|
3844
|
+
}
|
|
3343
3845
|
function immediatelyGuardedAbsenceProbe(lowerer, decl) {
|
|
3344
3846
|
if (decl.type || !ts.isIdentifier(decl.name) || !decl.initializer)
|
|
3345
3847
|
return false;
|
|
@@ -3397,9 +3899,8 @@ export function lowerSwitch(lowerer, stmt) {
|
|
|
3397
3899
|
const labels = lowerer.takeLabels();
|
|
3398
3900
|
const disc = lowerer.lowerExpr(stmt.expression);
|
|
3399
3901
|
const dk = disc.type.kind;
|
|
3400
|
-
if (dk === "dyn")
|
|
3401
|
-
lowerer
|
|
3402
|
-
}
|
|
3902
|
+
if (dk === "dyn")
|
|
3903
|
+
return lowerDynSwitch(lowerer, stmt, disc);
|
|
3403
3904
|
if (dk === "union")
|
|
3404
3905
|
return lowerUnionSwitch(lowerer, stmt, disc);
|
|
3405
3906
|
if (dk !== "f64" && dk !== "string" && dk !== "bool") {
|
|
@@ -3432,8 +3933,8 @@ export function lowerSwitch(lowerer, stmt) {
|
|
|
3432
3933
|
* exactly `disc === test`), because the backend switch compares plain
|
|
3433
3934
|
* primitives only. The desugar is JS-exact for the shape it accepts and
|
|
3434
3935
|
* fences everything it cannot reproduce:
|
|
3435
|
-
* - the discriminant
|
|
3436
|
-
*
|
|
3936
|
+
* - the discriminant evaluates exactly once; effectful values stabilize
|
|
3937
|
+
* in a hidden local that every generated equality test reuses;
|
|
3437
3938
|
* - tests evaluate lazily in source order (the chain's short-circuit IS
|
|
3438
3939
|
* the switch's test order — grouped empty cases `case a: case b:` OR
|
|
3439
3940
|
* their tests, still in order);
|
|
@@ -3448,12 +3949,51 @@ export function lowerSwitch(lowerer, stmt) {
|
|
|
3448
3949
|
* is last).
|
|
3449
3950
|
* Case bodies share ONE lexical scope, exactly like the real switch. */
|
|
3450
3951
|
function lowerUnionSwitch(lowerer, stmt, disc) {
|
|
3451
|
-
const loc = locOf(stmt);
|
|
3452
3952
|
if (disc.type.kind !== "union")
|
|
3453
3953
|
throw new InternalCompilerError("lowerer bug: non-union disc");
|
|
3454
3954
|
const unionType = disc.type;
|
|
3455
|
-
if (!
|
|
3456
|
-
lowerer.unsupported("SC1090", stmt.expression, "switch on
|
|
3955
|
+
if (!lowerer.eqComparableUnion(unionType.unionId)) {
|
|
3956
|
+
lowerer.unsupported("SC1090", stmt.expression, "switch on a union whose arms do not have static equality");
|
|
3957
|
+
}
|
|
3958
|
+
return lowerBranchSwitch(lowerer, stmt, disc, (stableDisc, test, expression) => {
|
|
3959
|
+
const unitTest = test.kind === "unitLit"
|
|
3960
|
+
? lowerer.lowerUnitComparison(stableDisc, test, false, locOf(expression))
|
|
3961
|
+
: null;
|
|
3962
|
+
return unitTest ?? {
|
|
3963
|
+
kind: "unionEq",
|
|
3964
|
+
unionId: unionType.unionId,
|
|
3965
|
+
negated: false,
|
|
3966
|
+
sameValue: false,
|
|
3967
|
+
left: stableDisc,
|
|
3968
|
+
right: lowerer.coerceInto(expression, test, unionType),
|
|
3969
|
+
type: BOOL,
|
|
3970
|
+
loc: locOf(expression),
|
|
3971
|
+
};
|
|
3972
|
+
});
|
|
3973
|
+
}
|
|
3974
|
+
/** A checked-dynamic switch whose tests have native strict-equality
|
|
3975
|
+
* answers. Scalar tests use dynScalarEq; null/undefined use dyn kind tests.
|
|
3976
|
+
* Other case values retain the existing unknown-switch fence. */
|
|
3977
|
+
function lowerDynSwitch(lowerer, stmt, disc) {
|
|
3978
|
+
return lowerBranchSwitch(lowerer, stmt, disc, (stableDisc, test, expression) => {
|
|
3979
|
+
if (test.kind === "unitLit") {
|
|
3980
|
+
return { kind: "dynTest", test: test.unit, value: stableDisc, type: BOOL, loc: locOf(expression) };
|
|
3981
|
+
}
|
|
3982
|
+
if (test.type.kind !== "string" && test.type.kind !== "f64" &&
|
|
3983
|
+
test.type.kind !== "bool" && test.type.kind !== "dyn") {
|
|
3984
|
+
lowerer.unsupported("SC1100", expression, "switch case values that cannot compare strictly with 'unknown'");
|
|
3985
|
+
}
|
|
3986
|
+
return { kind: "dynScalarEq", left: stableDisc, right: test, type: BOOL, loc: locOf(expression) };
|
|
3987
|
+
});
|
|
3988
|
+
}
|
|
3989
|
+
function lowerBranchSwitch(lowerer, stmt, disc, compare) {
|
|
3990
|
+
const loc = locOf(stmt);
|
|
3991
|
+
const prefix = [];
|
|
3992
|
+
let stableDisc = disc;
|
|
3993
|
+
if (!isSafeToRepeat(disc)) {
|
|
3994
|
+
const temp = lowerer.declareHiddenLocal("%switch", disc.type);
|
|
3995
|
+
prefix.push({ kind: "varDecl", localId: temp.id, init: disc, loc });
|
|
3996
|
+
stableDisc = { kind: "varRef", localId: temp.id, type: disc.type, loc };
|
|
3457
3997
|
}
|
|
3458
3998
|
const clauses = stmt.caseBlock.clauses;
|
|
3459
3999
|
// An unlabeled break at a clause's END exits the switch — the chain's
|
|
@@ -3480,13 +4020,19 @@ function lowerUnionSwitch(lowerer, stmt, disc) {
|
|
|
3480
4020
|
}
|
|
3481
4021
|
}
|
|
3482
4022
|
}
|
|
4023
|
+
const statementExits = (last) => {
|
|
4024
|
+
if (!last)
|
|
4025
|
+
return false;
|
|
4026
|
+
if ((ts.isBreakStatement(last) && !last.label) ||
|
|
4027
|
+
ts.isReturnStatement(last) ||
|
|
4028
|
+
ts.isThrowStatement(last) ||
|
|
4029
|
+
ts.isContinueStatement(last))
|
|
4030
|
+
return true;
|
|
4031
|
+
return ts.isBlock(last) && statementExits(last.statements[last.statements.length - 1]);
|
|
4032
|
+
};
|
|
3483
4033
|
const exits = (clause) => {
|
|
3484
4034
|
const last = clause.statements[clause.statements.length - 1];
|
|
3485
|
-
return (
|
|
3486
|
-
((ts.isBreakStatement(last) && !last.label) ||
|
|
3487
|
-
ts.isReturnStatement(last) ||
|
|
3488
|
-
ts.isThrowStatement(last) ||
|
|
3489
|
-
ts.isContinueStatement(last)));
|
|
4035
|
+
return statementExits(last);
|
|
3490
4036
|
};
|
|
3491
4037
|
// The whole case-body sequence is ONE lexical scope, like the real
|
|
3492
4038
|
// switch lowering.
|
|
@@ -3506,7 +4052,7 @@ function lowerUnionSwitch(lowerer, stmt, disc) {
|
|
|
3506
4052
|
// final else, so matching them changes nothing when pure).
|
|
3507
4053
|
if (test.kind !== "strLit" && test.kind !== "numLit" &&
|
|
3508
4054
|
test.kind !== "boolLit" && test.kind !== "unitLit" &&
|
|
3509
|
-
!
|
|
4055
|
+
!isSafeToRepeat(test)) {
|
|
3510
4056
|
lowerer.unsupported("SC1090", clause.expression, "effectful case tests in a union-typed switch (bind the test value to a const first)");
|
|
3511
4057
|
}
|
|
3512
4058
|
// A unit-literal test takes the unit-comparison lowering: a tag
|
|
@@ -3514,19 +4060,7 @@ function lowerUnionSwitch(lowerer, stmt, disc) {
|
|
|
3514
4060
|
// lacks it (`case null:` on a `number | undefined` — legal TS,
|
|
3515
4061
|
// never matches; coercing the literal into the union would hit
|
|
3516
4062
|
// the stranded-arm trap and throw where JS just skips the case).
|
|
3517
|
-
|
|
3518
|
-
? lowerer.lowerUnitComparison(disc, test, false, locOf(clause.expression))
|
|
3519
|
-
: null;
|
|
3520
|
-
pendingTests.push(unitTest ?? {
|
|
3521
|
-
kind: "unionEq",
|
|
3522
|
-
unionId: unionType.unionId,
|
|
3523
|
-
negated: false,
|
|
3524
|
-
sameValue: false,
|
|
3525
|
-
left: disc,
|
|
3526
|
-
right: lowerer.coerceInto(clause.expression, test, unionType),
|
|
3527
|
-
type: BOOL,
|
|
3528
|
-
loc: locOf(clause.expression),
|
|
3529
|
-
});
|
|
4063
|
+
pendingTests.push(compare(stableDisc, test, clause.expression));
|
|
3530
4064
|
}
|
|
3531
4065
|
const isDefault = ts.isDefaultClause(clause);
|
|
3532
4066
|
if (clause.statements.length === 0 && !isDefault && i < clauses.length - 1) {
|
|
@@ -3563,13 +4097,13 @@ function lowerUnionSwitch(lowerer, stmt, disc) {
|
|
|
3563
4097
|
}
|
|
3564
4098
|
chain = [{ kind: "if", cond, then: g.body, else_: chain.length > 0 ? chain : null, loc }];
|
|
3565
4099
|
}
|
|
3566
|
-
return { kind: "block", body: chain, loc };
|
|
4100
|
+
return { kind: "block", body: [...prefix, ...chain], loc };
|
|
3567
4101
|
}
|
|
3568
4102
|
finally {
|
|
3569
4103
|
lowerer.scopes.pop();
|
|
3570
4104
|
}
|
|
3571
4105
|
}
|
|
3572
|
-
/** try/catch/finally
|
|
4106
|
+
/** try/catch/finally:
|
|
3573
4107
|
* - `catch { }` (bindingless) discards the thrown value on entry.
|
|
3574
4108
|
* - `catch (e)` binds it as a CAUGHT local — a snapshot of the exception
|
|
3575
4109
|
* cell, deliberately narrower than dyn: the supported uses are the
|
|
@@ -3580,23 +4114,14 @@ function lowerUnionSwitch(lowerer, stmt, disc) {
|
|
|
3580
4114
|
* assignment are fenced (SC1063 and friends); destructuring patterns
|
|
3581
4115
|
* are SC1062. The `: any` / `: unknown` annotations tsc admits both
|
|
3582
4116
|
* lower the same way (tsc narrows either through the tests).
|
|
3583
|
-
* - `finally` runs on normal completion,
|
|
3584
|
-
*
|
|
3585
|
-
*
|
|
3586
|
-
*
|
|
3587
|
-
* break/continue crossing a try-with-finally stay rejected, as does
|
|
3588
|
-
* any jump out of the finally body itself (it would replace a pending
|
|
3589
|
-
* completion — rejectJumpCrossingFinally). Plain try/catch places no
|
|
3590
|
-
* restriction on jumps.
|
|
4117
|
+
* - `finally` runs on normal completion, exception paths, and every
|
|
4118
|
+
* abrupt completion that crosses it. Return values are snapshotted
|
|
4119
|
+
* before cleanup; an abrupt completion inside the finally replaces the
|
|
4120
|
+
* pending one, matching JavaScript completion-record semantics.
|
|
3591
4121
|
* Each block is its own lexical scope (the binding lives in a scope
|
|
3592
4122
|
* WRAPPING the catch block, like the spec's catch environment). */
|
|
3593
4123
|
export function lowerTry(lowerer, stmt) {
|
|
3594
|
-
const
|
|
3595
|
-
// try/catch bodies are jump-fenced only when a finally guards them.
|
|
3596
|
-
const lowerGuarded = (block) => hasFinally
|
|
3597
|
-
? lowerer.inCtl("tryFinally", () => lowerer.lowerScopedBlock(block))
|
|
3598
|
-
: lowerer.lowerScopedBlock(block);
|
|
3599
|
-
const tryBody = lowerGuarded(stmt.tryBlock);
|
|
4124
|
+
const tryBody = lowerer.lowerScopedBlock(stmt.tryBlock);
|
|
3600
4125
|
let catchBody = null;
|
|
3601
4126
|
let catchLocalId = null;
|
|
3602
4127
|
if (stmt.catchClause) {
|
|
@@ -3609,19 +4134,17 @@ export function lowerTry(lowerer, stmt) {
|
|
|
3609
4134
|
try {
|
|
3610
4135
|
const local = lowerer.declareLocal(vd.name, vd.name.text, CAUGHT, false);
|
|
3611
4136
|
catchLocalId = local.id;
|
|
3612
|
-
catchBody =
|
|
4137
|
+
catchBody = lowerer.lowerScopedBlock(stmt.catchClause.block);
|
|
3613
4138
|
}
|
|
3614
4139
|
finally {
|
|
3615
4140
|
lowerer.scopes.pop();
|
|
3616
4141
|
}
|
|
3617
4142
|
}
|
|
3618
4143
|
else {
|
|
3619
|
-
catchBody =
|
|
4144
|
+
catchBody = lowerer.lowerScopedBlock(stmt.catchClause.block);
|
|
3620
4145
|
}
|
|
3621
4146
|
}
|
|
3622
|
-
const finallyBody = stmt.finallyBlock
|
|
3623
|
-
? lowerer.inCtl("finallyBlock", () => lowerer.lowerScopedBlock(stmt.finallyBlock))
|
|
3624
|
-
: null;
|
|
4147
|
+
const finallyBody = stmt.finallyBlock ? lowerer.lowerScopedBlock(stmt.finallyBlock) : null;
|
|
3625
4148
|
return {
|
|
3626
4149
|
kind: "tryCatch",
|
|
3627
4150
|
tryBody,
|
|
@@ -3645,12 +4168,16 @@ function lowerDeleteStatement(lowerer, expr) {
|
|
|
3645
4168
|
if (!ts.isElementAccessExpression(target) && !ts.isPropertyAccessExpression(target)) {
|
|
3646
4169
|
lowerer.unsupported("SC1090", expr, "'delete' of non-property expressions");
|
|
3647
4170
|
}
|
|
4171
|
+
fenceNodeModuleMutation(lowerer, target, "delete");
|
|
3648
4172
|
const lowerKey = () => {
|
|
3649
4173
|
if (ts.isPropertyAccessExpression(target)) {
|
|
3650
4174
|
return { kind: "strLit", value: target.name.text, type: STRING, loc: locOf(target.name) };
|
|
3651
4175
|
}
|
|
3652
4176
|
const keyNode = target.argumentExpression;
|
|
3653
|
-
|
|
4177
|
+
let key = lowerer.lowerExpr(keyNode);
|
|
4178
|
+
if (lowerer.runtimeOptionalWidening(key.type, STRING) !== null) {
|
|
4179
|
+
key = lowerer.ensureString(key, keyNode);
|
|
4180
|
+
}
|
|
3654
4181
|
if (key.type.kind !== "string") {
|
|
3655
4182
|
lowerer.unsupported("SC1090", keyNode, `'delete' with '${lowerer.fmt(key.type)}' keys (index-signature and env keys are strings)`);
|
|
3656
4183
|
}
|
|
@@ -4135,6 +4662,22 @@ export function lowerExprStatement(lowerer, expr) {
|
|
|
4135
4662
|
if (ts.isElementAccessExpression(expr.left))
|
|
4136
4663
|
return lowerer.lowerElementWrite(expr);
|
|
4137
4664
|
if (ts.isPropertyAccessExpression(expr.left)) {
|
|
4665
|
+
fenceNodeModuleMutation(lowerer, expr.left, "assignment");
|
|
4666
|
+
// Native arrays have a writable length property. The runtime grows
|
|
4667
|
+
// by creating holes and truncates with RC-aware removal; keep this
|
|
4668
|
+
// as a statement operation so invalid lengths can use the normal
|
|
4669
|
+
// pending-exception path.
|
|
4670
|
+
if (!expr.left.questionDotToken && expr.left.name.text === "length") {
|
|
4671
|
+
const arrayType = lowerer.mapTypeOf(lowerer.typeOf(expr.left.expression));
|
|
4672
|
+
if (arrayType?.kind === "array") {
|
|
4673
|
+
const arr = lowerer.lowerExpr(expr.left.expression);
|
|
4674
|
+
if (arr.type.kind !== "array") {
|
|
4675
|
+
lowerer.unsupported("SC1090", expr.left.expression, "writing the length of an array-typed value that remains an island handle");
|
|
4676
|
+
}
|
|
4677
|
+
const length = lowerer.lowerExprExpecting(expr.right, F64);
|
|
4678
|
+
return { kind: "arraySetLength", arr, length, loc: locOf(expr) };
|
|
4679
|
+
}
|
|
4680
|
+
}
|
|
4138
4681
|
// `process.env.NAME = v` — setenv(3): later env reads and spawned
|
|
4139
4682
|
// children observe the write, exactly Node. Values are strings
|
|
4140
4683
|
// (Node stringifies everything; a non-string RHS fences instead
|
|
@@ -4272,10 +4815,10 @@ export function lowerExprStatement(lowerer, expr) {
|
|
|
4272
4815
|
// chain): the dyn keyed write — the value converts INTO dyn
|
|
4273
4816
|
// (closures box), the runtime sets the member on OBJ receivers
|
|
4274
4817
|
// and throws Node's TypeErrors on the rest. The receiver is
|
|
4275
|
-
// PROBED (
|
|
4818
|
+
// PROBED (tryLowerExpression) and claimed exactly when it lowers to
|
|
4276
4819
|
// dyn; typed receivers keep their own lowerings and fences.
|
|
4277
4820
|
if (!expr.left.questionDotToken) {
|
|
4278
|
-
const recv =
|
|
4821
|
+
const recv = tryLowerExpression(lowerer, expr.left.expression);
|
|
4279
4822
|
if (recv && recv.type.kind === "dyn") {
|
|
4280
4823
|
const loc = locOf(expr);
|
|
4281
4824
|
const key = { kind: "strLit", value: expr.left.name.text, type: STRING, loc: locOf(expr.left.name) };
|
|
@@ -4571,6 +5114,7 @@ function lowerCompoundToTarget(lowerer, expr, compound, target) {
|
|
|
4571
5114
|
const loc = locOf(expr);
|
|
4572
5115
|
const read = { kind: "varRef", localId: target.id, type: target.type, loc: locOf(expr.left) };
|
|
4573
5116
|
const rhs = lowerer.lowerExpr(expr.right);
|
|
5117
|
+
const numericRhs = lowerOptionalNumber(lowerer, rhs, loc);
|
|
4574
5118
|
let value;
|
|
4575
5119
|
if (target.type.kind === "jsval" || rhs.type.kind === "jsval") {
|
|
4576
5120
|
const JS_COMPOUND = { "+": "add", "-": "sub", "*": "mul", "/": "div", "%": "mod", "**": "pow" };
|
|
@@ -4587,8 +5131,8 @@ function lowerCompoundToTarget(lowerer, expr, compound, target) {
|
|
|
4587
5131
|
else if (compound === "+" && target.type.kind === "string") {
|
|
4588
5132
|
value = { kind: "strConcat", left: read, right: lowerer.ensureString(rhs, expr.right), type: STRING, loc };
|
|
4589
5133
|
}
|
|
4590
|
-
else if (target.type.kind === "f64" &&
|
|
4591
|
-
value = { kind: "bin", op: compound, left: read, right:
|
|
5134
|
+
else if (target.type.kind === "f64" && numericRhs.type.kind === "f64") {
|
|
5135
|
+
value = { kind: "bin", op: compound, left: read, right: numericRhs, type: F64, loc };
|
|
4592
5136
|
}
|
|
4593
5137
|
else if ((target.type.kind === "dyn" || rhs.type.kind === "dyn") &&
|
|
4594
5138
|
(target.type.kind === "dyn" || target.type.kind === "f64") &&
|
|
@@ -4843,6 +5387,16 @@ export function lowerDestructuringAssignParts(lowerer, target, rhs, loc) {
|
|
|
4843
5387
|
* source-spelling TypeError machinery cannot apply), `blame` the node
|
|
4844
5388
|
* fences point at. */
|
|
4845
5389
|
function destructuringAssignInto(lowerer, target, init, rhs, blame, loc) {
|
|
5390
|
+
if (init.type.kind === "union") {
|
|
5391
|
+
const source = lowerer.declareHiddenLocal("%destrSource", init.type);
|
|
5392
|
+
const out = [{ kind: "varDecl", localId: source.id, init, loc }];
|
|
5393
|
+
const value = varRef(source.id, init.type, loc);
|
|
5394
|
+
const present = patternSourceValue(lowerer, value, ts.isArrayLiteralExpression(target), out, loc);
|
|
5395
|
+
if (!typeEquals(present.type, init.type)) {
|
|
5396
|
+
const sub = destructuringAssignInto(lowerer, target, present, rhs, blame, loc);
|
|
5397
|
+
return { stmts: [...out, ...sub.stmts], value };
|
|
5398
|
+
}
|
|
5399
|
+
}
|
|
4846
5400
|
// IR-string sources in JS files split two ways the carrier type
|
|
4847
5401
|
// cannot: a STDLIB GLOBAL (`({ subtle } = globalThis.crypto)`) — the
|
|
4848
5402
|
// declaration path binds member identity tokens into FRESH bindings,
|
|
@@ -5316,14 +5870,10 @@ function lowerAssignTargetInto(lowerer, out, targetNode, blame, dflt, valueOf) {
|
|
|
5316
5870
|
}
|
|
5317
5871
|
const idx = lowerer.declareHiddenLocal("%dtIdx", F64);
|
|
5318
5872
|
out.push({ kind: "varDecl", localId: idx.id, init: index, loc });
|
|
5319
|
-
const value =
|
|
5320
|
-
|
|
5321
|
-
|
|
5322
|
-
|
|
5323
|
-
index: { kind: "varRef", localId: idx.id, type: F64, loc },
|
|
5324
|
-
value,
|
|
5325
|
-
loc,
|
|
5326
|
-
});
|
|
5873
|
+
const value = valueOf(recvT.elem);
|
|
5874
|
+
const stored = lowerer.declareHiddenLocal("%dtValue", value.type);
|
|
5875
|
+
out.push({ kind: "varDecl", localId: stored.id, init: value, loc });
|
|
5876
|
+
out.push(arrayValueStore(lowerer, varRef(recv.id, arr.type, loc), varRef(idx.id, F64, loc), varRef(stored.id, value.type, loc), recvT.elem, loc));
|
|
5327
5877
|
return;
|
|
5328
5878
|
}
|
|
5329
5879
|
}
|
|
@@ -5499,11 +6049,9 @@ function lowerArrayAssignInto(lowerer, out, target, tmpRef, blame, loc) {
|
|
|
5499
6049
|
loc: locOf(at),
|
|
5500
6050
|
};
|
|
5501
6051
|
}
|
|
5502
|
-
//
|
|
5503
|
-
// array traps (the arrayGet discipline, divergence 4's policy; JS
|
|
5504
|
-
// would answer undefined).
|
|
6052
|
+
// Assignment patterns consume the same values as binding patterns.
|
|
5505
6053
|
const elemType = srcType.elem;
|
|
5506
|
-
return
|
|
6054
|
+
return arrayValueRead(lowerer, tmpRef(), numLit(i, locOf(at)), elemType, locOf(at));
|
|
5507
6055
|
};
|
|
5508
6056
|
elements.forEach((el, i) => {
|
|
5509
6057
|
if (ts.isOmittedExpression(el))
|
|
@@ -5556,7 +6104,7 @@ function lowerArrayAssignInto(lowerer, out, target, tmpRef, blame, loc) {
|
|
|
5556
6104
|
}
|
|
5557
6105
|
return isTuple
|
|
5558
6106
|
? tupleTailValue(lowerer, el, tmpRef, srcType, shape, i, targetT)
|
|
5559
|
-
:
|
|
6107
|
+
: arrayRestValue(tmpRef(), i, srcType, locOf(el));
|
|
5560
6108
|
}
|
|
5561
6109
|
if (isTuple && defaultNode !== null && targetT !== null && !shape.fields.some((f) => f.name === String(i))) {
|
|
5562
6110
|
// Past the tuple's end the read is always undefined — the default
|
|
@@ -5626,29 +6174,27 @@ export function lowerForOf(lowerer, stmt) {
|
|
|
5626
6174
|
// desugars drop them — a labeled jump naming those loops fences at the
|
|
5627
6175
|
// jump site (no label point exists in their desugared shape).
|
|
5628
6176
|
const labels = lowerer.takeLabels();
|
|
5629
|
-
// `for await` is ASYNC ITERATION, not the shipped async/await.
|
|
5630
|
-
// async
|
|
5631
|
-
//
|
|
5632
|
-
// named specifically (the bare SC1070 text would claim async/await
|
|
5633
|
-
// itself is missing).
|
|
6177
|
+
// `for await` is ASYNC ITERATION, not the shipped async/await. Typed
|
|
6178
|
+
// async generators use the generic request protocol; process.stdin
|
|
6179
|
+
// and readable streams retain their dedicated next-chunk paths.
|
|
5634
6180
|
if (stmt.awaitModifier) {
|
|
5635
|
-
if (ts.isPropertyAccessExpression(stmt.expression) &&
|
|
5636
|
-
lowerer.stdlibGlobalMember(stmt.expression, "process") === "stdin") {
|
|
5637
|
-
return lowerForAwaitStdin(lowerer, stmt);
|
|
5638
|
-
}
|
|
5639
|
-
// Readable streams (the readableAsyncIterator surface): the mapped
|
|
5640
|
-
// receiver class roots at a readable-sided stream class.
|
|
5641
6181
|
{
|
|
5642
|
-
const
|
|
5643
|
-
if (
|
|
5644
|
-
const
|
|
5645
|
-
|
|
5646
|
-
|
|
5647
|
-
return lowerForAwaitReadable(lowerer, stmt, recvT);
|
|
6182
|
+
const genT = lowerer.mapTypeOf(lowerer.typeOf(stmt.expression));
|
|
6183
|
+
if (genT?.kind === "generator" && genT.async) {
|
|
6184
|
+
const iterable = lowerer.lowerExpr(stmt.expression);
|
|
6185
|
+
if (iterable.type.kind === "generator" && iterable.type.async) {
|
|
6186
|
+
return lowerForAwaitGenerator(lowerer, stmt, iterable, labels);
|
|
5648
6187
|
}
|
|
5649
6188
|
}
|
|
5650
6189
|
}
|
|
5651
|
-
|
|
6190
|
+
if (ts.isPropertyAccessExpression(stmt.expression) &&
|
|
6191
|
+
lowerer.stdlibGlobalMember(stmt.expression, "process") === "stdin") {
|
|
6192
|
+
return lowerForAwaitStdin(lowerer, stmt);
|
|
6193
|
+
}
|
|
6194
|
+
const builtin = lowerForAwaitBuiltin(lowerer, stmt);
|
|
6195
|
+
if (builtin)
|
|
6196
|
+
return builtin;
|
|
6197
|
+
lowerer.unsupported("SC1070", stmt, "'for await' over this value (supported: typed async generators, represented class async iterators, process.stdin, and Node/Web readable streams)");
|
|
5652
6198
|
}
|
|
5653
6199
|
lowerer.fenceStaticHeadersIteration(stmt.expression);
|
|
5654
6200
|
// A stored numeric value iterator declared in this function keeps its
|
|
@@ -5777,6 +6323,19 @@ export function lowerForOf(lowerer, stmt) {
|
|
|
5777
6323
|
}
|
|
5778
6324
|
iterSrc ??= stmt.expression;
|
|
5779
6325
|
let iterable = lowerer.lowerExpr(iterSrc);
|
|
6326
|
+
// A nested loop receiver can be an OOB-safe array read from the outer
|
|
6327
|
+
// loop (`for (const row of grid) for (const cell of row)`). The checker
|
|
6328
|
+
// sees the inner value as an array, while its runtime slot is array |
|
|
6329
|
+
// undefined; validate the receiver before selecting the array iterator.
|
|
6330
|
+
const checkerIterable = lowerer.mapTypeOf(lowerer.typeOf(iterSrc));
|
|
6331
|
+
if (iterable.type.kind === "union" && lowerer.armTag(iterable.type.unionId, UNDEFINED_T) >= 0 && checkerIterable?.kind === "array") {
|
|
6332
|
+
const present = lowerer.stripUndefinedArm(iterable.type);
|
|
6333
|
+
const helper = present.kind === "array"
|
|
6334
|
+
? lowerer.narrowedArmHelper(iterable.type.unionId, present, locOf(iterSrc))
|
|
6335
|
+
: null;
|
|
6336
|
+
if (helper)
|
|
6337
|
+
iterable = { kind: "call", callee: helper, args: [iterable], type: present, loc: locOf(iterSrc) };
|
|
6338
|
+
}
|
|
5780
6339
|
if (iterable.type.kind === "bytes") {
|
|
5781
6340
|
return lowerForOfBytes(lowerer, stmt, iterable, labels);
|
|
5782
6341
|
}
|
|
@@ -5854,7 +6413,7 @@ export function lowerForOf(lowerer, stmt) {
|
|
|
5854
6413
|
elemT = { kind: "union", unionId: lowerer.unions.intern(arms) };
|
|
5855
6414
|
}
|
|
5856
6415
|
}
|
|
5857
|
-
if (elemT !== null &&
|
|
6416
|
+
if (elemT !== null && isSafeToRepeat(iterable)) {
|
|
5858
6417
|
const loc = locOf(stmt.expression);
|
|
5859
6418
|
const shapeId = iterable.type.shapeId;
|
|
5860
6419
|
const snapshotElem = elemT;
|
|
@@ -5902,6 +6461,29 @@ export function lowerForOf(lowerer, stmt) {
|
|
|
5902
6461
|
lowerer.badType(stmt.expression, lowerer.typeOf(stmt.expression));
|
|
5903
6462
|
}
|
|
5904
6463
|
}
|
|
6464
|
+
const elemValueT = arrayValueType(lowerer, iterable.type.elem);
|
|
6465
|
+
// Native arrays retain their payload ABI; the loop binds the value yielded
|
|
6466
|
+
// by Get, which is undefined for both holes and present undefined slots.
|
|
6467
|
+
const sourceT = iterable.type;
|
|
6468
|
+
const source = lowerer.declareHiddenLocal("%arrayIterator", sourceT);
|
|
6469
|
+
const cursor = lowerer.declareHiddenLocal("%arrayCursor", F64);
|
|
6470
|
+
cursor.mutable = true;
|
|
6471
|
+
const forValues = (localId, body) => ({
|
|
6472
|
+
kind: "block",
|
|
6473
|
+
body: [
|
|
6474
|
+
{ kind: "varDecl", localId: source.id, init: iterable, loc: locOf(stmt) },
|
|
6475
|
+
{
|
|
6476
|
+
kind: "for",
|
|
6477
|
+
init: { kind: "varDecl", localId: cursor.id, init: numLit(0, locOf(stmt)), loc: locOf(stmt) },
|
|
6478
|
+
cond: { kind: "bin", op: "<", left: varRef(cursor.id, F64, locOf(stmt)), right: { kind: "arrIntrinsic", method: "length", receiver: varRef(source.id, sourceT, locOf(stmt)), args: [], type: F64, loc: locOf(stmt) }, type: BOOL, loc: locOf(stmt) },
|
|
6479
|
+
update: { kind: "assign", localId: cursor.id, value: { kind: "bin", op: "+", left: varRef(cursor.id, F64, locOf(stmt)), right: numLit(1, locOf(stmt)), type: F64, loc: locOf(stmt) }, loc: locOf(stmt) },
|
|
6480
|
+
body: [{ kind: "varDecl", localId, init: arrayValueRead(lowerer, varRef(source.id, sourceT, locOf(stmt)), varRef(cursor.id, F64, locOf(stmt)), sourceT.elem, locOf(stmt)), loc: locOf(stmt) }, ...body],
|
|
6481
|
+
...(labels && { labels }),
|
|
6482
|
+
loc: locOf(stmt),
|
|
6483
|
+
},
|
|
6484
|
+
],
|
|
6485
|
+
loc: locOf(stmt),
|
|
6486
|
+
});
|
|
5905
6487
|
if (!ts.isVariableDeclarationList(stmt.initializer)) {
|
|
5906
6488
|
// `for (x of xs)` over a PRE-DECLARED writable binding: JS assigns
|
|
5907
6489
|
// the existing binding once per pass — one shared binding across
|
|
@@ -5914,18 +6496,18 @@ export function lowerForOf(lowerer, stmt) {
|
|
|
5914
6496
|
while (ts.isParenthesizedExpression(target))
|
|
5915
6497
|
target = target.expression;
|
|
5916
6498
|
if (ts.isArrayLiteralExpression(target) || ts.isObjectLiteralExpression(target)) {
|
|
5917
|
-
const elemType =
|
|
6499
|
+
const elemType = elemValueT;
|
|
5918
6500
|
const loc = locOf(target);
|
|
5919
6501
|
const tmp = lowerer.declareHiddenLocal("%vof", elemType);
|
|
5920
6502
|
const elemRef = { kind: "varRef", localId: tmp.id, type: elemType, loc };
|
|
5921
6503
|
const assigns = lowerDestructuringAssign(lowerer, target, elemRef, target, loc);
|
|
5922
6504
|
const body = lowerer.inCtl("loop", () => lowerer.lowerScopedBlock(stmt.statement), labels);
|
|
5923
|
-
return
|
|
6505
|
+
return forValues(tmp.id, [assigns, ...body]);
|
|
5924
6506
|
}
|
|
5925
6507
|
if (ts.isIdentifier(target)) {
|
|
5926
6508
|
const writable = lowerer.resolveWritable(target);
|
|
5927
6509
|
if (writable) {
|
|
5928
|
-
const elemType =
|
|
6510
|
+
const elemType = elemValueT;
|
|
5929
6511
|
const loc = locOf(target);
|
|
5930
6512
|
const tmp = lowerer.declareHiddenLocal("%vof", elemType);
|
|
5931
6513
|
const write = {
|
|
@@ -5935,15 +6517,13 @@ export function lowerForOf(lowerer, stmt) {
|
|
|
5935
6517
|
loc,
|
|
5936
6518
|
};
|
|
5937
6519
|
const body = lowerer.inCtl("loop", () => lowerer.lowerScopedBlock(stmt.statement), labels);
|
|
5938
|
-
return
|
|
6520
|
+
return forValues(tmp.id, [write, ...body]);
|
|
5939
6521
|
}
|
|
5940
6522
|
}
|
|
5941
6523
|
lowerer.unsupported("SC1090", stmt.initializer, "for-of over a pre-declared variable (declare the loop variable in the loop: for (const x of ...))");
|
|
5942
6524
|
}
|
|
5943
6525
|
const list = stmt.initializer;
|
|
5944
|
-
|
|
5945
|
-
lowerer.unsupported("SC1090", list, "'using' declarations (dispose-at-scope-exit semantics)");
|
|
5946
|
-
}
|
|
6526
|
+
const resourceLoop = (list.flags & ts.NodeFlags.Using) !== 0;
|
|
5947
6527
|
const isLet = (list.flags & ts.NodeFlags.Let) !== 0 || (list.flags & ts.NodeFlags.BlockScoped) === 0;
|
|
5948
6528
|
const decl = list.declarations[0]; // the grammar allows exactly one
|
|
5949
6529
|
// `for (const [k, v] of pairs)` — a destructuring loop variable: the
|
|
@@ -5955,13 +6535,13 @@ export function lowerForOf(lowerer, stmt) {
|
|
|
5955
6535
|
if (ts.isArrayBindingPattern(decl.name) || ts.isObjectBindingPattern(decl.name)) {
|
|
5956
6536
|
lowerer.scopes.push(new Map());
|
|
5957
6537
|
try {
|
|
5958
|
-
const elemType =
|
|
6538
|
+
const elemType = elemValueT;
|
|
5959
6539
|
const loc = locOf(decl.name);
|
|
5960
6540
|
const tmp = lowerer.declareHiddenLocal("%destr", elemType);
|
|
5961
6541
|
const binds = [];
|
|
5962
6542
|
lowerer.lowerBindingPattern(decl.name, () => ({ kind: "varRef", localId: tmp.id, type: elemType, loc }), elemType, isLet, binds);
|
|
5963
6543
|
const body = lowerer.inCtl("loop", () => lowerer.lowerScopedBlock(stmt.statement), labels);
|
|
5964
|
-
return
|
|
6544
|
+
return forValues(tmp.id, [...binds, ...body]);
|
|
5965
6545
|
}
|
|
5966
6546
|
finally {
|
|
5967
6547
|
lowerer.scopes.pop();
|
|
@@ -5977,7 +6557,7 @@ export function lowerForOf(lowerer, stmt) {
|
|
|
5977
6557
|
// after the loop (both Node-exact for var).
|
|
5978
6558
|
const varTarget = forOfVarTarget(lowerer, decl);
|
|
5979
6559
|
if (varTarget) {
|
|
5980
|
-
const elemType =
|
|
6560
|
+
const elemType = elemValueT;
|
|
5981
6561
|
const loc = locOf(decl.name);
|
|
5982
6562
|
const tmp = lowerer.declareHiddenLocal("%vof", elemType);
|
|
5983
6563
|
const write = {
|
|
@@ -5987,11 +6567,28 @@ export function lowerForOf(lowerer, stmt) {
|
|
|
5987
6567
|
loc,
|
|
5988
6568
|
};
|
|
5989
6569
|
const body = lowerer.inCtl("loop", () => lowerer.lowerScopedBlock(stmt.statement), labels);
|
|
5990
|
-
return
|
|
6570
|
+
return forValues(tmp.id, [write, ...body]);
|
|
6571
|
+
}
|
|
6572
|
+
const local = lowerer.declareLocal(decl.name, decl.name.text, elemValueT, isLet);
|
|
6573
|
+
if (!typeEquals(elemValueT, sourceT.elem)) {
|
|
6574
|
+
const root = lowerer.runtimeOptionalRootOf(local);
|
|
6575
|
+
lowerer.runtimeOptionalLocals.add(root);
|
|
6576
|
+
lowerer.runtimeOptionalStorageLocals.add(root);
|
|
5991
6577
|
}
|
|
5992
|
-
const local = lowerer.declareLocal(decl.name, decl.name.text, iterable.type.elem, isLet);
|
|
5993
6578
|
const body = lowerer.inCtl("loop", () => lowerer.lowerScopedBlock(stmt.statement), labels);
|
|
5994
|
-
|
|
6579
|
+
if (!resourceLoop)
|
|
6580
|
+
return forValues(local.id, body);
|
|
6581
|
+
const resource = varRef(local.id, local.type, locOf(decl.name));
|
|
6582
|
+
const cleanup = cleanupForValue(lowerer, decl.name, resource, isAwaitUsing(list));
|
|
6583
|
+
return forValues(local.id, [{
|
|
6584
|
+
kind: "tryCatch",
|
|
6585
|
+
tryBody: body,
|
|
6586
|
+
catchBody: null,
|
|
6587
|
+
catchLocalId: null,
|
|
6588
|
+
finallyBody: cleanup,
|
|
6589
|
+
suppressFinallyErrors: true,
|
|
6590
|
+
loc: locOf(decl),
|
|
6591
|
+
}]);
|
|
5995
6592
|
}
|
|
5996
6593
|
finally {
|
|
5997
6594
|
lowerer.scopes.pop();
|
|
@@ -6087,8 +6684,9 @@ function lowerForOfStoredNumericIterator(lowerer, stmt, state, labels) {
|
|
|
6087
6684
|
type: F64,
|
|
6088
6685
|
loc,
|
|
6089
6686
|
};
|
|
6687
|
+
const valueT = sourceT.kind === "array" ? arrayValueType(lowerer, F64) : F64;
|
|
6090
6688
|
const sourceValue = () => sourceT.kind === "array"
|
|
6091
|
-
?
|
|
6689
|
+
? arrayValueRead(lowerer, sourceRef(), indexRef(), F64, loc)
|
|
6092
6690
|
: {
|
|
6093
6691
|
kind: "bytesIntrinsic",
|
|
6094
6692
|
method: "get",
|
|
@@ -6102,9 +6700,14 @@ function lowerForOfStoredNumericIterator(lowerer, stmt, state, labels) {
|
|
|
6102
6700
|
try {
|
|
6103
6701
|
const varTarget = exprTarget ?? (decl ? forOfVarTarget(lowerer, decl) : null);
|
|
6104
6702
|
const value = varTarget
|
|
6105
|
-
? lowerer.declareHiddenLocal("%numiterValue",
|
|
6106
|
-
: lowerer.declareLocal(decl.name, decl.name.text,
|
|
6107
|
-
|
|
6703
|
+
? lowerer.declareHiddenLocal("%numiterValue", valueT)
|
|
6704
|
+
: lowerer.declareLocal(decl.name, decl.name.text, valueT, isLet);
|
|
6705
|
+
if (!varTarget && valueT.kind === "union") {
|
|
6706
|
+
const root = lowerer.runtimeOptionalRootOf(value);
|
|
6707
|
+
lowerer.runtimeOptionalLocals.add(root);
|
|
6708
|
+
lowerer.runtimeOptionalStorageLocals.add(root);
|
|
6709
|
+
}
|
|
6710
|
+
const valueRef = varRef(value.id, valueT, loc);
|
|
6108
6711
|
const blame = decl?.name ?? exprTargetNode ?? stmt.initializer;
|
|
6109
6712
|
const head = [
|
|
6110
6713
|
{
|
|
@@ -7141,112 +7744,6 @@ function lowerForAwaitStdin(lowerer, stmt) {
|
|
|
7141
7744
|
lowerer.scopes.pop();
|
|
7142
7745
|
}
|
|
7143
7746
|
}
|
|
7144
|
-
/** `for await (const chunk of readable)` — the stream async iterator
|
|
7145
|
-
* (the stdin desugar's sibling): every pass awaits the runtime's
|
|
7146
|
-
* next-chunk promise — buffered content, or a REJECTION carrying the
|
|
7147
|
-
* stream's error (the await rethrows it, Node's iterator contract) —
|
|
7148
|
-
* and exits on the EOF sentinel. Chunks are Buffers in typed code
|
|
7149
|
-
* (encoded streams fence at the runtime entry); in the JS lane the
|
|
7150
|
-
* chunk is checked-dynamic and boxes by runtime tag (dyn strings once
|
|
7151
|
-
* an encoding applies, dyn undefined as the sentinel — chunks are
|
|
7152
|
-
* never undefined). Early exit leaves the stream alive (Node's
|
|
7153
|
-
* iterator return() would destroy it — a documented divergence). */
|
|
7154
|
-
function lowerForAwaitReadable(lowerer, stmt, recvT) {
|
|
7155
|
-
if (!lowerer.ctx.isAsync) {
|
|
7156
|
-
lowerer.unsupported("SC1090", stmt, "top-level 'for await' (await outside async functions)");
|
|
7157
|
-
}
|
|
7158
|
-
if (!ts.isVariableDeclarationList(stmt.initializer)) {
|
|
7159
|
-
lowerer.unsupported("SC1090", stmt.initializer, "for-await over a pre-declared variable (declare the loop variable in the loop: for await (const chunk of ...))");
|
|
7160
|
-
}
|
|
7161
|
-
const list = stmt.initializer;
|
|
7162
|
-
const isConst = (list.flags & ts.NodeFlags.Const) !== 0;
|
|
7163
|
-
const isLet = (list.flags & ts.NodeFlags.Let) !== 0;
|
|
7164
|
-
// `for await (var chunk of ...)`: the chunk binding is per-await
|
|
7165
|
-
// machinery (a fresh value each pass, fiber-parked in between) —
|
|
7166
|
-
// threading it through a shared hoisted slot has no user yet.
|
|
7167
|
-
if (!isConst && !isLet)
|
|
7168
|
-
lowerer.unsupported("SC1030", list, "'var' loop bindings in 'for await' (use const)");
|
|
7169
|
-
const decl = list.declarations[0];
|
|
7170
|
-
if (!ts.isIdentifier(decl.name))
|
|
7171
|
-
lowerer.unsupported("SC1031", decl.name);
|
|
7172
|
-
const loc = locOf(stmt);
|
|
7173
|
-
// The chunk is CHECKED-DYNAMIC in both lanes (the shim types the
|
|
7174
|
-
// iterator's element as any): the runtime boxes by tag — Buffers
|
|
7175
|
-
// normally, strings once an encoding applies — and the dyn surface
|
|
7176
|
-
// carries the common consumptions (.length, [i], toString, +=,
|
|
7177
|
-
// typeof). A statically-typed chunk would have to pick one side of
|
|
7178
|
-
// the encoding question at compile time.
|
|
7179
|
-
const dynLane = true;
|
|
7180
|
-
const chunkT = DYN;
|
|
7181
|
-
const promiseT = { kind: "promise", inner: chunkT };
|
|
7182
|
-
lowerer.scopes.push(new Map());
|
|
7183
|
-
try {
|
|
7184
|
-
// The receiver evaluates ONCE, before the loop.
|
|
7185
|
-
const recvLocal = lowerer.declareHiddenLocal("%faStream", recvT);
|
|
7186
|
-
const recvDecl = {
|
|
7187
|
-
kind: "varDecl",
|
|
7188
|
-
localId: recvLocal.id,
|
|
7189
|
-
init: lowerer.lowerExpr(stmt.expression),
|
|
7190
|
-
loc,
|
|
7191
|
-
};
|
|
7192
|
-
const p = lowerer.declareHiddenLocal("%streamNext", promiseT);
|
|
7193
|
-
const chunk = lowerer.declareLocal(decl.name, decl.name.text, chunkT, isLet);
|
|
7194
|
-
const body = lowerer.inCtl("loop", () => lowerer.lowerScopedBlock(stmt.statement));
|
|
7195
|
-
const chunkRef = { kind: "varRef", localId: chunk.id, type: chunkT, loc };
|
|
7196
|
-
const eofCond = dynLane
|
|
7197
|
-
? { kind: "dynTest", test: "undefined", value: chunkRef, type: BOOL, loc }
|
|
7198
|
-
: {
|
|
7199
|
-
kind: "bin",
|
|
7200
|
-
op: "===",
|
|
7201
|
-
left: { kind: "bytesIntrinsic", method: "length", receiver: chunkRef, args: [], type: F64, loc },
|
|
7202
|
-
right: { kind: "numLit", value: 0, type: F64, loc },
|
|
7203
|
-
type: BOOL,
|
|
7204
|
-
loc,
|
|
7205
|
-
};
|
|
7206
|
-
const head = [
|
|
7207
|
-
{
|
|
7208
|
-
kind: "varDecl",
|
|
7209
|
-
localId: p.id,
|
|
7210
|
-
init: {
|
|
7211
|
-
kind: "libCall",
|
|
7212
|
-
fn: dynLane ? "readable.nextChunkDyn" : "readable.nextChunk",
|
|
7213
|
-
args: [{ kind: "varRef", localId: recvLocal.id, type: recvT, loc }],
|
|
7214
|
-
type: promiseT,
|
|
7215
|
-
loc,
|
|
7216
|
-
},
|
|
7217
|
-
loc,
|
|
7218
|
-
},
|
|
7219
|
-
{
|
|
7220
|
-
kind: "varDecl",
|
|
7221
|
-
localId: chunk.id,
|
|
7222
|
-
init: {
|
|
7223
|
-
kind: "awaitExpr",
|
|
7224
|
-
value: { kind: "varRef", localId: p.id, type: promiseT, loc },
|
|
7225
|
-
type: chunkT,
|
|
7226
|
-
loc,
|
|
7227
|
-
},
|
|
7228
|
-
loc,
|
|
7229
|
-
},
|
|
7230
|
-
{ kind: "if", cond: eofCond, then: [{ kind: "break", loc }], else_: null, loc },
|
|
7231
|
-
];
|
|
7232
|
-
return {
|
|
7233
|
-
kind: "block",
|
|
7234
|
-
body: [
|
|
7235
|
-
recvDecl,
|
|
7236
|
-
{
|
|
7237
|
-
kind: "while",
|
|
7238
|
-
cond: { kind: "boolLit", value: true, type: BOOL, loc },
|
|
7239
|
-
body: [...head, ...body],
|
|
7240
|
-
loc,
|
|
7241
|
-
},
|
|
7242
|
-
],
|
|
7243
|
-
loc,
|
|
7244
|
-
};
|
|
7245
|
-
}
|
|
7246
|
-
finally {
|
|
7247
|
-
lowerer.scopes.pop();
|
|
7248
|
-
}
|
|
7249
|
-
}
|
|
7250
7747
|
export function lowerForStatement(lowerer, stmt) {
|
|
7251
7748
|
const labels = lowerer.takeLabels();
|
|
7252
7749
|
lowerer.scopes.push(new Map());
|