@scriptc/compiler 0.1.1 → 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 +45 -17
- package/ambient/scriptc.d.ts +2 -1
- 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 +220 -10
- package/dist/backend/c/exprs.js.map +1 -1
- package/dist/backend/c/island.js.map +1 -1
- package/dist/backend/c/may-throw.js.map +1 -1
- package/dist/backend/c/stmts.d.ts +3 -5
- package/dist/backend/c/stmts.js +58 -17
- package/dist/backend/c/stmts.js.map +1 -1
- package/dist/backend/c/types.js.map +1 -1
- package/dist/backend/c/walkers.js +31 -5
- package/dist/backend/c/walkers.js.map +1 -1
- package/dist/backend/external-c.js.map +1 -1
- package/dist/backend/linker.js.map +1 -1
- package/dist/backend/llvm/blocks.js.map +1 -1
- package/dist/backend/llvm/dyn.js +56 -3
- package/dist/backend/llvm/dyn.js.map +1 -1
- package/dist/backend/llvm/emitter.js +98 -19
- package/dist/backend/llvm/emitter.js.map +1 -1
- package/dist/backend/llvm/expr-async.js +85 -3
- package/dist/backend/llvm/expr-async.js.map +1 -1
- package/dist/backend/llvm/expr-bytes.js.map +1 -1
- package/dist/backend/llvm/expr-calls.js.map +1 -1
- package/dist/backend/llvm/expr-containers.js.map +1 -1
- package/dist/backend/llvm/expr-context.d.ts +1 -0
- package/dist/backend/llvm/expr-control.js.map +1 -1
- package/dist/backend/llvm/expr-dispatch.js.map +1 -1
- package/dist/backend/llvm/expr-dynamic.js +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.js.map +1 -1
- package/dist/backend/llvm/expr-records.js.map +1 -1
- package/dist/backend/llvm/expr-stream-bridges.js +220 -9
- 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.map +1 -1
- package/dist/backend/native-codegen.js.map +1 -1
- package/dist/backend/native-codegen.test.js +2 -0
- package/dist/backend/native-codegen.test.js.map +1 -1
- package/dist/backend/native-toolchain.js.map +1 -1
- package/dist/backend/native-toolchain.test.js.map +1 -1
- package/dist/backend/runtime-pack.js.map +1 -1
- package/dist/backend/targets.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 +12 -0
- 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 +4 -0
- package/dist/frontend/lowering/array-values.js +18 -2
- package/dist/frontend/lowering/array-values.js.map +1 -1
- package/dist/frontend/lowering/callback-arg.js.map +1 -1
- package/dist/frontend/lowering/containers/bytes.d.ts +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-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 +219 -48
- package/dist/frontend/lowering/lower-builtins.js.map +1 -1
- package/dist/frontend/lowering/lower-calls.d.ts +13 -4
- package/dist/frontend/lowering/lower-calls.js +407 -64
- 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 +135 -1467
- package/dist/frontend/lowering/lower-containers.js.map +1 -1
- package/dist/frontend/lowering/lower-event-emitter.js +51 -5
- package/dist/frontend/lowering/lower-event-emitter.js.map +1 -1
- package/dist/frontend/lowering/lower-exprs.d.ts +0 -78
- package/dist/frontend/lowering/lower-exprs.js +402 -2351
- 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 +3 -3
- package/dist/frontend/lowering/lower-inspect.js.map +1 -1
- package/dist/frontend/lowering/lower-island.d.ts +6 -0
- package/dist/frontend/lowering/lower-island.js +37 -5
- package/dist/frontend/lowering/lower-island.js.map +1 -1
- package/dist/frontend/lowering/lower-modules.js +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 +1 -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 +519 -253
- 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 +37 -34
- package/dist/frontend/lowering/lowerer.js +143 -57
- 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/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.js +5 -5
- package/dist/index.js.map +1 -1
- package/dist/ir/analysis.js.map +1 -1
- package/dist/ir/ir.d.ts +60 -30
- package/dist/ir/ir.js +57 -6
- package/dist/ir/ir.js.map +1 -1
- package/dist/ir/validate.js +44 -59
- package/dist/ir/validate.js.map +1 -1
- package/dist/library/cache-primitives.js.map +1 -1
- package/dist/library/cache-reset.test.js.map +1 -1
- package/dist/library/compiler-self-identity.js.map +1 -1
- package/dist/library/int-infer.js.map +1 -1
- package/dist/library/int-infer.test.js.map +1 -1
- package/dist/library/library-profile.js.map +1 -1
- package/dist/library/sidecar-validate.js.map +1 -1
- package/dist/library/sidecar.js.map +1 -1
- package/dist/startup-cache.js.map +1 -1
- package/dist/wasi-paths.js.map +1 -1
- package/package.json +18 -18
- package/surface-manifest.json +60 -4
|
@@ -0,0 +1,1815 @@
|
|
|
1
|
+
import { InternalCompilerError } from "../../../errors.js";
|
|
2
|
+
import * as ts from "../../ts7/adapter.js";
|
|
3
|
+
import { BOOL, DYN, F64, STRING, UNDEFINED_T, VOID, arrayOf, canDynCheckTo, isUnitType, shapeHasAccessorSlots, typeEquals, } from "../../../ir/ir.js";
|
|
4
|
+
import { isCjsExportTableLiteral, isJsSourceFile, locOf } from "../../program.js";
|
|
5
|
+
import { NARROW_FIRST } from "../surfaces.js";
|
|
6
|
+
import { recordShapeMismatchDiag } from "../../../diagnostics/diagnostic.js";
|
|
7
|
+
import { PoisonError, dynUndefinedExpr } from "../lowerer.js";
|
|
8
|
+
import { lowerIndexMergeHelper } from "../lower-containers.js";
|
|
9
|
+
import { isGenericCallableMemberType } from "../../type-mapper.js";
|
|
10
|
+
import { numLit, varRef } from "../../../ir/build.js";
|
|
11
|
+
import { isSafeToRepeat } from "./evaluation-safety.js";
|
|
12
|
+
import { tryLowerExpression } from "./try-lower-expression.js";
|
|
13
|
+
/** `{ a: 1, b: "x" }` → recordLit. The record type comes from the
|
|
14
|
+
* contextual type when tsc has one (annotated declarations, arguments,
|
|
15
|
+
* nested literals) and from the literal's own type otherwise — both intern
|
|
16
|
+
* to the same shapeId unless width subtyping is in play (an `as` cast can
|
|
17
|
+
* smuggle a wider/narrower literal past tsc's freshness check), which the
|
|
18
|
+
* exact-shape checks below reject with SC2002. Fields lower IN SOURCE
|
|
19
|
+
* ORDER: JS evaluates property values in source order. */
|
|
20
|
+
/** The dropped-field names of the PromiseSettledResult honest subset
|
|
21
|
+
* (SEMANTICS.md 46): when the literal's target type is (a union
|
|
22
|
+
* containing) the lib's PromiseFulfilledResult / PromiseRejectedResult,
|
|
23
|
+
* the record mapping kept only `status` — `value` and `reason` evaluate
|
|
24
|
+
* for effect and are not stored. Null for every other target. */
|
|
25
|
+
function settledDropNames(lowerer, tsType) {
|
|
26
|
+
let names = null;
|
|
27
|
+
const parts = tsType.isUnionType() ? ts.constituentTypes(tsType) : [tsType];
|
|
28
|
+
for (const part of parts) {
|
|
29
|
+
const sym = part.getSymbol();
|
|
30
|
+
if (!sym ||
|
|
31
|
+
!lowerer.checker.declarationsOf(sym).some((d) => ts.isInterfaceDeclaration(d) && lowerer.isStdlibFile(d.getSourceFile()))) {
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
if (sym.name === "PromiseFulfilledResult")
|
|
35
|
+
(names ??= new Set()).add("value");
|
|
36
|
+
if (sym.name === "PromiseRejectedResult")
|
|
37
|
+
(names ??= new Set()).add("reason");
|
|
38
|
+
}
|
|
39
|
+
return names;
|
|
40
|
+
}
|
|
41
|
+
/** `{ [KEY]: v }` where KEY is a compile-time-known string or number:
|
|
42
|
+
* the key folds into an ordinary (spelled) property name — the record
|
|
43
|
+
* shape is exactly what tsc computed for the literal (the late-bound
|
|
44
|
+
* name IS the checker's literal type), so the fold is just spelling.
|
|
45
|
+
* PURE key forms only, so skipping the key's evaluation is exact (JS
|
|
46
|
+
* evaluates the key before the value; effectful expressions keep the
|
|
47
|
+
* fence): any side-effect-free expression — identifiers, property
|
|
48
|
+
* chains with no accessor on them (enum members, `other.name` reads),
|
|
49
|
+
* literals, parenthesized/as-wrapped forms, operators over those —
|
|
50
|
+
* whose checker type is ONE string or number literal (or a union whose
|
|
51
|
+
* arms all spell the same name — `E1.x || E2.x` where both are 0).
|
|
52
|
+
* Number keys take JS's canonical spelling (`{ [E.member]: v }` stores
|
|
53
|
+
* "0" — ToPropertyKey), exactly the name tsc late-bound. Templates
|
|
54
|
+
* fold structurally span by span as well (a template of literals whose
|
|
55
|
+
* checker type widened still spells one string). Symbol-typed and
|
|
56
|
+
* runtime-valued keys stay out. */
|
|
57
|
+
function literalComputedKey(lowerer, name) {
|
|
58
|
+
return foldedStringKeyOf(lowerer, name.expression);
|
|
59
|
+
}
|
|
60
|
+
export function foldedStringKeyOf(lowerer, expr) {
|
|
61
|
+
if (ts.isParenthesizedExpression(expr))
|
|
62
|
+
return foldedStringKeyOf(lowerer, expr.expression);
|
|
63
|
+
if (ts.isStringLiteral(expr) || ts.isNoSubstitutionTemplateLiteral(expr))
|
|
64
|
+
return expr.text;
|
|
65
|
+
if (ts.isTemplateExpression(expr)) {
|
|
66
|
+
// Structural fold first — exact even where the checker widened the
|
|
67
|
+
// template's own type; the general type-directed fold below is the
|
|
68
|
+
// fallback (a template the checker DID type as one literal).
|
|
69
|
+
let out = expr.head.text;
|
|
70
|
+
let folded = true;
|
|
71
|
+
for (const span of expr.templateSpans) {
|
|
72
|
+
const part = foldedStringKeyOf(lowerer, span.expression);
|
|
73
|
+
if (part === null) {
|
|
74
|
+
folded = false;
|
|
75
|
+
break;
|
|
76
|
+
}
|
|
77
|
+
out += part + span.literal.text;
|
|
78
|
+
}
|
|
79
|
+
if (folded)
|
|
80
|
+
return out;
|
|
81
|
+
}
|
|
82
|
+
// The general fold: a PURE expression whose checker type spells one
|
|
83
|
+
// property name. An as-cast folds by its OWN checker type — that is
|
|
84
|
+
// the name tsc late-bound the property under, so shape and storage
|
|
85
|
+
// stay one name (the standard trust-the-checker bet); a cast that
|
|
86
|
+
// WIDENS to string un-late-binds the property, its type is no literal,
|
|
87
|
+
// and the fence stays.
|
|
88
|
+
if (!pureKeyExpr(lowerer, expr))
|
|
89
|
+
return null;
|
|
90
|
+
return literalKeySpellingOf(lowerer.typeOf(expr));
|
|
91
|
+
}
|
|
92
|
+
/** The property-name spelling of a single-literal checker type: string
|
|
93
|
+
* literals directly, number literals in JS's canonical ToString spelling
|
|
94
|
+
* (enum members included — their literal flags carry the value), and
|
|
95
|
+
* unions whose arms all spell the SAME name (`E1.x || E2.x` — two enum
|
|
96
|
+
* literal types, one value). Null for everything else. */
|
|
97
|
+
function literalKeySpellingOf(t) {
|
|
98
|
+
if (t.isStringLiteralType())
|
|
99
|
+
return t.value;
|
|
100
|
+
if (t.isNumberLiteralType())
|
|
101
|
+
return String(t.value);
|
|
102
|
+
if (t.isUnionType()) {
|
|
103
|
+
let out = null;
|
|
104
|
+
for (const arm of ts.constituentTypes(t)) {
|
|
105
|
+
const s = arm.isStringLiteralType() ? arm.value : arm.isNumberLiteralType() ? String(arm.value) : null;
|
|
106
|
+
if (s === null || (out !== null && s !== out))
|
|
107
|
+
return null;
|
|
108
|
+
out = s;
|
|
109
|
+
}
|
|
110
|
+
return out;
|
|
111
|
+
}
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
/** True iff evaluating `expr` can have no observable effect, so a fold
|
|
115
|
+
* may skip it: literals, identifier reads, property chains whose every
|
|
116
|
+
* member is a plain field/enum member (an accessor anywhere on the chain
|
|
117
|
+
* is a call), operators and casts over those. Calls, `new`, assignments,
|
|
118
|
+
* and element accesses stay impure. */
|
|
119
|
+
function pureKeyExpr(lowerer, expr) {
|
|
120
|
+
if (ts.isParenthesizedExpression(expr) || ts.isNonNullExpression(expr))
|
|
121
|
+
return pureKeyExpr(lowerer, expr.expression);
|
|
122
|
+
if (ts.isAsExpression(expr) || ts.isTypeAssertion(expr))
|
|
123
|
+
return pureKeyExpr(lowerer, expr.expression);
|
|
124
|
+
if (ts.isIdentifier(expr))
|
|
125
|
+
return true;
|
|
126
|
+
if (ts.isStringLiteralLike(expr) || ts.isNumericLiteral(expr))
|
|
127
|
+
return true;
|
|
128
|
+
if (expr.kind === ts.SyntaxKind.TrueKeyword || expr.kind === ts.SyntaxKind.FalseKeyword ||
|
|
129
|
+
expr.kind === ts.SyntaxKind.NullKeyword)
|
|
130
|
+
return true;
|
|
131
|
+
if (ts.isPropertyAccessExpression(expr)) {
|
|
132
|
+
const sym = lowerer.checker.getSymbolAtLocation(expr.name);
|
|
133
|
+
if (sym && sym.flags & (ts.SymbolFlags.GetAccessor | ts.SymbolFlags.SetAccessor))
|
|
134
|
+
return false;
|
|
135
|
+
return pureKeyExpr(lowerer, expr.expression);
|
|
136
|
+
}
|
|
137
|
+
if (ts.isPrefixUnaryExpression(expr) &&
|
|
138
|
+
(expr.operator === ts.SyntaxKind.MinusToken || expr.operator === ts.SyntaxKind.PlusToken ||
|
|
139
|
+
expr.operator === ts.SyntaxKind.ExclamationToken || expr.operator === ts.SyntaxKind.TildeToken)) {
|
|
140
|
+
return pureKeyExpr(lowerer, expr.operand);
|
|
141
|
+
}
|
|
142
|
+
if (ts.isBinaryExpression(expr)) {
|
|
143
|
+
const op = expr.operatorToken.kind;
|
|
144
|
+
const pureOp = op === ts.SyntaxKind.AmpersandAmpersandToken || op === ts.SyntaxKind.BarBarToken ||
|
|
145
|
+
op === ts.SyntaxKind.QuestionQuestionToken ||
|
|
146
|
+
(op >= ts.SyntaxKind.LessThanToken && op <= ts.SyntaxKind.CaretToken &&
|
|
147
|
+
op !== ts.SyntaxKind.InstanceOfKeyword && op !== ts.SyntaxKind.InKeyword);
|
|
148
|
+
return pureOp && pureKeyExpr(lowerer, expr.left) && pureKeyExpr(lowerer, expr.right);
|
|
149
|
+
}
|
|
150
|
+
if (ts.isConditionalExpression(expr)) {
|
|
151
|
+
return pureKeyExpr(lowerer, expr.condition) && pureKeyExpr(lowerer, expr.whenTrue) && pureKeyExpr(lowerer, expr.whenFalse);
|
|
152
|
+
}
|
|
153
|
+
if (ts.isTemplateExpression(expr))
|
|
154
|
+
return expr.templateSpans.every((s) => pureKeyExpr(lowerer, s.expression));
|
|
155
|
+
return false;
|
|
156
|
+
}
|
|
157
|
+
/** A property's field name: identifier/string-literal keys directly,
|
|
158
|
+
* foldable computed keys through literalComputedKey (the syntax pass
|
|
159
|
+
* fenced everything else). */
|
|
160
|
+
function propNameText(lowerer, name) {
|
|
161
|
+
if (ts.isComputedPropertyName(name)) {
|
|
162
|
+
const k = literalComputedKey(lowerer, name);
|
|
163
|
+
if (k === null)
|
|
164
|
+
throw new InternalCompilerError("lowerer bug: unfoldable computed key past the fence");
|
|
165
|
+
return k;
|
|
166
|
+
}
|
|
167
|
+
// Numeric literals name their canonical string key (JS's ToPropertyKey
|
|
168
|
+
// — the same name the checker gives the property symbol).
|
|
169
|
+
if (ts.isNumericLiteral(name))
|
|
170
|
+
return String(Number(name.text));
|
|
171
|
+
return name.text;
|
|
172
|
+
}
|
|
173
|
+
/** The runtime-keyed JS object literal (a computed key that doesn't fold):
|
|
174
|
+
* builds a dyn object member-by-member. Keys evaluate before their values,
|
|
175
|
+
* properties in source order — JS's object-literal evaluation exactly.
|
|
176
|
+
* Identifier/string keys are compile-time strings; numeric keys take
|
|
177
|
+
* their canonical number string (`{ 0x10: v }` stores "16" — JS's
|
|
178
|
+
* ToPropertyKey); computed keys evaluate and pass through ToString (the
|
|
179
|
+
* dyn's String() for dyn operands — `{ [field]: v }` where field is a
|
|
180
|
+
* checked-dynamic param). Values convert through the usual dyn boundary
|
|
181
|
+
* (dynFrom's domain, functions box); a value with no dyn representation
|
|
182
|
+
* fences per property. Spreads copy through dyn.assign in source order;
|
|
183
|
+
* accessors stay fenced. */
|
|
184
|
+
export function lowerDynObjectLiteral(lowerer, expr, boxValue) {
|
|
185
|
+
const loc = locOf(expr);
|
|
186
|
+
let fields = [];
|
|
187
|
+
let acc = null;
|
|
188
|
+
const flushFields = () => {
|
|
189
|
+
if (fields.length === 0)
|
|
190
|
+
return;
|
|
191
|
+
const chunk = { kind: "dynObjLit", fields, type: DYN, loc };
|
|
192
|
+
acc = acc === null
|
|
193
|
+
? chunk
|
|
194
|
+
: { kind: "libCall", fn: "dyn.assign", args: [acc, chunk], type: DYN, loc };
|
|
195
|
+
fields = [];
|
|
196
|
+
};
|
|
197
|
+
for (const prop of expr.properties) {
|
|
198
|
+
if (ts.isSpreadAssignment(prop)) {
|
|
199
|
+
flushFields();
|
|
200
|
+
const raw = lowerer.lowerExpr(prop.expression);
|
|
201
|
+
const source = boxValue
|
|
202
|
+
? boxValue(prop.expression, raw)
|
|
203
|
+
: lowerer.coerceToExpected(raw, DYN);
|
|
204
|
+
if (source.type.kind !== "dyn") {
|
|
205
|
+
lowerer.unsupported("SC1101", prop.expression, `spreading '${lowerer.fmt(source.type)}' into a checked-dynamic object literal`);
|
|
206
|
+
}
|
|
207
|
+
acc ??= { kind: "dynObjLit", fields: [], type: DYN, loc };
|
|
208
|
+
acc = { kind: "libCall", fn: "dyn.assign", args: [acc, source], type: DYN, loc: locOf(prop) };
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
if (!ts.isPropertyAssignment(prop) &&
|
|
212
|
+
!ts.isShorthandPropertyAssignment(prop) &&
|
|
213
|
+
!ts.isMethodDeclaration(prop)) {
|
|
214
|
+
lowerer.unsupported("SC1090", prop, "accessors in a runtime-keyed (computed-key) object literal");
|
|
215
|
+
}
|
|
216
|
+
const name = prop.name;
|
|
217
|
+
let key;
|
|
218
|
+
if (ts.isComputedPropertyName(name)) {
|
|
219
|
+
const folded = literalComputedKey(lowerer, name);
|
|
220
|
+
if (folded !== null) {
|
|
221
|
+
key = { kind: "strLit", value: folded, type: STRING, loc: locOf(name) };
|
|
222
|
+
}
|
|
223
|
+
else {
|
|
224
|
+
let k = lowerer.lowerExpr(name.expression);
|
|
225
|
+
if (k.type.kind === "f64" || k.type.kind === "bool" || k.type.kind === "dyn") {
|
|
226
|
+
k = { kind: "toString", operand: k, type: STRING, loc: locOf(name) };
|
|
227
|
+
}
|
|
228
|
+
if (k.type.kind !== "string") {
|
|
229
|
+
lowerer.unsupported("SC1090", name, `'${lowerer.fmt(k.type)}'-typed computed property keys (string, number, boolean, and unknown keys stringify)`);
|
|
230
|
+
}
|
|
231
|
+
key = k;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
else if (ts.isIdentifier(name) || ts.isStringLiteral(name)) {
|
|
235
|
+
key = { kind: "strLit", value: name.text, type: STRING, loc: locOf(name) };
|
|
236
|
+
}
|
|
237
|
+
else if (ts.isNumericLiteral(name)) {
|
|
238
|
+
key = { kind: "strLit", value: String(Number(name.text)), type: STRING, loc: locOf(name) };
|
|
239
|
+
}
|
|
240
|
+
else {
|
|
241
|
+
lowerer.unsupported("SC1090", prop, "non-identifier property names");
|
|
242
|
+
}
|
|
243
|
+
const valueExpr = ts.isMethodDeclaration(prop)
|
|
244
|
+
? prop
|
|
245
|
+
: ts.isShorthandPropertyAssignment(prop)
|
|
246
|
+
? prop.name
|
|
247
|
+
: prop.initializer;
|
|
248
|
+
// Lambda values that fail to lower become trap closures (the
|
|
249
|
+
// per-call fence granularity — fenceClosureProbe) so the object
|
|
250
|
+
// still builds; everything else keeps the per-property fence below.
|
|
251
|
+
let raw;
|
|
252
|
+
const propDiagsBefore = lowerer.diags.length;
|
|
253
|
+
try {
|
|
254
|
+
const lowerValue = () => ts.isMethodDeclaration(prop)
|
|
255
|
+
? (lowerer.rejectThisInObjectMethod(prop.body ?? prop), lowerer.lowerLambda(prop))
|
|
256
|
+
: lowerer.lowerExpr(valueExpr);
|
|
257
|
+
raw =
|
|
258
|
+
fenceClosureProbe(lowerer, valueExpr, undefined, lowerValue) ??
|
|
259
|
+
lowerValue();
|
|
260
|
+
}
|
|
261
|
+
catch (err) {
|
|
262
|
+
// A PURE member read a JS file cannot lower (a namespace object
|
|
263
|
+
// in an export aggregate): the slot takes a boxed fence closure —
|
|
264
|
+
// the diagnostics defer to the runtime-fence ledger, the object
|
|
265
|
+
// builds, and only USING the member stops the run.
|
|
266
|
+
const pureMember = ts.isIdentifier(valueExpr);
|
|
267
|
+
if (!(err instanceof PoisonError) || !pureMember)
|
|
268
|
+
throw err;
|
|
269
|
+
const fence = lowerer.deferToRuntimeFence(propDiagsBefore, prop, {
|
|
270
|
+
kind: "closure",
|
|
271
|
+
name: () => `%fn${lowerer.lambdaCounter++}_dynfence`,
|
|
272
|
+
returnType: VOID,
|
|
273
|
+
type: { kind: "func", params: [], ret: VOID },
|
|
274
|
+
});
|
|
275
|
+
if (!fence)
|
|
276
|
+
throw err;
|
|
277
|
+
raw = fence;
|
|
278
|
+
}
|
|
279
|
+
let v = boxValue
|
|
280
|
+
? boxValue(valueExpr, raw)
|
|
281
|
+
: lowerer.coerceToExpected(raw, DYN);
|
|
282
|
+
if (v.type.kind !== "dyn") {
|
|
283
|
+
const convDiagsBefore = lowerer.diags.length;
|
|
284
|
+
try {
|
|
285
|
+
lowerer.unsupported("SC1101", valueExpr, `holding '${lowerer.fmt(v.type)}' values in a runtime-keyed (computed-key) object literal`);
|
|
286
|
+
}
|
|
287
|
+
catch (err) {
|
|
288
|
+
// A member value the checked-dynamic tree cannot hold — a func whose signature
|
|
289
|
+
// cannot box, an island ('any') handle, a record carrying func
|
|
290
|
+
// fields (the export aggregate's typed utilities and npm-handle
|
|
291
|
+
// members): the same call-time fence deferral as an unlowerable
|
|
292
|
+
// member — the slot takes a boxed fence closure; only USING it
|
|
293
|
+
// stops the run. Probe mode and ICEs keep the poison.
|
|
294
|
+
if (!(err instanceof PoisonError))
|
|
295
|
+
throw err;
|
|
296
|
+
const fence = lowerer.deferToRuntimeFence(convDiagsBefore, prop, {
|
|
297
|
+
kind: "closure",
|
|
298
|
+
name: () => `%fn${lowerer.lambdaCounter++}_dynfence`,
|
|
299
|
+
returnType: VOID,
|
|
300
|
+
type: { kind: "func", params: [], ret: VOID },
|
|
301
|
+
});
|
|
302
|
+
if (!fence)
|
|
303
|
+
throw err;
|
|
304
|
+
v = lowerer.coerceToExpected(fence, DYN);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
fields.push({ key, value: v });
|
|
308
|
+
}
|
|
309
|
+
flushFields();
|
|
310
|
+
return acc ?? { kind: "dynObjLit", fields: [], type: DYN, loc };
|
|
311
|
+
}
|
|
312
|
+
/** Spreading a source whose shape carries accessor slots: Node's copy
|
|
313
|
+
* invokes each getter exactly once in insertion order — even for keys a
|
|
314
|
+
* later contributor overrides — while the field-copy desugar assumes pure
|
|
315
|
+
* reads it may drop or reorder. Getter-call counts would silently diverge,
|
|
316
|
+
* so accessor-carrying sources fence by name. */
|
|
317
|
+
function fenceAccessorSpreadSource(lowerer, prop, srcShape) {
|
|
318
|
+
if (srcShape && shapeHasAccessorSlots(srcShape)) {
|
|
319
|
+
lowerer.unsupported("SC1090", prop, "object spread of sources carrying get/set accessor properties (Node invokes each getter once during the copy — the field-copy desugar cannot model the calls; bind the reads to consts first)");
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
/** The JS trap-closure fallback for FUNCTION-VALUED object properties: a
|
|
323
|
+
* lambda whose body fails to lower inside a JS object literal becomes a
|
|
324
|
+
* closure of the field's exact func type whose body is the runtime fence —
|
|
325
|
+
* the object still BUILDS (commander's `_outputConfiguration` table: the
|
|
326
|
+
* driven writeOut/writeErr entries work; an unloweraable color probe traps
|
|
327
|
+
* only if something CALLS it). The failed lambda's diagnostics move to the
|
|
328
|
+
* runtime-fence inventory, exactly like a fenced JS statement. Null when
|
|
329
|
+
* the fallback does not apply (not a JS file, not a lambda, no func slot)
|
|
330
|
+
* — the caller lowers normally. ICEs (SC9001) never convert. */
|
|
331
|
+
function fenceClosureProbe(lowerer, node, slotType, attempt) {
|
|
332
|
+
if (!isJsSourceFile(node.getSourceFile()))
|
|
333
|
+
return null;
|
|
334
|
+
let inner = node;
|
|
335
|
+
while (ts.isParenthesizedExpression(inner))
|
|
336
|
+
inner = inner.expression;
|
|
337
|
+
if (!ts.isArrowFunction(inner) && !ts.isFunctionExpression(inner) && !ts.isMethodDeclaration(inner)) {
|
|
338
|
+
return null;
|
|
339
|
+
}
|
|
340
|
+
// The trap's ABI: the field's func slot when one exists; otherwise (the
|
|
341
|
+
// dyn-object literal path — the value boxes into dyn) the all-dyn
|
|
342
|
+
// signature of the lambda's own arity, which canBoxFuncIntoDyn always
|
|
343
|
+
// admits.
|
|
344
|
+
const fieldType = slotType !== undefined && slotType.kind === "func"
|
|
345
|
+
? slotType
|
|
346
|
+
: { kind: "func", params: inner.parameters.map(() => DYN), ret: DYN };
|
|
347
|
+
if (slotType !== undefined && slotType.kind !== "func")
|
|
348
|
+
return null;
|
|
349
|
+
const diagsBefore = lowerer.diags.length;
|
|
350
|
+
try {
|
|
351
|
+
return attempt();
|
|
352
|
+
}
|
|
353
|
+
catch (e) {
|
|
354
|
+
if (!(e instanceof PoisonError))
|
|
355
|
+
throw e;
|
|
356
|
+
const params = fieldType.params.map((t, i) => ({ localId: `p.${i}`, name: `p${i}`, type: t }));
|
|
357
|
+
const fence = lowerer.deferToRuntimeFence(diagsBefore, node, {
|
|
358
|
+
kind: "closure",
|
|
359
|
+
name: () => `%fence.fn.${lowerer.liftedFns.length}`,
|
|
360
|
+
params,
|
|
361
|
+
returnType: fieldType.ret,
|
|
362
|
+
paramsMutable: true,
|
|
363
|
+
type: fieldType,
|
|
364
|
+
fallback: { code: "SC1090", message: "this function's body has no static lowering" },
|
|
365
|
+
bareMessage: true,
|
|
366
|
+
allowDiagSink: true,
|
|
367
|
+
});
|
|
368
|
+
if (!fence)
|
|
369
|
+
throw e;
|
|
370
|
+
return fence;
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
/** The union arm an object LITERAL inhabits when its fields must widen
|
|
374
|
+
* PER FIELD into exactly one record arm — the reducer-action pattern
|
|
375
|
+
* (`{ kind: "a", parsed: 1 }` into `{ kind: "a"; parsed: number | null }
|
|
376
|
+
* | { kind: "b" }`). The IR shapes erased the literal types tsc
|
|
377
|
+
* discriminated on, so the probe runs against the contextual union's
|
|
378
|
+
* CHECKER members: every literal field must exist on the member
|
|
379
|
+
* (excess-property freshness — tsc already enforced it), every arm field
|
|
380
|
+
* missing from the literal must be optional-flavored (an undefined-armed
|
|
381
|
+
* union, or a dyn slot — the absent-completion rule), and each present
|
|
382
|
+
* field's LITERAL type must fit the member's field type — literal against
|
|
383
|
+
* literal decides by VALUE (the discriminant), everything else by the
|
|
384
|
+
* widened IR pair under the width-lift relation. Exactly ONE fitting arm
|
|
385
|
+
* answers it; zero or several answer null and the caller keeps its
|
|
386
|
+
* fences. Plain property-assignment/shorthand literals only — spreads,
|
|
387
|
+
* accessors, methods, and unfoldable computed keys keep their own paths. */
|
|
388
|
+
function literalUnionArmOf(lowerer, expr, tsType, recordArms) {
|
|
389
|
+
if (!tsType.isUnionType())
|
|
390
|
+
return null;
|
|
391
|
+
const props = [];
|
|
392
|
+
for (const p of expr.properties) {
|
|
393
|
+
if (ts.isPropertyAssignment(p) && !ts.isComputedPropertyName(p.name)) {
|
|
394
|
+
props.push({ name: propNameText(lowerer, p.name), node: p.initializer });
|
|
395
|
+
}
|
|
396
|
+
else if (ts.isShorthandPropertyAssignment(p) && ts.isIdentifier(p.name)) {
|
|
397
|
+
props.push({ name: p.name.text, node: p.name });
|
|
398
|
+
}
|
|
399
|
+
else {
|
|
400
|
+
return null;
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
/** litT fits ftT: unions per arm; literal-vs-literal by value; unit
|
|
404
|
+
* types only into their own unit; otherwise the widened IR pair must be
|
|
405
|
+
* equal or width-liftable. */
|
|
406
|
+
const fits = (litT, ftT) => {
|
|
407
|
+
if (ftT.isUnionType())
|
|
408
|
+
return ts.constituentTypes(ftT).some((a) => fits(litT, a));
|
|
409
|
+
if (ftT.isStringLiteralType())
|
|
410
|
+
return litT.isStringLiteralType() && litT.value === ftT.value;
|
|
411
|
+
if (ftT.isNumberLiteralType())
|
|
412
|
+
return litT.isNumberLiteralType() && litT.value === ftT.value;
|
|
413
|
+
if (ftT.flags & ts.TypeFlags.BooleanLiteral) {
|
|
414
|
+
return (litT.flags & ts.TypeFlags.BooleanLiteral) !== 0 && lowerer.checker.typeToString(litT) === lowerer.checker.typeToString(ftT);
|
|
415
|
+
}
|
|
416
|
+
if (ftT.flags & ts.TypeFlags.Null)
|
|
417
|
+
return (litT.flags & ts.TypeFlags.Null) !== 0;
|
|
418
|
+
if (ftT.flags & ts.TypeFlags.Undefined)
|
|
419
|
+
return (litT.flags & ts.TypeFlags.Undefined) !== 0;
|
|
420
|
+
if (litT.flags & (ts.TypeFlags.Null | ts.TypeFlags.Undefined))
|
|
421
|
+
return false;
|
|
422
|
+
const li = lowerer.mapTypeOf(lowerer.checker.getBaseTypeOfLiteralType(litT));
|
|
423
|
+
const fi = lowerer.mapTypeOf(ftT);
|
|
424
|
+
if (!li || !fi)
|
|
425
|
+
return false;
|
|
426
|
+
return typeEquals(li, fi) || lowerer.widthLiftPlan(li, fi) !== null;
|
|
427
|
+
};
|
|
428
|
+
const armShapeIds = new Set(recordArms.map((a) => a.shapeId));
|
|
429
|
+
const candidates = new Set();
|
|
430
|
+
for (const member of ts.constituentTypes(tsType)) {
|
|
431
|
+
const mMapped = lowerer.mapTypeOf(member);
|
|
432
|
+
if (mMapped?.kind !== "record" || !armShapeIds.has(mMapped.shapeId) || candidates.has(mMapped.shapeId))
|
|
433
|
+
continue;
|
|
434
|
+
const shape = lowerer.shapes.get(mMapped.shapeId);
|
|
435
|
+
if (!shape || shape.tuple || shape.indexValue)
|
|
436
|
+
continue;
|
|
437
|
+
// Excess-property freshness: every literal field must exist on the
|
|
438
|
+
// member (tsc rejected the others for a fresh literal).
|
|
439
|
+
if (!props.every((p) => lowerer.checker.getPropertyOfType(member, p.name) !== undefined))
|
|
440
|
+
continue;
|
|
441
|
+
// Arm fields the literal leaves unset must be optional-flavored (the
|
|
442
|
+
// absent-completion rule: an undefined-armed union or a dyn slot).
|
|
443
|
+
const names = new Set(props.map((p) => p.name));
|
|
444
|
+
const absentOk = shape.fields.every((f) => {
|
|
445
|
+
if (names.has(f.name))
|
|
446
|
+
return true;
|
|
447
|
+
if (f.type.kind === "dyn")
|
|
448
|
+
return true;
|
|
449
|
+
if (f.type.kind !== "union")
|
|
450
|
+
return false;
|
|
451
|
+
return lowerer.unions.get(f.type.unionId)?.arms.some((a) => a.kind === "undefinedT") ?? false;
|
|
452
|
+
});
|
|
453
|
+
if (!absentOk)
|
|
454
|
+
continue;
|
|
455
|
+
const fieldsFit = props.every((p) => {
|
|
456
|
+
const sym = lowerer.checker.getPropertyOfType(member, p.name);
|
|
457
|
+
if (!sym)
|
|
458
|
+
return false;
|
|
459
|
+
return fits(lowerer.typeOf(p.node), lowerer.checker.getTypeOfSymbol(sym));
|
|
460
|
+
});
|
|
461
|
+
if (fieldsFit)
|
|
462
|
+
candidates.add(mMapped.shapeId);
|
|
463
|
+
}
|
|
464
|
+
if (candidates.size !== 1)
|
|
465
|
+
return null;
|
|
466
|
+
const [only] = candidates;
|
|
467
|
+
return recordArms.find((a) => a.shapeId === only) ?? null;
|
|
468
|
+
}
|
|
469
|
+
/** Object spread treats nullish sources as empty. Preserve evaluation once,
|
|
470
|
+
* then turn `Record<K, V> | undefined` into either that record or a fresh
|
|
471
|
+
* empty record of the same represented shape. */
|
|
472
|
+
function lowerOptionalIndexSpreadSource(lowerer, source, loc) {
|
|
473
|
+
if (source.type.kind !== "union")
|
|
474
|
+
return null;
|
|
475
|
+
const def = lowerer.unions.get(source.type.unionId);
|
|
476
|
+
if (!def || def.arms.length !== 2)
|
|
477
|
+
return null;
|
|
478
|
+
const undefinedTag = def.arms.findIndex((arm) => arm.kind === "undefinedT");
|
|
479
|
+
const recordTag = def.arms.findIndex((arm) => arm.kind === "record");
|
|
480
|
+
if (undefinedTag < 0 || recordTag < 0)
|
|
481
|
+
return null;
|
|
482
|
+
const recordType = def.arms[recordTag];
|
|
483
|
+
if (recordType.kind !== "record")
|
|
484
|
+
return null;
|
|
485
|
+
const prefix = [];
|
|
486
|
+
let stable = source;
|
|
487
|
+
if (!isSafeToRepeat(source)) {
|
|
488
|
+
const local = lowerer.declareHiddenLocal("%spread", source.type);
|
|
489
|
+
prefix.push({ kind: "varDecl", localId: local.id, init: source, loc });
|
|
490
|
+
stable = varRef(local.id, source.type, loc);
|
|
491
|
+
}
|
|
492
|
+
const selected = {
|
|
493
|
+
kind: "ternary",
|
|
494
|
+
cond: { kind: "unionIsTag", unionId: source.type.unionId, tag: undefinedTag, negated: false, value: stable, type: BOOL, loc },
|
|
495
|
+
then: { kind: "recordLit", fields: [], type: recordType, loc },
|
|
496
|
+
else_: { kind: "unionNarrow", unionId: source.type.unionId, tag: recordTag, value: stable, type: recordType, loc },
|
|
497
|
+
type: recordType,
|
|
498
|
+
loc,
|
|
499
|
+
};
|
|
500
|
+
return prefix.length === 0 ? selected : { kind: "seqExpr", stmts: prefix, result: selected, type: recordType, loc };
|
|
501
|
+
}
|
|
502
|
+
export function lowerObjectLiteral(lowerer, expr) {
|
|
503
|
+
const loc = locOf(expr);
|
|
504
|
+
// The RUNTIME-KEYED literal (JS): a computed key that doesn't fold to a
|
|
505
|
+
// compile-time string means the literal's shape is not a compile-time
|
|
506
|
+
// fact — no record shape can hold it. The whole literal builds as a dyn
|
|
507
|
+
// object instead (`{ [field]: criteria, actual: 0, ... }` —
|
|
508
|
+
// test/common's _mustCallInner context), where keys are runtime string
|
|
509
|
+
// values. TypeScript keeps the record world and its fence: this shape
|
|
510
|
+
// only arises in checked-dynamic JS.
|
|
511
|
+
if (isJsSourceFile(expr.getSourceFile()) &&
|
|
512
|
+
expr.properties.some((p) => {
|
|
513
|
+
const n = ts.isSpreadAssignment(p) ? undefined : p.name;
|
|
514
|
+
return n !== undefined && ts.isComputedPropertyName(n) && literalComputedKey(lowerer, n) === null;
|
|
515
|
+
})) {
|
|
516
|
+
return lowerDynObjectLiteral(lowerer, expr);
|
|
517
|
+
}
|
|
518
|
+
// Syntax fence FIRST: unsupported member forms get their specific
|
|
519
|
+
// message even when they also make the literal's type unmappable
|
|
520
|
+
// (a getter or a `this`-returning method would otherwise surface as an
|
|
521
|
+
// opaque type fence on the whole literal).
|
|
522
|
+
for (const prop of expr.properties) {
|
|
523
|
+
if (ts.isSpreadAssignment(prop)) {
|
|
524
|
+
// Supported shapes: FULL spreads of known record shapes read as
|
|
525
|
+
// identifiers or other side-effect-free reads, all BEFORE any
|
|
526
|
+
// explicit property (the field-copy desugar reads spread fields
|
|
527
|
+
// first, exactly JS's eager copy order); and CONDITIONAL spreads
|
|
528
|
+
// `...(c ? {k: v} : {})` — the optional-field idiom tsc types as
|
|
529
|
+
// `k?: ...` — which desugar to one conditional field at the
|
|
530
|
+
// spread's own position (any position: they introduce one fresh
|
|
531
|
+
// name, collision-fenced below). Everything else stays fenced.
|
|
532
|
+
const cs = conditionalSpreadOf(prop.expression);
|
|
533
|
+
if (cs === "unsupported" || (cs && cs.props.length !== 1)) {
|
|
534
|
+
lowerer.unsupported("SC1090", prop, "conditional spreads beyond `...(c ? { field: v } : {})` (exactly one property against an empty arm — spell other shapes as optional fields)");
|
|
535
|
+
}
|
|
536
|
+
// Spread ORDER is fenced in the field-by-field desugar below, not
|
|
537
|
+
// here: the index-signature merge path supports any order (keyed
|
|
538
|
+
// last-write-wins writes), so `{ K: v, ...extra }` into a pure
|
|
539
|
+
// Record shape compiles — the buildServiceEnv pattern.
|
|
540
|
+
continue;
|
|
541
|
+
}
|
|
542
|
+
if (ts.isGetAccessorDeclaration(prop) || ts.isSetAccessorDeclaration(prop)) {
|
|
543
|
+
// JavaScript CJS-export tables reach reads through the lifted-
|
|
544
|
+
// accessor path (cjsExportAccessorRead) — the literal VALUE
|
|
545
|
+
// narrows to its plain fields below (accessor names are not
|
|
546
|
+
// record storage; SEMANTICS.md documents the enumeration
|
|
547
|
+
// divergence). TypeScript accessors lower into the shape's
|
|
548
|
+
// reserved closure slots (%get:/%set: — see accessorSlotProp);
|
|
549
|
+
// `this` in the body is fenced up front: the closure slot has no
|
|
550
|
+
// receiver (capturing the record under construction would be an
|
|
551
|
+
// RC cycle), and the generic lexical-this walk would silently
|
|
552
|
+
// capture an ENCLOSING method's `this` — the object-method rule.
|
|
553
|
+
if (!isJsSourceFile(expr.getSourceFile())) {
|
|
554
|
+
rejectThisInObjectAccessor(lowerer, prop.body ?? prop);
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
// Unfoldable computed keys are NOT fenced here: an index-signature
|
|
558
|
+
// target lowers them as runtime keyed writes (the merge path below);
|
|
559
|
+
// every other target re-fences them after the merge path declined
|
|
560
|
+
// (fenceUnfoldableComputedKeys).
|
|
561
|
+
// Identifier keys, STRING-LITERAL keys (`"content-type": v` —
|
|
562
|
+
// record field names are data, never C identifiers; the mangler
|
|
563
|
+
// encodes what C can't spell), NUMERIC-LITERAL keys in their
|
|
564
|
+
// canonical string spelling (`{ 0: v }` stores "0", `{ 0x10: v }`
|
|
565
|
+
// stores "16" — JS's ToPropertyKey; the checker names the property
|
|
566
|
+
// symbol the same way), and FOLDABLE computed keys (above).
|
|
567
|
+
if (!prop.name ||
|
|
568
|
+
!(ts.isIdentifier(prop.name) || ts.isStringLiteral(prop.name) ||
|
|
569
|
+
ts.isNumericLiteral(prop.name) || ts.isComputedPropertyName(prop.name))) {
|
|
570
|
+
lowerer.unsupported("SC1090", prop, "non-identifier property names");
|
|
571
|
+
}
|
|
572
|
+
// Shorthand methods are just closure-valued fields — but their `this`
|
|
573
|
+
// is dynamically bound (typed as the literal by tsc, so noImplicitThis
|
|
574
|
+
// lets it through) and records don't model it; the generic
|
|
575
|
+
// lexical-this walk would silently capture an ENCLOSING method's
|
|
576
|
+
// `this`, so any `this` in the body is rejected up front.
|
|
577
|
+
if (ts.isMethodDeclaration(prop))
|
|
578
|
+
lowerer.rejectThisInObjectMethod(prop.body ?? prop);
|
|
579
|
+
}
|
|
580
|
+
let tsType = lowerer.checker.getContextualType(expr) ?? lowerer.typeOf(expr);
|
|
581
|
+
// `lit satisfies T` is TYPE-LEVEL only: the expression's checker type —
|
|
582
|
+
// and therefore the shape every downstream consumer sees — is the
|
|
583
|
+
// literal's OWN type (T still contextually types members, so inferred
|
|
584
|
+
// parameter types flow). Building at T would reshape the value tsc
|
|
585
|
+
// says has the literal's type: `{...} satisfies Movable &
|
|
586
|
+
// Record<string, unknown>` must NOT become an index-signature record.
|
|
587
|
+
// Own type wins whenever it maps to a record; an unmappable own type
|
|
588
|
+
// keeps the contextual fallback (a bare-null field whose satisfies
|
|
589
|
+
// target names the wider slot type).
|
|
590
|
+
{
|
|
591
|
+
let p = expr.parent;
|
|
592
|
+
while (ts.isParenthesizedExpression(p))
|
|
593
|
+
p = p.parent;
|
|
594
|
+
if (ts.isSatisfiesExpression(p)) {
|
|
595
|
+
const own = lowerer.typeOf(expr);
|
|
596
|
+
if (lowerer.mapTypeOf(own)?.kind === "record")
|
|
597
|
+
tsType = own;
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
// An async function's return position types the literal
|
|
601
|
+
// `T | PromiseLike<T>` (the lib's await-unwrapping contract). The
|
|
602
|
+
// PromiseLike arm never maps, and the record the return slot actually
|
|
603
|
+
// holds is exactly the checker's awaited type — strip to it BEFORE the
|
|
604
|
+
// own-type fallback below: the awaited contextual type carries the
|
|
605
|
+
// slot's field types (`lanIp: string | null`), which the literal's own
|
|
606
|
+
// type narrows away (a field written as `lanIp: null` types as bare
|
|
607
|
+
// `null`, which maps to nothing on its own).
|
|
608
|
+
if (tsType.isUnionType() && ts.constituentTypes(tsType).some((t) => t.getSymbol()?.name === "PromiseLike")) {
|
|
609
|
+
tsType = lowerer.checker.getAwaitedType(tsType) ?? tsType;
|
|
610
|
+
}
|
|
611
|
+
let mapped = lowerer.mapTypeOf(tsType);
|
|
612
|
+
// An EMPTY-record context under a NON-empty literal (`Object.keys({
|
|
613
|
+
// ...process.env })` — the lib's `{}`-typed parameters admit every
|
|
614
|
+
// object): `{}` carries no shape information, so the literal builds as
|
|
615
|
+
// its OWN type, exactly the unmappable-context fallback below. An
|
|
616
|
+
// empty LITERAL keeps the context (the shapes agree).
|
|
617
|
+
if (mapped?.kind === "record" && expr.properties.length > 0) {
|
|
618
|
+
const ctxShape = lowerer.shapes.get(mapped.shapeId);
|
|
619
|
+
if (ctxShape && ctxShape.fields.length === 0 && !ctxShape.indexValue && !ctxShape.tuple) {
|
|
620
|
+
mapped = lowerer.mapTypeOf(lowerer.typeOf(expr)) ?? mapped;
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
// A literal with a property the contextual TYPE ITSELF lacks — only
|
|
624
|
+
// reachable through type assertions and satisfies (fresh-literal
|
|
625
|
+
// excess-property checks reject the direct spelling): the context
|
|
626
|
+
// cannot hold the value, so the literal builds at its OWN type and the
|
|
627
|
+
// slot's width coercion narrows it (divergence 36's copy stance — the
|
|
628
|
+
// extra fields drop in the copy). The probe is against the CHECKER's
|
|
629
|
+
// contextual type, not the mapped shape: a property the contextual
|
|
630
|
+
// type carries but the shape dropped (settled value/reason,
|
|
631
|
+
// generic-callable members) belongs to the drop paths below, and
|
|
632
|
+
// index-signature contexts keep every key (overflow capture).
|
|
633
|
+
if (mapped?.kind === "record" && expr.properties.length > 0) {
|
|
634
|
+
const ctxShape = lowerer.shapes.get(mapped.shapeId);
|
|
635
|
+
if (ctxShape && !ctxShape.indexValue && !ctxShape.tuple) {
|
|
636
|
+
const names = new Set(ctxShape.fields.map((f) => f.name));
|
|
637
|
+
const ctxHasProp = (name) => {
|
|
638
|
+
const members = tsType.isUnionType() ? ts.constituentTypes(tsType) : [tsType];
|
|
639
|
+
return members.some((m) => lowerer.checker.getPropertyOfType(m, name) !== undefined);
|
|
640
|
+
};
|
|
641
|
+
const extraOf = (text) => !names.has(text) && !ctxHasProp(text);
|
|
642
|
+
const extra = expr.properties.some((p) => {
|
|
643
|
+
if (ts.isSpreadAssignment(p) || !p.name)
|
|
644
|
+
return false;
|
|
645
|
+
if (ts.isIdentifier(p.name) || ts.isStringLiteral(p.name))
|
|
646
|
+
return extraOf(p.name.text);
|
|
647
|
+
if (ts.isNumericLiteral(p.name))
|
|
648
|
+
return extraOf(String(Number(p.name.text)));
|
|
649
|
+
return false; // computed keys keep their existing paths
|
|
650
|
+
});
|
|
651
|
+
if (extra)
|
|
652
|
+
mapped = lowerer.mapTypeOf(lowerer.typeOf(expr)) ?? mapped;
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
// A union-typed slot (`const r: Res = { kind: "ok", ... }`) contextually
|
|
656
|
+
// types the literal as the WHOLE union; build the literal as its own
|
|
657
|
+
// (arm) shape and let the slot's coercion wrap it into the union. An
|
|
658
|
+
// unknown-typed slot (`JSON.stringify({ a: 1 })`) likewise. An
|
|
659
|
+
// UNMAPPABLE context falls back the same way — the literal's own type
|
|
660
|
+
// is the plain record the slot coerces. A CLASS-INSTANCE context
|
|
661
|
+
// (`const p: Point = { x, y }` — tsc's structural view of data
|
|
662
|
+
// classes) falls back too: the literal builds as its own record and
|
|
663
|
+
// the slot's width coercion constructs through the trivial
|
|
664
|
+
// parameter-property constructor (recordToClassPlan), or fences with
|
|
665
|
+
// the record-shape story.
|
|
666
|
+
// A jsval-mapped context reaches here only when lowerExpr's island
|
|
667
|
+
// gate DECLINED it (a project-declared typedef that absorbed to the
|
|
668
|
+
// island through checker-`any` field residue): the literal builds at
|
|
669
|
+
// its own type like every unmappable context.
|
|
670
|
+
if (mapped === null || mapped.kind === "union" || mapped.kind === "dyn" || mapped.kind === "object" || mapped.kind === "jsval") {
|
|
671
|
+
const ctxUnion = mapped?.kind === "union" ? mapped : null;
|
|
672
|
+
mapped = lowerer.mapTypeOf(lowerer.typeOf(expr)) ?? mapped;
|
|
673
|
+
// A literal whose own shape re-tags into NO arm of the contextual
|
|
674
|
+
// union, where the union has exactly ONE record arm: build AS that
|
|
675
|
+
// arm — there is no ambiguity (tsc already checked the literal
|
|
676
|
+
// against the union, and the record arm is the only shape it can
|
|
677
|
+
// inhabit), and the arm's field types drive every property's
|
|
678
|
+
// coercion (`{ env: {...spread...}, onCleanup }` against an
|
|
679
|
+
// optional options param — the env value builds by ITS contextual
|
|
680
|
+
// index-signature type and wraps into the arm's `| undefined`
|
|
681
|
+
// field, where the literal's own inferred width would mismatch).
|
|
682
|
+
// Empty literals (`_env = {}` — the fieldless own shape) and
|
|
683
|
+
// literals against PURE index-signature arms (`{ OPENSSL_CONF:
|
|
684
|
+
// candidate }` — keys become overflow entries) are the same rule's
|
|
685
|
+
// simplest cases. The empty-array-in-union rule, record form.
|
|
686
|
+
if (ctxUnion) {
|
|
687
|
+
const def = lowerer.unions.get(ctxUnion.unionId);
|
|
688
|
+
const recordArms = def?.arms.filter((a) => a.kind === "record") ?? [];
|
|
689
|
+
if (recordArms.length === 1) {
|
|
690
|
+
const armShape = lowerer.shapes.get(recordArms[0].shapeId);
|
|
691
|
+
if ((mapped?.kind !== "record" || mapped.shapeId !== recordArms[0].shapeId) &&
|
|
692
|
+
!armShape?.tuple) {
|
|
693
|
+
mapped = recordArms[0];
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
else if (recordArms.length > 1) {
|
|
697
|
+
const ownShapeId = mapped?.kind === "record" ? mapped.shapeId : null;
|
|
698
|
+
// SEVERAL record arms (the reducer-action / discriminated-message
|
|
699
|
+
// pattern): the literal's own inferred shape re-tags into no arm
|
|
700
|
+
// — its field types widened per field (`parsed: 1` against
|
|
701
|
+
// `parsed: number | null`), so the IR-level candidate probe is
|
|
702
|
+
// ambiguous (a narrower arm also admits the literal by dropping
|
|
703
|
+
// fields). The LITERAL types tsc checked carry the discriminant
|
|
704
|
+
// the shapes erased: match the literal's fields against each
|
|
705
|
+
// union member's CHECKER types (literal-vs-literal field pairs
|
|
706
|
+
// decide by value — the `kind: "a"` discriminant), and when
|
|
707
|
+
// exactly ONE member fits, build AS that arm — its field types
|
|
708
|
+
// drive every property's coercion, exactly the single-record-arm
|
|
709
|
+
// rule above. Ambiguous literals keep the SC2003 fence.
|
|
710
|
+
if (ownShapeId === null || !recordArms.some((a) => a.shapeId === ownShapeId)) {
|
|
711
|
+
const arm = literalUnionArmOf(lowerer, expr, tsType, recordArms);
|
|
712
|
+
if (arm)
|
|
713
|
+
mapped = arm;
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
// The CJS EXPORT-TABLE literal in VALUE position (JS): importers reach
|
|
719
|
+
// every member through alias plumbing and accessor lifts — the record
|
|
720
|
+
// VALUE exists for the module's own reads (Object.keys, internal
|
|
721
|
+
// member reads), so it keeps exactly the plain fields whose values
|
|
722
|
+
// lower; accessor entries and members with no value representation
|
|
723
|
+
// (rest-param function types) narrow away. SEMANTICS.md documents the
|
|
724
|
+
// enumeration divergence.
|
|
725
|
+
if (isJsSourceFile(expr.getSourceFile()) &&
|
|
726
|
+
(!mapped || expr.properties.some((p) => ts.isGetAccessorDeclaration(p) || ts.isSetAccessorDeclaration(p))) &&
|
|
727
|
+
isCjsExportTableLiteral(expr)) {
|
|
728
|
+
const fields = [];
|
|
729
|
+
for (const prop of expr.properties) {
|
|
730
|
+
if (ts.isGetAccessorDeclaration(prop) || ts.isSetAccessorDeclaration(prop))
|
|
731
|
+
continue;
|
|
732
|
+
const propName = ts.isSpreadAssignment(prop) ? undefined : prop.name;
|
|
733
|
+
if (!propName || !(ts.isIdentifier(propName) || ts.isStringLiteral(propName)))
|
|
734
|
+
continue;
|
|
735
|
+
const name = propName.text;
|
|
736
|
+
let v = null;
|
|
737
|
+
if (ts.isShorthandPropertyAssignment(prop)) {
|
|
738
|
+
v = tryLowerExpression(lowerer, propName);
|
|
739
|
+
}
|
|
740
|
+
else if (ts.isPropertyAssignment(prop)) {
|
|
741
|
+
v = tryLowerExpression(lowerer, prop.initializer);
|
|
742
|
+
}
|
|
743
|
+
if (!v || v.type.kind === "void" || v.type.kind === "caught" || v.type.kind === "jsval")
|
|
744
|
+
continue;
|
|
745
|
+
if (isUnitType(v.type))
|
|
746
|
+
continue;
|
|
747
|
+
fields.push({ name, value: v });
|
|
748
|
+
}
|
|
749
|
+
// Canonical (sorted) field order — the shape registry's invariant;
|
|
750
|
+
// the dropped reads are all pure, so reordering loses nothing.
|
|
751
|
+
fields.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
|
|
752
|
+
const shapeId = lowerer.shapes.intern(fields.map((f) => ({ name: f.name, type: f.value.type })));
|
|
753
|
+
return { kind: "recordLit", fields, type: { kind: "record", shapeId }, loc };
|
|
754
|
+
}
|
|
755
|
+
// The JS declaration fallback, literal-side (the checked-dynamic tree-array rule's
|
|
756
|
+
// object form): a literal whose shape has no static home — an
|
|
757
|
+
// unmappable contextual type over unmappable own fields (the
|
|
758
|
+
// PropertyDescriptorMap argument of Object.defineProperties, nested
|
|
759
|
+
// descriptor records with `any` values) — builds as a dyn OBJECT:
|
|
760
|
+
// each field converts through the usual dyn boundary, and dynamic
|
|
761
|
+
// consumers ride the keyed-dyn paths. TypeScript keeps the fence.
|
|
762
|
+
if ((!mapped || mapped.kind === "dyn") && isJsSourceFile(expr.getSourceFile())) {
|
|
763
|
+
return lowerDynObjectLiteral(lowerer, expr);
|
|
764
|
+
}
|
|
765
|
+
if (!mapped || mapped.kind !== "record")
|
|
766
|
+
lowerer.badType(expr, tsType);
|
|
767
|
+
let type = mapped;
|
|
768
|
+
let shape = lowerer.shapes.get(type.shapeId);
|
|
769
|
+
// ACCESSOR properties, JS literals only (TS accessors fill the shape's
|
|
770
|
+
// %get:/%set: closure slots below): no record storage exists for them,
|
|
771
|
+
// so the literal's shape NARROWS to its plain fields (reads resolve
|
|
772
|
+
// through the lifted accessors; Object.keys over the value omits
|
|
773
|
+
// accessor names — the documented divergence).
|
|
774
|
+
if (isJsSourceFile(expr.getSourceFile())) {
|
|
775
|
+
const accessorNames = new Set(expr.properties
|
|
776
|
+
.filter((p) => ts.isGetAccessorDeclaration(p) || ts.isSetAccessorDeclaration(p))
|
|
777
|
+
.map((p) => (p.name && (ts.isIdentifier(p.name) || ts.isStringLiteral(p.name)) ? p.name.text : ""))
|
|
778
|
+
.filter((n) => n !== ""));
|
|
779
|
+
if (accessorNames.size > 0) {
|
|
780
|
+
const narrowed = shape.fields.filter((f) => !accessorNames.has(f.name));
|
|
781
|
+
const narrowedId = lowerer.shapes.intern(narrowed.map((f) => ({ name: f.name, type: f.type })), false, shape.indexValue);
|
|
782
|
+
type = { kind: "record", shapeId: narrowedId };
|
|
783
|
+
shape = lowerer.shapes.get(narrowedId);
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
const fieldTypes = new Map(shape.fields.map((f) => [f.name, f.type]));
|
|
787
|
+
// File-scope JavaScript object bindings live in the checked-dynamic tree
|
|
788
|
+
// to preserve open writes and identity. Build an optional-field literal
|
|
789
|
+
// from its actually written keys so Object.hasOwn can distinguish an
|
|
790
|
+
// omitted property from a present property whose value is undefined.
|
|
791
|
+
const topLevelJsDecl = ts.isVariableDeclaration(expr.parent) &&
|
|
792
|
+
expr.parent.initializer === expr &&
|
|
793
|
+
ts.isVariableStatement(expr.parent.parent.parent) &&
|
|
794
|
+
ts.isSourceFile(expr.parent.parent.parent.parent);
|
|
795
|
+
const assignedProperties = expr.properties.filter((prop) => ts.isPropertyAssignment(prop) || ts.isShorthandPropertyAssignment(prop));
|
|
796
|
+
if (topLevelJsDecl && isJsSourceFile(expr.getSourceFile()) &&
|
|
797
|
+
assignedProperties.length === expr.properties.length) {
|
|
798
|
+
const provided = new Set(assignedProperties.map((prop) => propNameText(lowerer, prop.name)));
|
|
799
|
+
const omittedOptional = shape.fields.some((field) => !provided.has(field.name) && field.type.kind === "union" &&
|
|
800
|
+
lowerer.armTag(field.type.unionId, UNDEFINED_T) >= 0);
|
|
801
|
+
if (omittedOptional)
|
|
802
|
+
return lowerDynObjectLiteral(lowerer, expr);
|
|
803
|
+
}
|
|
804
|
+
// The clone probe may lower the leading spread before declining (a
|
|
805
|
+
// static `as` cast can make the checker-visible shape match while its
|
|
806
|
+
// erased IR value keeps a wider shape). Reuse that exact value in the
|
|
807
|
+
// ordinary spread path so lowering still happens once.
|
|
808
|
+
let leadingSpreadLowered = null;
|
|
809
|
+
// The overwhelmingly common immutable-update form over a record:
|
|
810
|
+
// `{ ...model, changed, other: value }`. The historic lowering expanded
|
|
811
|
+
// the spread into one recordGet per untouched field at EVERY site, then
|
|
812
|
+
// the backends inlined all of those retains and stores into the caller.
|
|
813
|
+
// A 178-field model updated hundreds of times consequently produced a
|
|
814
|
+
// half-million-line LLVM function. Keep the same evaluation/ownership
|
|
815
|
+
// semantics in one compact IR node: source first, explicit overrides in
|
|
816
|
+
// source order, and one backend clone helper per shape.
|
|
817
|
+
//
|
|
818
|
+
// Stay deliberately narrow. Tuple/index/accessor shapes, conditional or
|
|
819
|
+
// multiple spreads, spread-after-explicit order, and shape-changing width
|
|
820
|
+
// copies retain their existing lowering and diagnostics.
|
|
821
|
+
if (!isJsSourceFile(expr.getSourceFile()) &&
|
|
822
|
+
!shape.indexValue &&
|
|
823
|
+
!shape.tuple &&
|
|
824
|
+
!shapeHasAccessorSlots(shape) &&
|
|
825
|
+
expr.properties.length >= 2 &&
|
|
826
|
+
ts.isSpreadAssignment(expr.properties[0]) &&
|
|
827
|
+
!conditionalSpreadOf(expr.properties[0].expression) &&
|
|
828
|
+
expr.properties.slice(1).every((p) => (ts.isPropertyAssignment(p) || ts.isShorthandPropertyAssignment(p)) &&
|
|
829
|
+
p.name !== undefined &&
|
|
830
|
+
(ts.isIdentifier(p.name) ||
|
|
831
|
+
ts.isStringLiteral(p.name) ||
|
|
832
|
+
ts.isNumericLiteral(p.name) ||
|
|
833
|
+
(ts.isComputedPropertyName(p.name) && literalComputedKey(lowerer, p.name) !== null)))) {
|
|
834
|
+
const spread = expr.properties[0];
|
|
835
|
+
const props = expr.properties.slice(1);
|
|
836
|
+
const names = props.map((p) => propNameText(lowerer, p.name));
|
|
837
|
+
const unique = new Set(names);
|
|
838
|
+
const sourceType = lowerer.mapTypeOf(lowerer.typeOf(spread.expression));
|
|
839
|
+
if (sourceType?.kind === "record" &&
|
|
840
|
+
sourceType.shapeId === type.shapeId &&
|
|
841
|
+
unique.size === names.length &&
|
|
842
|
+
names.every((name) => fieldTypes.has(name))) {
|
|
843
|
+
const source = lowerer.lowerExpr(spread.expression);
|
|
844
|
+
if (source.type.kind === "record" && source.type.shapeId === type.shapeId) {
|
|
845
|
+
const overrides = [];
|
|
846
|
+
for (let i = 0; i < props.length; i++) {
|
|
847
|
+
const p = props[i];
|
|
848
|
+
const name = names[i];
|
|
849
|
+
const fieldType = fieldTypes.get(name);
|
|
850
|
+
const valueNode = ts.isPropertyAssignment(p) ? p.initializer : p;
|
|
851
|
+
let value = ts.isPropertyAssignment(p)
|
|
852
|
+
? lowerer.lowerExpr(p.initializer)
|
|
853
|
+
: lowerer.lowerShorthandValue(p);
|
|
854
|
+
value = lowerer.coerceInto(valueNode, value, fieldType);
|
|
855
|
+
if (!typeEquals(value.type, fieldType))
|
|
856
|
+
lowerer.badType(valueNode, lowerer.typeOf(valueNode));
|
|
857
|
+
overrides.push({ name, value });
|
|
858
|
+
}
|
|
859
|
+
return { kind: "recordClone", source, overrides, type, loc };
|
|
860
|
+
}
|
|
861
|
+
leadingSpreadLowered = source;
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
// A PURE index-signature target with spreads — `{ ...process.env }`,
|
|
865
|
+
// `{ ...process.env, ...extraEnv }`, `{ ...env, PATH: p }` (the
|
|
866
|
+
// spawn-env pattern): the field-by-field desugar below cannot
|
|
867
|
+
// enumerate runtime overflow keys, so the literal lowers as ONE
|
|
868
|
+
// interned merge-helper call — contributors apply in literal order
|
|
869
|
+
// with keyed writes (JS last-write-wins), sources and values evaluate
|
|
870
|
+
// once each in source order (the call's argument order). A CONDITIONAL
|
|
871
|
+
// spread `...(c ? { k: v } : {})` contributes its one key as a ternary
|
|
872
|
+
// — cond once, v lazily, the empty arm holding the value slot's
|
|
873
|
+
// undefined arm (the explicit-undefined-is-absent stance: JSON and
|
|
874
|
+
// child-env builders drop it, exactly Node's absent key); targets
|
|
875
|
+
// with declared fields keep the historic desugar below.
|
|
876
|
+
const isRuntimeComputedKey = (p) => !ts.isSpreadAssignment(p) &&
|
|
877
|
+
p.name !== undefined &&
|
|
878
|
+
ts.isComputedPropertyName(p.name) &&
|
|
879
|
+
literalComputedKey(lowerer, p.name) === null;
|
|
880
|
+
if (shape.indexValue &&
|
|
881
|
+
shape.fields.length === 0 &&
|
|
882
|
+
!shape.tuple &&
|
|
883
|
+
(expr.properties.some((p) => ts.isSpreadAssignment(p)) || expr.properties.some(isRuntimeComputedKey))) {
|
|
884
|
+
const contributors = [];
|
|
885
|
+
let mergeable = true;
|
|
886
|
+
for (const prop of expr.properties) {
|
|
887
|
+
if (ts.isSpreadAssignment(prop)) {
|
|
888
|
+
const cs = conditionalSpreadOf(prop.expression);
|
|
889
|
+
if (cs === "unsupported" || (cs && cs.props.length !== 1)) {
|
|
890
|
+
lowerer.unsupported("SC1090", prop, "conditional spreads beyond `...(c ? { field: v } : {})` (exactly one property against an empty arm)");
|
|
891
|
+
}
|
|
892
|
+
if (cs) {
|
|
893
|
+
const absent = lowerer.wrappedUndefined(shape.indexValue, locOf(prop));
|
|
894
|
+
if (!absent) {
|
|
895
|
+
lowerer.unsupported("SC1090", prop, `conditional spreads into '${lowerer.fmt(shape.indexValue)}'-valued index-signature keys (the empty arm needs an undefined arm to hold — write the key in an if statement instead)`);
|
|
896
|
+
}
|
|
897
|
+
const csProp = cs.props[0];
|
|
898
|
+
const cond = lowerer.lowerCondition(cs.cond);
|
|
899
|
+
const vNode = ts.isPropertyAssignment(csProp) ? csProp.initializer : csProp;
|
|
900
|
+
const v = lowerer.intoIndexValueSlot(ts.isPropertyAssignment(csProp) ? lowerer.lowerExpr(csProp.initializer) : lowerer.lowerShorthandValue(csProp), shape.indexValue, vNode);
|
|
901
|
+
contributors.push({
|
|
902
|
+
kind: "field",
|
|
903
|
+
name: csProp.name.text,
|
|
904
|
+
value: {
|
|
905
|
+
kind: "ternary",
|
|
906
|
+
cond,
|
|
907
|
+
then: cs.whenTrue ? v : absent,
|
|
908
|
+
else_: cs.whenTrue ? absent : v,
|
|
909
|
+
type: shape.indexValue,
|
|
910
|
+
loc: locOf(prop),
|
|
911
|
+
},
|
|
912
|
+
});
|
|
913
|
+
continue;
|
|
914
|
+
}
|
|
915
|
+
let src = lowerer.lowerExpr(prop.expression);
|
|
916
|
+
src = lowerOptionalIndexSpreadSource(lowerer, src, locOf(prop)) ?? src;
|
|
917
|
+
if (src.type.kind !== "record") {
|
|
918
|
+
lowerer.unsupported("SC1090", prop, `object spread of '${lowerer.fmt(src.type)}' sources into an index-signature shape (only index-signature records merge — ${NARROW_FIRST})`);
|
|
919
|
+
}
|
|
920
|
+
fenceAccessorSpreadSource(lowerer, prop, lowerer.shapes.get(src.type.shapeId));
|
|
921
|
+
contributors.push({ kind: "spread", shapeId: src.type.shapeId, value: src });
|
|
922
|
+
continue;
|
|
923
|
+
}
|
|
924
|
+
if ((ts.isPropertyAssignment(prop) || ts.isShorthandPropertyAssignment(prop)) &&
|
|
925
|
+
prop.name &&
|
|
926
|
+
(ts.isIdentifier(prop.name) ||
|
|
927
|
+
ts.isStringLiteral(prop.name) ||
|
|
928
|
+
ts.isNumericLiteral(prop.name) ||
|
|
929
|
+
(ts.isComputedPropertyName(prop.name) && literalComputedKey(lowerer, prop.name) !== null))) {
|
|
930
|
+
const value = ts.isPropertyAssignment(prop)
|
|
931
|
+
? lowerer.intoIndexValueSlot(lowerer.lowerExpr(prop.initializer), shape.indexValue, prop.initializer)
|
|
932
|
+
: lowerer.intoIndexValueSlot(lowerer.lowerShorthandValue(prop), shape.indexValue, prop);
|
|
933
|
+
contributors.push({ kind: "field", name: propNameText(lowerer, prop.name), value });
|
|
934
|
+
continue;
|
|
935
|
+
}
|
|
936
|
+
// A RUNTIME-keyed property (`{ ...m, ["a" + "b"]: "" }`, `{ [K]:
|
|
937
|
+
// v }` where K is a runtime string): the key evaluates before its
|
|
938
|
+
// value (JS's per-property order — both are helper arguments) and
|
|
939
|
+
// writes through the signature exactly like a spread's keys.
|
|
940
|
+
// Number/boolean/unknown keys stringify (ToPropertyKey).
|
|
941
|
+
if (ts.isPropertyAssignment(prop) && ts.isComputedPropertyName(prop.name)) {
|
|
942
|
+
let k = lowerer.lowerExpr(prop.name.expression);
|
|
943
|
+
if (k.type.kind === "f64" || k.type.kind === "bool" || k.type.kind === "dyn") {
|
|
944
|
+
k = { kind: "toString", operand: k, type: STRING, loc: locOf(prop.name) };
|
|
945
|
+
}
|
|
946
|
+
if (k.type.kind !== "string") {
|
|
947
|
+
lowerer.unsupported("SC1090", prop.name, `'${lowerer.fmt(k.type)}'-typed computed property keys (string, number, boolean, and unknown keys stringify)`);
|
|
948
|
+
}
|
|
949
|
+
const value = lowerer.intoIndexValueSlot(lowerer.lowerExpr(prop.initializer), shape.indexValue, prop.initializer);
|
|
950
|
+
contributors.push({ kind: "keyedField", key: k, value });
|
|
951
|
+
continue;
|
|
952
|
+
}
|
|
953
|
+
mergeable = false;
|
|
954
|
+
break;
|
|
955
|
+
}
|
|
956
|
+
if (mergeable) {
|
|
957
|
+
const helper = lowerIndexMergeHelper(lowerer, type.shapeId, contributors, loc);
|
|
958
|
+
if (helper === null) {
|
|
959
|
+
lowerer.unsupported("SC1090", expr, `object spread into '${lowerer.fmt(type)}' from these source shapes (spread sources must be index-signature records whose value type is, or lifts into, the target's)`);
|
|
960
|
+
}
|
|
961
|
+
return {
|
|
962
|
+
kind: "call",
|
|
963
|
+
callee: helper,
|
|
964
|
+
args: contributors.flatMap((c) => (c.kind === "keyedField" ? [c.key, c.value] : [c.value])),
|
|
965
|
+
type,
|
|
966
|
+
loc,
|
|
967
|
+
};
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
// Unfoldable computed keys outside the index-signature merge path:
|
|
971
|
+
// no record shape can hold a runtime-decided name.
|
|
972
|
+
for (const prop of expr.properties) {
|
|
973
|
+
if (isRuntimeComputedKey(prop)) {
|
|
974
|
+
lowerer.unsupported("SC1090", prop.name, "computed property keys (compile-time-known keys fold — a pure expression whose checker type is one string or number literal: consts, enum members, quoted keys, templates of those — `{ [MARKER]: v }`; runtime string keys write through an index-signature target; symbol keys stay out)");
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
// A DECLARED-fields target built ENTIRELY from index-signature spreads —
|
|
978
|
+
// `{ ...Object.fromEntries(...), ...Object.fromEntries(...) }` typed
|
|
979
|
+
// AppConfig (the defaults-merge idiom over runtime-keyed sources): the
|
|
980
|
+
// field-by-field desugar cannot enumerate runtime keys, so the literal
|
|
981
|
+
// lowers as ONE interned merge-helper call — sources evaluate once each
|
|
982
|
+
// in source order (the call's argument order — computed sources
|
|
983
|
+
// included, no re-read), contributors apply in order with per-key
|
|
984
|
+
// dispatch onto the declared fields (JS last-write-wins). Divergence 68
|
|
985
|
+
// has the runtime rules (validated collisions, extra keys dropped).
|
|
986
|
+
if (!shape.indexValue &&
|
|
987
|
+
!shape.tuple &&
|
|
988
|
+
shape.fields.length > 0 &&
|
|
989
|
+
expr.properties.length > 0 &&
|
|
990
|
+
expr.properties.every((p) => ts.isSpreadAssignment(p) && !conditionalSpreadOf(p.expression)) &&
|
|
991
|
+
expr.properties.some((p) => {
|
|
992
|
+
const t = lowerer.mapTypeOf(lowerer.typeOf(p.expression));
|
|
993
|
+
return t?.kind === "record" && !!lowerer.shapes.get(t.shapeId)?.indexValue;
|
|
994
|
+
})) {
|
|
995
|
+
return lowerDeclaredSpreadMerge(lowerer, expr, type, shape, loc);
|
|
996
|
+
}
|
|
997
|
+
const shapeMismatch = (node) => {
|
|
998
|
+
const own = lowerer.mapTypeOf(lowerer.typeOf(expr));
|
|
999
|
+
lowerer.pushDiag(recordShapeMismatchDiag(lowerer.fmt(type), own ? lowerer.fmt(own) : lowerer.checker.typeToString(lowerer.typeOf(expr)), locOf(node), own?.kind === "record" && type.kind === "record"
|
|
1000
|
+
? (lowerer.describeRecordWidthBlocker(own.shapeId, type.shapeId) ?? undefined)
|
|
1001
|
+
: undefined));
|
|
1002
|
+
throw new PoisonError();
|
|
1003
|
+
};
|
|
1004
|
+
const fields = [];
|
|
1005
|
+
// Field names introduced by conditional spreads: their ternary carries
|
|
1006
|
+
// the spread's whole evaluation (cond once, value lazily), so a LATER
|
|
1007
|
+
// contributor overriding one would silently drop that evaluation —
|
|
1008
|
+
// collisions fence in both directions.
|
|
1009
|
+
const conditionalNames = new Set();
|
|
1010
|
+
// Member names DROPPED from the shape (JS deferral — unlowerable pure
|
|
1011
|
+
// member reads narrow away; see the catch in the property loop).
|
|
1012
|
+
const droppedNames = new Set();
|
|
1013
|
+
for (const prop of expr.properties) {
|
|
1014
|
+
if (ts.isSpreadAssignment(prop)) {
|
|
1015
|
+
const cs = conditionalSpreadOf(prop.expression);
|
|
1016
|
+
if (cs && cs !== "unsupported") {
|
|
1017
|
+
// `...(c ? { k: v } : {})` — ONE conditional field at the
|
|
1018
|
+
// spread's own position: cond evaluates exactly once, `v` only
|
|
1019
|
+
// when the non-empty arm is taken (ternary arms are lazy), and
|
|
1020
|
+
// the empty arm holds the interned undefined arm — exactly the
|
|
1021
|
+
// omitted-optional-field representation, which is also why the
|
|
1022
|
+
// target field must be optional (tsc types the idiom that way).
|
|
1023
|
+
const csProp = cs.props[0]; // single-prop-checked in the fence pass
|
|
1024
|
+
const name = csProp.name.text;
|
|
1025
|
+
const fieldType = fieldTypes.get(name);
|
|
1026
|
+
if (!fieldType) {
|
|
1027
|
+
if (shape.indexValue) {
|
|
1028
|
+
lowerer.unsupported("SC1090", prop, "conditional spreads into index-signature keys (overflow entries model presence — write the key in an if statement instead)");
|
|
1029
|
+
}
|
|
1030
|
+
throw shapeMismatch(prop);
|
|
1031
|
+
}
|
|
1032
|
+
const absent = lowerer.wrappedUndefined(fieldType, locOf(prop));
|
|
1033
|
+
if (!absent) {
|
|
1034
|
+
lowerer.unsupported("SC1090", prop, `conditional spreads onto the required field '${name}' (the empty arm leaves it undefined — declare the field optional)`);
|
|
1035
|
+
}
|
|
1036
|
+
if (fields.some((f) => f.name === name)) {
|
|
1037
|
+
lowerer.unsupported("SC1090", prop, `conditional spread of '${name}' over an earlier '${name}' (the desugar keeps one entry per name — restructure so each name has one contributor)`);
|
|
1038
|
+
}
|
|
1039
|
+
const cond = lowerer.lowerCondition(cs.cond);
|
|
1040
|
+
const valueNode = ts.isPropertyAssignment(csProp) ? csProp.initializer : csProp;
|
|
1041
|
+
let v = ts.isPropertyAssignment(csProp)
|
|
1042
|
+
? lowerer.lowerExpr(csProp.initializer)
|
|
1043
|
+
: lowerer.lowerShorthandValue(csProp);
|
|
1044
|
+
v = lowerer.coerceInto(valueNode, v, fieldType);
|
|
1045
|
+
if (!typeEquals(v.type, fieldType))
|
|
1046
|
+
lowerer.badType(valueNode, lowerer.typeOf(valueNode));
|
|
1047
|
+
conditionalNames.add(name);
|
|
1048
|
+
fields.push({
|
|
1049
|
+
name,
|
|
1050
|
+
value: {
|
|
1051
|
+
kind: "ternary",
|
|
1052
|
+
cond,
|
|
1053
|
+
then: cs.whenTrue ? v : absent,
|
|
1054
|
+
else_: cs.whenTrue ? absent : v,
|
|
1055
|
+
type: fieldType,
|
|
1056
|
+
loc: locOf(prop),
|
|
1057
|
+
},
|
|
1058
|
+
});
|
|
1059
|
+
continue;
|
|
1060
|
+
}
|
|
1061
|
+
// `{ ...base, ... }` — field-by-field copy of a known record
|
|
1062
|
+
// shape. Every source field must land on the target shape with an
|
|
1063
|
+
// equal type (a wider source would silently DROP fields JS keeps —
|
|
1064
|
+
// the width fence, same as literals). Later contributors override
|
|
1065
|
+
// earlier ones (JS last-write-wins; the reads are side-effect-free,
|
|
1066
|
+
// so dropping the earlier read is exact). Identifier sources
|
|
1067
|
+
// re-read per field (historic path); any OTHER source must be a
|
|
1068
|
+
// re-emittable pure read, sharing one lowered node per field.
|
|
1069
|
+
// The desugar's one-entry-per-name list reads spread fields
|
|
1070
|
+
// EAGERLY at the spread's position, so an explicit property
|
|
1071
|
+
// BEFORE a spread would need JS's overwrite — order-fenced (the
|
|
1072
|
+
// index-signature merge path above takes any order).
|
|
1073
|
+
if (expr.properties
|
|
1074
|
+
.slice(0, expr.properties.indexOf(prop))
|
|
1075
|
+
.some((p) => !ts.isSpreadAssignment(p))) {
|
|
1076
|
+
lowerer.unsupported("SC1090", prop, "object spread after explicit properties (spreads must come first — a later spread would overwrite them with JS semantics the desugar does not model)");
|
|
1077
|
+
}
|
|
1078
|
+
let srcNode = prop.expression;
|
|
1079
|
+
while (ts.isParenthesizedExpression(srcNode))
|
|
1080
|
+
srcNode = srcNode.expression;
|
|
1081
|
+
const srcLowered = prop === expr.properties[0] && leadingSpreadLowered !== null
|
|
1082
|
+
? leadingSpreadLowered
|
|
1083
|
+
: ts.isIdentifier(srcNode)
|
|
1084
|
+
? null
|
|
1085
|
+
: lowerer.lowerExpr(srcNode);
|
|
1086
|
+
const srcType = srcLowered ? srcLowered.type : lowerer.mapTypeOf(lowerer.typeOf(srcNode));
|
|
1087
|
+
// `...options.installConfig` — a spread of `Partial<X> | undefined`
|
|
1088
|
+
// (the optional-options merge idiom `{ ...DEFAULTS, ...overrides }`):
|
|
1089
|
+
// JS spreads nothing for the unit arm and copies present keys
|
|
1090
|
+
// otherwise. Per target field the desugar builds
|
|
1091
|
+
// `present ? extracted : earlier` — present tests the source's
|
|
1092
|
+
// record tag AND (optional source fields) the field's own value
|
|
1093
|
+
// arm; absent keeps the earlier contributor's value (both reads
|
|
1094
|
+
// are pure, so the reorder into the ternary is unobservable).
|
|
1095
|
+
if (srcType?.kind === "union") {
|
|
1096
|
+
const def = lowerer.unions.get(srcType.unionId);
|
|
1097
|
+
const recArms = def?.arms.filter((a) => a.kind === "record") ?? [];
|
|
1098
|
+
if (!def ||
|
|
1099
|
+
recArms.length !== 1 ||
|
|
1100
|
+
!def.arms.every((a) => a.kind === "record" || isUnitType(a))) {
|
|
1101
|
+
lowerer.unsupported("SC1090", prop, `object spread of '${lowerer.fmt(srcType)}' sources (only known record shapes spread — ${NARROW_FIRST})`);
|
|
1102
|
+
}
|
|
1103
|
+
if (srcLowered && !isSafeToRepeat(srcLowered)) {
|
|
1104
|
+
lowerer.unsupported("SC1090", prop, "object spread of computed sources (the field copies re-read the source — bind it to a const first)");
|
|
1105
|
+
}
|
|
1106
|
+
const recArm = recArms[0];
|
|
1107
|
+
const recTag = def.arms.indexOf(recArm);
|
|
1108
|
+
// A checked-dynamic VALUE under the union-mapped checker type
|
|
1109
|
+
// (a JS dyn-holding binding): the present/absent desugar tests
|
|
1110
|
+
// union tags a dyn box does not carry — fence honestly instead
|
|
1111
|
+
// of the validator's ICE.
|
|
1112
|
+
const probedSrc = srcLowered ?? tryLowerExpression(lowerer, srcNode);
|
|
1113
|
+
if (probedSrc?.type.kind === "dyn") {
|
|
1114
|
+
lowerer.unsupported("SC1090", prop, `object spread of a checked-dynamic '${lowerer.fmt(srcType)}' source (${NARROW_FIRST})`);
|
|
1115
|
+
}
|
|
1116
|
+
const srcShape = lowerer.shapes.get(recArm.shapeId);
|
|
1117
|
+
if (!srcShape)
|
|
1118
|
+
throw new InternalCompilerError(`lowerer bug: spread of unknown shape ${recArm.shapeId}`);
|
|
1119
|
+
fenceAccessorSpreadSource(lowerer, prop, srcShape);
|
|
1120
|
+
if (srcShape.indexValue || shape.indexValue) {
|
|
1121
|
+
lowerer.unsupported("SC1090", prop, "object spread involving index-signature shapes (overflow keys are runtime state — copy the fields you need explicitly)");
|
|
1122
|
+
}
|
|
1123
|
+
const laterNames = new Set();
|
|
1124
|
+
for (const later of expr.properties.slice(expr.properties.indexOf(prop) + 1)) {
|
|
1125
|
+
if (ts.isSpreadAssignment(later)) {
|
|
1126
|
+
if (conditionalSpreadOf(later.expression))
|
|
1127
|
+
continue;
|
|
1128
|
+
const lt = lowerer.mapTypeOf(lowerer.typeOf(later.expression));
|
|
1129
|
+
if (lt?.kind === "record") {
|
|
1130
|
+
for (const lf of lowerer.shapes.get(lt.shapeId)?.fields ?? [])
|
|
1131
|
+
laterNames.add(lf.name);
|
|
1132
|
+
}
|
|
1133
|
+
continue;
|
|
1134
|
+
}
|
|
1135
|
+
if (later.name &&
|
|
1136
|
+
(ts.isIdentifier(later.name) ||
|
|
1137
|
+
ts.isStringLiteral(later.name) ||
|
|
1138
|
+
ts.isNumericLiteral(later.name) ||
|
|
1139
|
+
(ts.isComputedPropertyName(later.name) && literalComputedKey(lowerer, later.name) !== null))) {
|
|
1140
|
+
laterNames.add(propNameText(lowerer, later.name));
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1143
|
+
const srcRef = () => srcLowered ?? lowerer.lowerExpr(srcNode);
|
|
1144
|
+
for (const f of srcShape.fields) {
|
|
1145
|
+
if (laterNames.has(f.name))
|
|
1146
|
+
continue;
|
|
1147
|
+
const targetType = fieldTypes.get(f.name);
|
|
1148
|
+
// No slot on the target shape: the copy DROPS the field
|
|
1149
|
+
// (divergence 36's stance, same as the plain-record spread).
|
|
1150
|
+
if (!targetType)
|
|
1151
|
+
continue;
|
|
1152
|
+
if (conditionalNames.has(f.name)) {
|
|
1153
|
+
lowerer.unsupported("SC1090", prop, `spread of '${f.name}' over an earlier conditional spread (the desugar keeps one entry per name — restructure so each name has one contributor)`);
|
|
1154
|
+
}
|
|
1155
|
+
const fRead = () => ({
|
|
1156
|
+
kind: "recordGet",
|
|
1157
|
+
obj: { kind: "unionNarrow", unionId: srcType.unionId, tag: recTag, value: srcRef(), type: recArm, loc: locOf(prop) },
|
|
1158
|
+
shapeId: recArm.shapeId,
|
|
1159
|
+
field: f.name,
|
|
1160
|
+
type: f.type,
|
|
1161
|
+
loc: locOf(prop),
|
|
1162
|
+
});
|
|
1163
|
+
let cond = { kind: "unionIsTag", unionId: srcType.unionId, tag: recTag, negated: false, value: srcRef(), type: BOOL, loc: locOf(prop) };
|
|
1164
|
+
let thenVal;
|
|
1165
|
+
if (typeEquals(f.type, targetType)) {
|
|
1166
|
+
thenVal = fRead();
|
|
1167
|
+
}
|
|
1168
|
+
else if (f.type.kind === "union" &&
|
|
1169
|
+
lowerer.armTag(f.type.unionId, UNDEFINED_T) >= 0 &&
|
|
1170
|
+
typeEquals(lowerer.stripUndefinedArm(f.type), targetType)) {
|
|
1171
|
+
// Optional source field into a required target slot: present
|
|
1172
|
+
// means the source holds the record AND the field its value
|
|
1173
|
+
// arm — the spread-override completion, union-source form.
|
|
1174
|
+
const ftUndef = lowerer.armTag(f.type.unionId, UNDEFINED_T);
|
|
1175
|
+
cond = {
|
|
1176
|
+
kind: "logical",
|
|
1177
|
+
op: "&&",
|
|
1178
|
+
left: cond,
|
|
1179
|
+
right: { kind: "unionIsTag", unionId: f.type.unionId, tag: ftUndef, negated: true, value: fRead(), type: BOOL, loc: locOf(prop) },
|
|
1180
|
+
type: BOOL,
|
|
1181
|
+
loc: locOf(prop),
|
|
1182
|
+
};
|
|
1183
|
+
const ftDef = lowerer.unions.get(f.type.unionId);
|
|
1184
|
+
if (targetType.kind === "union") {
|
|
1185
|
+
const retag = lowerer.unionRetagHelper(f.type.unionId, targetType.unionId, locOf(prop));
|
|
1186
|
+
if (!retag) {
|
|
1187
|
+
lowerer.pushDiag(recordShapeMismatchDiag(lowerer.fmt(type), lowerer.fmt(recArm), locOf(prop), `spread field '${f.name}': '${lowerer.fmt(f.type)}' cannot re-tag into '${lowerer.fmt(targetType)}' behind the present-test`));
|
|
1188
|
+
throw new PoisonError();
|
|
1189
|
+
}
|
|
1190
|
+
thenVal = { kind: "call", callee: retag, args: [fRead()], type: targetType, loc: locOf(prop) };
|
|
1191
|
+
}
|
|
1192
|
+
else if (ftDef && ftDef.arms.length === 2 && lowerer.armTag(f.type.unionId, targetType) >= 0) {
|
|
1193
|
+
thenVal = { kind: "unionNarrow", unionId: f.type.unionId, tag: lowerer.armTag(f.type.unionId, targetType), value: fRead(), type: targetType, loc: locOf(prop) };
|
|
1194
|
+
}
|
|
1195
|
+
else {
|
|
1196
|
+
lowerer.pushDiag(recordShapeMismatchDiag(lowerer.fmt(type), lowerer.fmt(recArm), locOf(prop), `spread field '${f.name}': '${lowerer.fmt(f.type)}' cannot narrow into '${lowerer.fmt(targetType)}' behind the present-test`));
|
|
1197
|
+
throw new PoisonError();
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
else {
|
|
1201
|
+
// The width-lift fallback (arm wrap, re-tag, nested
|
|
1202
|
+
// reshape) — the same per-field rule the slot coercion
|
|
1203
|
+
// applies. Runs AFTER the optional-completion branch: a
|
|
1204
|
+
// present-test has its own semantics a re-tag's stranded
|
|
1205
|
+
// undefined trap must not shadow.
|
|
1206
|
+
const lift = lowerer.widthLiftPlan(f.type, targetType);
|
|
1207
|
+
if (!lift) {
|
|
1208
|
+
lowerer.pushDiag(recordShapeMismatchDiag(lowerer.fmt(type), lowerer.fmt(recArm), locOf(prop), `spread field '${f.name}': '${lowerer.fmt(f.type)}' does not lift into '${lowerer.fmt(targetType)}'`));
|
|
1209
|
+
throw new PoisonError();
|
|
1210
|
+
}
|
|
1211
|
+
thenVal = lowerer.applyWidthLift(lift, fRead(), targetType, locOf(prop));
|
|
1212
|
+
}
|
|
1213
|
+
const at = fields.findIndex((x) => x.name === f.name && !x.drop);
|
|
1214
|
+
const elseVal = at >= 0 ? fields[at].value : lowerer.wrappedUndefined(targetType, locOf(prop));
|
|
1215
|
+
if (!elseVal) {
|
|
1216
|
+
lowerer.unsupported("SC1090", prop, `object spread of '${lowerer.fmt(srcType)}' sources where '${f.name}' has no earlier contributor (the absent arm leaves the required field unset — spread defaults first: { ...defaults, ...overrides })`);
|
|
1217
|
+
}
|
|
1218
|
+
const merged = { kind: "ternary", cond, then: thenVal, else_: elseVal, type: targetType, loc: locOf(prop) };
|
|
1219
|
+
if (at >= 0)
|
|
1220
|
+
fields[at] = { name: f.name, value: merged };
|
|
1221
|
+
else
|
|
1222
|
+
fields.push({ name: f.name, value: merged });
|
|
1223
|
+
}
|
|
1224
|
+
continue;
|
|
1225
|
+
}
|
|
1226
|
+
if (srcType?.kind !== "record") {
|
|
1227
|
+
lowerer.unsupported("SC1090", prop, `object spread of '${srcType ? lowerer.fmt(srcType) : lowerer.checker.typeToString(lowerer.typeOf(srcNode))}' sources (only known record shapes spread — ${NARROW_FIRST})`);
|
|
1228
|
+
}
|
|
1229
|
+
if (srcLowered && !isSafeToRepeat(srcLowered)) {
|
|
1230
|
+
lowerer.unsupported("SC1090", prop, "object spread of computed sources (the field copies re-read the source — bind it to a const first)");
|
|
1231
|
+
}
|
|
1232
|
+
const srcShape = lowerer.shapes.get(srcType.shapeId);
|
|
1233
|
+
if (!srcShape)
|
|
1234
|
+
throw new InternalCompilerError(`lowerer bug: spread of unknown shape ${srcType.shapeId}`);
|
|
1235
|
+
fenceAccessorSpreadSource(lowerer, prop, srcShape);
|
|
1236
|
+
// Index-signature shapes carry runtime-keyed overflow entries the
|
|
1237
|
+
// field-by-field desugar cannot enumerate — fenced on either side.
|
|
1238
|
+
if (srcShape.indexValue || shape.indexValue) {
|
|
1239
|
+
lowerer.unsupported("SC1090", prop, "object spread involving index-signature shapes (overflow keys are runtime state — copy the fields you need explicitly)");
|
|
1240
|
+
}
|
|
1241
|
+
// Names a LATER contributor unconditionally defines: copying such a
|
|
1242
|
+
// source field is dead under JS last-write-wins (and spread reads
|
|
1243
|
+
// are side-effect-free), so it neither lowers nor width-checks —
|
|
1244
|
+
// the spread-then-override completion `{ ...config, stateDir }`
|
|
1245
|
+
// narrows an optional source field into a required target slot
|
|
1246
|
+
// exactly like Node does. Conditional spreads don't count (their
|
|
1247
|
+
// empty arm defines nothing) — their own collision fences hold.
|
|
1248
|
+
const laterNames = new Set();
|
|
1249
|
+
for (const later of expr.properties.slice(expr.properties.indexOf(prop) + 1)) {
|
|
1250
|
+
if (ts.isSpreadAssignment(later)) {
|
|
1251
|
+
if (conditionalSpreadOf(later.expression))
|
|
1252
|
+
continue;
|
|
1253
|
+
const lt = lowerer.mapTypeOf(lowerer.typeOf(later.expression));
|
|
1254
|
+
if (lt?.kind === "record") {
|
|
1255
|
+
for (const lf of lowerer.shapes.get(lt.shapeId)?.fields ?? [])
|
|
1256
|
+
laterNames.add(lf.name);
|
|
1257
|
+
}
|
|
1258
|
+
continue;
|
|
1259
|
+
}
|
|
1260
|
+
if (later.name &&
|
|
1261
|
+
(ts.isIdentifier(later.name) ||
|
|
1262
|
+
ts.isStringLiteral(later.name) ||
|
|
1263
|
+
ts.isNumericLiteral(later.name) ||
|
|
1264
|
+
(ts.isComputedPropertyName(later.name) && literalComputedKey(lowerer, later.name) !== null))) {
|
|
1265
|
+
laterNames.add(propNameText(lowerer, later.name));
|
|
1266
|
+
}
|
|
1267
|
+
}
|
|
1268
|
+
for (const f of srcShape.fields) {
|
|
1269
|
+
if (laterNames.has(f.name))
|
|
1270
|
+
continue;
|
|
1271
|
+
const targetType = fieldTypes.get(f.name);
|
|
1272
|
+
// A source field with NO slot on the target shape: the copy
|
|
1273
|
+
// DROPS it — a spread of a wider record into a narrower literal
|
|
1274
|
+
// is width subtyping in spread clothing, divergence 36's stance
|
|
1275
|
+
// (Node's object would keep the key); the read is pure, so
|
|
1276
|
+
// skipping evaluates nothing.
|
|
1277
|
+
if (!targetType)
|
|
1278
|
+
continue;
|
|
1279
|
+
const lift = typeEquals(f.type, targetType)
|
|
1280
|
+
? null
|
|
1281
|
+
: lowerer.widthLiftPlan(f.type, targetType);
|
|
1282
|
+
if (!typeEquals(f.type, targetType) && !lift) {
|
|
1283
|
+
// Print the SOURCE shape, not the literal's own type: the
|
|
1284
|
+
// checker's own type already has later overrides applied, so
|
|
1285
|
+
// it can render identically to the target while the spread
|
|
1286
|
+
// source (the thing that actually mismatches) differs — an
|
|
1287
|
+
// invisible difference is a diagnostics bug.
|
|
1288
|
+
lowerer.pushDiag(recordShapeMismatchDiag(lowerer.fmt(type), lowerer.fmt(srcType), locOf(prop), `spread field '${f.name}': '${lowerer.fmt(f.type)}' does not lift into '${lowerer.fmt(targetType)}'`));
|
|
1289
|
+
throw new PoisonError();
|
|
1290
|
+
}
|
|
1291
|
+
const obj = srcLowered ?? lowerer.lowerExpr(srcNode);
|
|
1292
|
+
// A record-mapped CHECKER type whose VALUE lives in the checked-dynamic tree (a
|
|
1293
|
+
// JS file-scope object-literal global): read each field from
|
|
1294
|
+
// the checked-dynamic tree (dynKeyGet) and VALIDATE it into the source shape's
|
|
1295
|
+
// field type (dynCheck) — the checked-dynamic member-read
|
|
1296
|
+
// discipline. A missing key answers the dyn undefined, exactly
|
|
1297
|
+
// the undefined-armed optional's absent case; a mismatched
|
|
1298
|
+
// runtime value throws the catchable TypeError, never a silent
|
|
1299
|
+
// wrong copy. Runtime-ADDED keys drop — width subtyping in
|
|
1300
|
+
// spread clothing, divergence 36's stance.
|
|
1301
|
+
let value;
|
|
1302
|
+
if (obj.type.kind === "dyn") {
|
|
1303
|
+
if (!canDynCheckTo(f.type, (id) => lowerer.shapes.get(id), (id) => lowerer.unions.get(id))) {
|
|
1304
|
+
lowerer.unsupported("SC1100", prop, `object spread of a checked-dynamic source whose field '${f.name}' ('${lowerer.fmt(f.type)}') cannot validate out of the checked-dynamic tree (copy the fields explicitly)`);
|
|
1305
|
+
}
|
|
1306
|
+
value = {
|
|
1307
|
+
kind: "dynCheck",
|
|
1308
|
+
value: {
|
|
1309
|
+
kind: "dynKeyGet",
|
|
1310
|
+
key: { kind: "strLit", value: f.name, type: STRING, loc: locOf(prop) },
|
|
1311
|
+
value: obj,
|
|
1312
|
+
type: DYN,
|
|
1313
|
+
loc: locOf(prop),
|
|
1314
|
+
},
|
|
1315
|
+
type: f.type,
|
|
1316
|
+
loc: locOf(prop),
|
|
1317
|
+
};
|
|
1318
|
+
}
|
|
1319
|
+
else {
|
|
1320
|
+
value = {
|
|
1321
|
+
kind: "recordGet",
|
|
1322
|
+
obj,
|
|
1323
|
+
shapeId: srcType.shapeId,
|
|
1324
|
+
field: f.name,
|
|
1325
|
+
type: f.type,
|
|
1326
|
+
loc: locOf(prop),
|
|
1327
|
+
};
|
|
1328
|
+
}
|
|
1329
|
+
// A liftable field widens into the target slot (arm wrap,
|
|
1330
|
+
// re-tag, nested reshape) — the same per-field rule the slot
|
|
1331
|
+
// coercion applies.
|
|
1332
|
+
if (lift)
|
|
1333
|
+
value = lowerer.applyWidthLift(lift, value, targetType, locOf(prop));
|
|
1334
|
+
if (conditionalNames.has(f.name)) {
|
|
1335
|
+
lowerer.unsupported("SC1090", prop, `spread of '${f.name}' over an earlier conditional spread (the desugar keeps one entry per name — restructure so each name has one contributor)`);
|
|
1336
|
+
}
|
|
1337
|
+
const at = fields.findIndex((x) => x.name === f.name);
|
|
1338
|
+
if (at >= 0)
|
|
1339
|
+
fields[at] = { name: f.name, value };
|
|
1340
|
+
else
|
|
1341
|
+
fields.push({ name: f.name, value });
|
|
1342
|
+
}
|
|
1343
|
+
continue;
|
|
1344
|
+
}
|
|
1345
|
+
if (ts.isGetAccessorDeclaration(prop) || ts.isSetAccessorDeclaration(prop)) {
|
|
1346
|
+
// JS accessor entries carry no storage (narrowed away above); TS
|
|
1347
|
+
// accessors fill the shape's reserved closure slots — the getter
|
|
1348
|
+
// body lowers as an ordinary zero-arg closure invoked per property
|
|
1349
|
+
// READ, the setter as a one-arg closure invoked per WRITE
|
|
1350
|
+
// (fieldGetExpr/fieldSetStmt dispatch on the slots). Creating the
|
|
1351
|
+
// closures is side-effect-free, so their position among the data
|
|
1352
|
+
// fields' source-order evaluation is unobservable — exactly JS,
|
|
1353
|
+
// where accessor definitions evaluate nothing.
|
|
1354
|
+
if (isJsSourceFile(expr.getSourceFile()))
|
|
1355
|
+
continue;
|
|
1356
|
+
const name = propNameText(lowerer, prop.name); // key-form-checked above
|
|
1357
|
+
const slotName = `${ts.isGetAccessorDeclaration(prop) ? "%get" : "%set"}:${name}`;
|
|
1358
|
+
const slotT = fieldTypes.get(slotName);
|
|
1359
|
+
if (!slotT || slotT.kind !== "func") {
|
|
1360
|
+
// The contextual shape stores a DATA value under this name
|
|
1361
|
+
// (`const p: { x: number } = { get x() {...} }` — tsc lets a
|
|
1362
|
+
// live accessor satisfy a data member): the slot would freeze
|
|
1363
|
+
// one getter answer where Node keeps the accessor live.
|
|
1364
|
+
if (fieldTypes.has(name)) {
|
|
1365
|
+
lowerer.unsupported("SC1090", prop, `a get/set accessor satisfying the data property '${name}' of '${lowerer.fmt(type)}' (the record slot stores a plain value — Node would keep the accessor live through this type)`);
|
|
1366
|
+
}
|
|
1367
|
+
throw shapeMismatch(prop);
|
|
1368
|
+
}
|
|
1369
|
+
const closure = lowerer.coerceInto(prop, lowerer.lowerLambda(prop), slotT);
|
|
1370
|
+
if (!typeEquals(closure.type, slotT))
|
|
1371
|
+
lowerer.badType(prop, lowerer.typeOf(prop));
|
|
1372
|
+
fields.push({ name: slotName, value: closure });
|
|
1373
|
+
continue;
|
|
1374
|
+
}
|
|
1375
|
+
const name = propNameText(lowerer, prop.name); // key-form-checked above
|
|
1376
|
+
let fieldType = fieldTypes.get(name);
|
|
1377
|
+
// An undeclared name against an index-signature shape is an OVERFLOW
|
|
1378
|
+
// entry (tsc typechecked it against the signature's value type);
|
|
1379
|
+
// against a plain shape it is the width mismatch it always was —
|
|
1380
|
+
// EXCEPT the PromiseSettledResult honest subset's dropped fields
|
|
1381
|
+
// (value/reason — SEMANTICS.md 46): those evaluate for effect in
|
|
1382
|
+
// their source-order slot and store nothing.
|
|
1383
|
+
// A GENERIC-callable member (a generic method `m<T>(x: T) {...}` or a
|
|
1384
|
+
// generic arrow/function-expression property): excluded from the
|
|
1385
|
+
// record shape (isGenericCallableMemberType — no single closure slot
|
|
1386
|
+
// can hold it), so the literal stores nothing for it. Pure
|
|
1387
|
+
// function-creating forms skip outright (creating a closure has no
|
|
1388
|
+
// side effects; calls resolve statically against this declaration);
|
|
1389
|
+
// a computed initializer would need its evaluation kept — fenced.
|
|
1390
|
+
if (!fieldType && !ts.isSpreadAssignment(prop)) {
|
|
1391
|
+
const memberSym = prop.name && lowerer.checker.getSymbolAtLocation(prop.name);
|
|
1392
|
+
const memberT = memberSym ? lowerer.checker.getTypeOfSymbol(memberSym) : undefined;
|
|
1393
|
+
if (memberT && isGenericCallableMemberType(memberT, lowerer.checker)) {
|
|
1394
|
+
const pureInit = (() => {
|
|
1395
|
+
if (ts.isMethodDeclaration(prop))
|
|
1396
|
+
return true;
|
|
1397
|
+
if (ts.isShorthandPropertyAssignment(prop))
|
|
1398
|
+
return true; // a pure read
|
|
1399
|
+
if (!ts.isPropertyAssignment(prop))
|
|
1400
|
+
return false;
|
|
1401
|
+
let init = prop.initializer;
|
|
1402
|
+
while (ts.isParenthesizedExpression(init))
|
|
1403
|
+
init = init.expression;
|
|
1404
|
+
return ts.isArrowFunction(init) || ts.isFunctionExpression(init) || ts.isIdentifier(init);
|
|
1405
|
+
})();
|
|
1406
|
+
if (!pureInit) {
|
|
1407
|
+
lowerer.unsupported("SC1090", prop, `generic-function-valued properties with computed initializers ('${name}' has no record slot — its evaluation would be dropped; bind the function to a top-level declaration instead)`);
|
|
1408
|
+
}
|
|
1409
|
+
continue;
|
|
1410
|
+
}
|
|
1411
|
+
}
|
|
1412
|
+
if (!fieldType && !shape.indexValue) {
|
|
1413
|
+
if (settledDropNames(lowerer, tsType)?.has(name)) {
|
|
1414
|
+
// Identifier and shorthand initializers are effect-free reads —
|
|
1415
|
+
// skipped outright. The caught `reason` MUST skip: lowering the
|
|
1416
|
+
// read would hit the catch-binding narrowness fence, and the
|
|
1417
|
+
// snapshot it names needs no evaluation.
|
|
1418
|
+
if (ts.isPropertyAssignment(prop) && !ts.isIdentifier(prop.initializer)) {
|
|
1419
|
+
const v = lowerer.lowerExpr(prop.initializer);
|
|
1420
|
+
// Effect-free lowerings (literals, plain reads) drop at
|
|
1421
|
+
// compile time; anything else — the awaited mapper — runs
|
|
1422
|
+
// (and may throw into the enclosing catch) with its result
|
|
1423
|
+
// released by the statement frame.
|
|
1424
|
+
if (v.kind !== "unitLit" && v.kind !== "numLit" && v.kind !== "strLit" &&
|
|
1425
|
+
v.kind !== "boolLit" && v.kind !== "varRef" && v.kind !== "closure") {
|
|
1426
|
+
fields.push({ name, value: v, drop: true });
|
|
1427
|
+
}
|
|
1428
|
+
}
|
|
1429
|
+
continue;
|
|
1430
|
+
}
|
|
1431
|
+
throw shapeMismatch(prop);
|
|
1432
|
+
}
|
|
1433
|
+
let value;
|
|
1434
|
+
let valueNode = prop;
|
|
1435
|
+
const propDiagsBefore = lowerer.diags.length;
|
|
1436
|
+
try {
|
|
1437
|
+
if (ts.isPropertyAssignment(prop)) {
|
|
1438
|
+
valueNode = prop.initializer;
|
|
1439
|
+
// ARRAY-LITERAL initializers route through the expected-type
|
|
1440
|
+
// lowering: a union field with one array-family arm builds the
|
|
1441
|
+
// literal AS that arm (lowerExprExpecting's IR-directed rule —
|
|
1442
|
+
// the option-table `default: [{ value: [] }]` shape), where the
|
|
1443
|
+
// bare lowering would take the JS dyn fallback and fence.
|
|
1444
|
+
let init = prop.initializer;
|
|
1445
|
+
while (ts.isParenthesizedExpression(init))
|
|
1446
|
+
init = init.expression;
|
|
1447
|
+
value =
|
|
1448
|
+
fenceClosureProbe(lowerer, prop.initializer, fieldType, () => lowerer.lowerExpr(prop.initializer)) ??
|
|
1449
|
+
(fieldType !== undefined && ts.isArrayLiteralExpression(init)
|
|
1450
|
+
? lowerer.lowerExprExpecting(prop.initializer, fieldType)
|
|
1451
|
+
: lowerer.lowerExpr(prop.initializer));
|
|
1452
|
+
}
|
|
1453
|
+
else if (ts.isShorthandPropertyAssignment(prop)) {
|
|
1454
|
+
value = lowerer.lowerShorthandValue(prop);
|
|
1455
|
+
}
|
|
1456
|
+
else if (ts.isMethodDeclaration(prop)) {
|
|
1457
|
+
value =
|
|
1458
|
+
fenceClosureProbe(lowerer, prop, fieldType, () => lowerer.lowerLambda(prop)) ?? lowerer.lowerLambda(prop);
|
|
1459
|
+
}
|
|
1460
|
+
else {
|
|
1461
|
+
lowerer.unsupported("SC1090", prop, `syntax '${ts.SyntaxKind[prop.kind]}'`);
|
|
1462
|
+
}
|
|
1463
|
+
}
|
|
1464
|
+
catch (err) {
|
|
1465
|
+
// A member VALUE a JS file cannot lower (a namespace object in an
|
|
1466
|
+
// export aggregate — the sharedWithCli `errors` member): the
|
|
1467
|
+
// member NARROWS AWAY like a CJS export-table accessor entry —
|
|
1468
|
+
// the diagnostics defer to the runtime-fence ledger, the shape
|
|
1469
|
+
// drops the field, and each READ of it meets its own per-site
|
|
1470
|
+
// fence. Pure member forms only (identifier/shorthand reads);
|
|
1471
|
+
// TypeScript, probe mode, and ICEs keep the poison.
|
|
1472
|
+
const pureMember = ts.isShorthandPropertyAssignment(prop) ||
|
|
1473
|
+
(ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.initializer));
|
|
1474
|
+
if (!(err instanceof PoisonError) ||
|
|
1475
|
+
!pureMember ||
|
|
1476
|
+
!isJsSourceFile(expr.getSourceFile()) ||
|
|
1477
|
+
lowerer.diagSink !== null ||
|
|
1478
|
+
lowerer.diags.length <= propDiagsBefore ||
|
|
1479
|
+
lowerer.diags.slice(propDiagsBefore).some((d) => d.code === "SC9001")) {
|
|
1480
|
+
throw err;
|
|
1481
|
+
}
|
|
1482
|
+
lowerer.runtimeFences.push(...lowerer.diags.splice(propDiagsBefore));
|
|
1483
|
+
droppedNames.add(name);
|
|
1484
|
+
continue;
|
|
1485
|
+
}
|
|
1486
|
+
if (!fieldType) {
|
|
1487
|
+
// Overflow entry: the value flows into the index signature's value
|
|
1488
|
+
// slot — dyn slots take a dyn conversion (dynFrom), typed slots the
|
|
1489
|
+
// ordinary coercion path. Later duplicates override (map semantics
|
|
1490
|
+
// are last-write-wins already; a duplicate literal key is a tsc
|
|
1491
|
+
// error anyway).
|
|
1492
|
+
const slotted = lowerer.intoIndexValueSlot(value, shape.indexValue, valueNode);
|
|
1493
|
+
fields.push({ name, value: slotted, overflow: true });
|
|
1494
|
+
continue;
|
|
1495
|
+
}
|
|
1496
|
+
const promotedField = fieldType ? lowerer.runtimeOptionalWidening(value.type, fieldType) : null;
|
|
1497
|
+
if (promotedField && fieldType) {
|
|
1498
|
+
type = lowerer.runtimeOptionalRecordField(type, name, promotedField);
|
|
1499
|
+
if (type.kind === "record")
|
|
1500
|
+
shape = lowerer.shapes.get(type.shapeId);
|
|
1501
|
+
fieldTypes.set(name, promotedField);
|
|
1502
|
+
fieldType = promotedField;
|
|
1503
|
+
}
|
|
1504
|
+
value = lowerer.coerceInto(valueNode, value, fieldType); // union-typed fields wrap arm values
|
|
1505
|
+
if (!typeEquals(value.type, fieldType))
|
|
1506
|
+
lowerer.badType(valueNode, lowerer.typeOf(valueNode));
|
|
1507
|
+
// An explicit property overrides a spread-copied field: JS
|
|
1508
|
+
// last-write-wins. The entry moves to the END so explicit property
|
|
1509
|
+
// values keep their source-order evaluation among themselves. A
|
|
1510
|
+
// conditional-spread entry cannot be overridden (its ternary IS the
|
|
1511
|
+
// spread's evaluation; splicing it out would drop that).
|
|
1512
|
+
if (conditionalNames.has(name)) {
|
|
1513
|
+
lowerer.unsupported("SC1090", prop, `'${name}' after a conditional spread of the same name (the desugar keeps one entry per name — restructure so each name has one contributor)`);
|
|
1514
|
+
}
|
|
1515
|
+
const at = fields.findIndex((x) => x.name === name);
|
|
1516
|
+
if (at >= 0)
|
|
1517
|
+
fields.splice(at, 1);
|
|
1518
|
+
fields.push({ name, value });
|
|
1519
|
+
}
|
|
1520
|
+
// Optional fields (undefined-armed union slots) may be omitted: the
|
|
1521
|
+
// absent field holds the interned undefined arm, exactly like writing
|
|
1522
|
+
// `a: undefined` (without exactOptionalPropertyTypes tsc treats the two
|
|
1523
|
+
// the same, and only optional fields may be omitted — tsc rejects
|
|
1524
|
+
// omission of required fields before lowering, undefined-armed or not).
|
|
1525
|
+
// A REQUIRED missing field keeps the shape-mismatch rejection (possible
|
|
1526
|
+
// through `as`: the cast smuggles a narrower literal past freshness).
|
|
1527
|
+
if (droppedNames.size > 0) {
|
|
1528
|
+
const narrowed = shape.fields.filter((f) => !droppedNames.has(f.name));
|
|
1529
|
+
const narrowedId = lowerer.shapes.intern(narrowed.map((f) => ({ name: f.name, type: f.type })), false, shape.indexValue);
|
|
1530
|
+
type = { kind: "record", shapeId: narrowedId };
|
|
1531
|
+
shape = lowerer.shapes.get(narrowedId);
|
|
1532
|
+
}
|
|
1533
|
+
if (fields.filter((f) => !f.overflow).length !== shape.fields.length) {
|
|
1534
|
+
const provided = new Set(fields.filter((f) => !f.overflow).map((f) => f.name));
|
|
1535
|
+
for (const f of shape.fields) {
|
|
1536
|
+
if (provided.has(f.name))
|
|
1537
|
+
continue;
|
|
1538
|
+
// 'unknown' fields complete with the dyn undefined — the absent
|
|
1539
|
+
// property reads as undefined in Node, and a dyn slot holds
|
|
1540
|
+
// exactly that (the options-record call shape against
|
|
1541
|
+
// `{ plugins: unknown, ... }` — a JS caller the checker admits).
|
|
1542
|
+
const absent = lowerer.wrappedUndefined(f.type, loc) ?? (f.type.kind === "dyn" ? dynUndefinedExpr(loc) : null);
|
|
1543
|
+
if (!absent)
|
|
1544
|
+
throw shapeMismatch(expr); // only optional (undefined-armed) and 'unknown' fields may be omitted
|
|
1545
|
+
fields.push({ name: f.name, value: absent });
|
|
1546
|
+
}
|
|
1547
|
+
}
|
|
1548
|
+
return { kind: "recordLit", fields, type, loc };
|
|
1549
|
+
}
|
|
1550
|
+
/** Parses the conditional-spread idiom `...(c ? { k: v, ... } : {})`
|
|
1551
|
+
* (either orientation). Returns the condition, the non-empty arm's
|
|
1552
|
+
* properties, and which arm carries them; "unsupported" for conditional
|
|
1553
|
+
* sources OUTSIDE the idiom (both arms non-empty, computed/method
|
|
1554
|
+
* members); null when the spread source isn't a conditional at all.
|
|
1555
|
+
* Callers slice their own honest subset (the static record path takes
|
|
1556
|
+
* exactly one property; the island literal takes any number; spawn's
|
|
1557
|
+
* options walk takes a `detached` boolean literal). */
|
|
1558
|
+
export function conditionalSpreadOf(expr) {
|
|
1559
|
+
let e = expr;
|
|
1560
|
+
while (ts.isParenthesizedExpression(e))
|
|
1561
|
+
e = e.expression;
|
|
1562
|
+
if (!ts.isConditionalExpression(e))
|
|
1563
|
+
return null;
|
|
1564
|
+
const unwrap = (a) => {
|
|
1565
|
+
let x = a;
|
|
1566
|
+
while (ts.isParenthesizedExpression(x))
|
|
1567
|
+
x = x.expression;
|
|
1568
|
+
return x;
|
|
1569
|
+
};
|
|
1570
|
+
const whenTrue = unwrap(e.whenTrue);
|
|
1571
|
+
const whenFalse = unwrap(e.whenFalse);
|
|
1572
|
+
if (!ts.isObjectLiteralExpression(whenTrue) || !ts.isObjectLiteralExpression(whenFalse)) {
|
|
1573
|
+
return "unsupported";
|
|
1574
|
+
}
|
|
1575
|
+
const trueCarries = whenFalse.properties.length === 0 && whenTrue.properties.length > 0;
|
|
1576
|
+
const falseCarries = whenTrue.properties.length === 0 && whenFalse.properties.length > 0;
|
|
1577
|
+
if (!trueCarries && !falseCarries)
|
|
1578
|
+
return "unsupported"; // both empty is tsc-unreachable; both non-empty has no single desugar
|
|
1579
|
+
const carrier = trueCarries ? whenTrue : whenFalse;
|
|
1580
|
+
const props = [];
|
|
1581
|
+
for (const p of carrier.properties) {
|
|
1582
|
+
if ((ts.isPropertyAssignment(p) || ts.isShorthandPropertyAssignment(p)) &&
|
|
1583
|
+
p.name && (ts.isIdentifier(p.name) || ts.isStringLiteral(p.name))) {
|
|
1584
|
+
props.push(p);
|
|
1585
|
+
}
|
|
1586
|
+
else {
|
|
1587
|
+
return "unsupported";
|
|
1588
|
+
}
|
|
1589
|
+
}
|
|
1590
|
+
return { cond: e.condition, props, whenTrue: trueCarries };
|
|
1591
|
+
}
|
|
1592
|
+
/** `{ ...idx, ...idx2 }` typed a DECLARED shape (no index signature) — the
|
|
1593
|
+
* defaults-merge idiom over runtime-keyed sources (`{ ...fromEntries(a),
|
|
1594
|
+
* ...fromEntries(b) }` typed AppConfig). Lowers to ONE interned helper
|
|
1595
|
+
* call: sources evaluate once each as arguments (source order — computed
|
|
1596
|
+
* sources included, JS's evaluate-once), the result starts all-undefined
|
|
1597
|
+
* (every target field must be optional — the runtime keys decide
|
|
1598
|
+
* presence), and each source's keys apply in JS own-key order with a
|
|
1599
|
+
* per-key dispatch onto the declared fields: a matching key writes the
|
|
1600
|
+
* field — identity when the source's value slot IS the field type, a
|
|
1601
|
+
* validated extraction otherwise (a dyn slot dynChecks; a union slot
|
|
1602
|
+
* re-tags arm-by-arm, and a value outside the field's arms throws the
|
|
1603
|
+
* catchable TypeError — divergence 34's keyed-write stance, where Node's
|
|
1604
|
+
* untyped copy would store the lie) — and a key naming NO declared field
|
|
1605
|
+
* is DROPPED (the shape cannot represent it; Node keeps it invisibly —
|
|
1606
|
+
* divergence 68). Later contributors overwrite earlier ones
|
|
1607
|
+
* (last-write-wins). Sources must be PURE index-signature records. */
|
|
1608
|
+
function lowerDeclaredSpreadMerge(lowerer, expr, type, shape, loc) {
|
|
1609
|
+
const srcs = [];
|
|
1610
|
+
for (const prop of expr.properties) {
|
|
1611
|
+
const spread = prop; // caller-checked: all spreads
|
|
1612
|
+
const value = lowerer.lowerExpr(spread.expression);
|
|
1613
|
+
const srcShape = value.type.kind === "record" ? lowerer.shapes.get(value.type.shapeId) : undefined;
|
|
1614
|
+
if (value.type.kind !== "record" || !srcShape?.indexValue || srcShape.tuple || srcShape.fields.length > 0) {
|
|
1615
|
+
lowerer.unsupported("SC1090", prop, `object spread of '${lowerer.fmt(value.type)}' into '${lowerer.fmt(type)}' (only PURE index-signature records — Object.fromEntries results, Record<string, T> values — spread into a declared shape)`);
|
|
1616
|
+
}
|
|
1617
|
+
srcs.push({ value, shapeId: value.type.shapeId, iv: srcShape.indexValue });
|
|
1618
|
+
}
|
|
1619
|
+
// Every target field must be optional: absent keys leave the undefined
|
|
1620
|
+
// arm, exactly the unset-optional representation.
|
|
1621
|
+
for (const f of shape.fields) {
|
|
1622
|
+
if (f.type.kind !== "union" || lowerer.armTag(f.type.unionId, UNDEFINED_T) < 0) {
|
|
1623
|
+
lowerer.unsupported("SC1090", expr, `object spread of runtime-keyed sources onto the required field '${f.name}' (the keys decide presence at runtime — declare the field optional or spell it explicitly)`);
|
|
1624
|
+
}
|
|
1625
|
+
}
|
|
1626
|
+
// Per (source value slot → field) conversion, checked up front so the
|
|
1627
|
+
// fence fires at the literal, not inside the interned helper.
|
|
1628
|
+
const conv = (iv, f, v) => {
|
|
1629
|
+
if (typeEquals(iv, f.type))
|
|
1630
|
+
return { value: v };
|
|
1631
|
+
if (iv.kind === "dyn") {
|
|
1632
|
+
return { value: { kind: "dynCheck", value: v, type: f.type, loc } };
|
|
1633
|
+
}
|
|
1634
|
+
if (iv.kind === "union" && f.type.kind === "union") {
|
|
1635
|
+
const fUnion = f.type;
|
|
1636
|
+
const def = lowerer.unions.get(iv.unionId);
|
|
1637
|
+
const fDef = lowerer.unions.get(fUnion.unionId);
|
|
1638
|
+
if (def && fDef && def.arms.some((a) => lowerer.armTag(fUnion.unionId, a) >= 0)) {
|
|
1639
|
+
// Arm-by-arm validated re-tag, inline in the helper: matching
|
|
1640
|
+
// arms map by identity (unit arms re-wrap, value arms narrow and
|
|
1641
|
+
// wrap); anything else throws the catchable TypeError.
|
|
1642
|
+
return {
|
|
1643
|
+
stmts: (write) => {
|
|
1644
|
+
const chain = (i) => {
|
|
1645
|
+
if (i >= def.arms.length) {
|
|
1646
|
+
return [{
|
|
1647
|
+
kind: "throw",
|
|
1648
|
+
value: {
|
|
1649
|
+
kind: "libCall",
|
|
1650
|
+
fn: "error.new",
|
|
1651
|
+
args: [{ kind: "strLit", value: `expected ${lowerer.fmt(fUnion)} at $.${f.name}`, type: STRING, loc }],
|
|
1652
|
+
type: { kind: "object", className: "%TypeError" },
|
|
1653
|
+
loc,
|
|
1654
|
+
},
|
|
1655
|
+
loc,
|
|
1656
|
+
}];
|
|
1657
|
+
}
|
|
1658
|
+
const arm = def.arms[i];
|
|
1659
|
+
const toTag = lowerer.armTag(fUnion.unionId, arm);
|
|
1660
|
+
if (toTag < 0)
|
|
1661
|
+
return chain(i + 1);
|
|
1662
|
+
const extracted = isUnitType(arm)
|
|
1663
|
+
? { kind: "unionWrap", unionId: fUnion.unionId, tag: toTag, value: { kind: "unitLit", unit: arm.kind === "undefinedT" ? "undefined" : "null", type: arm, loc }, type: fUnion, loc }
|
|
1664
|
+
: { kind: "unionWrap", unionId: fUnion.unionId, tag: toTag, value: { kind: "unionNarrow", unionId: iv.unionId, tag: i, value: v, type: arm, loc }, type: fUnion, loc };
|
|
1665
|
+
return [{
|
|
1666
|
+
kind: "if",
|
|
1667
|
+
cond: { kind: "unionIsTag", unionId: iv.unionId, tag: i, negated: false, value: v, type: BOOL, loc },
|
|
1668
|
+
then: [write(extracted)],
|
|
1669
|
+
else_: chain(i + 1),
|
|
1670
|
+
loc,
|
|
1671
|
+
}];
|
|
1672
|
+
};
|
|
1673
|
+
return chain(0);
|
|
1674
|
+
},
|
|
1675
|
+
};
|
|
1676
|
+
}
|
|
1677
|
+
}
|
|
1678
|
+
lowerer.unsupported("SC1090", expr, `object spread into '${lowerer.fmt(type)}' where the source's '${lowerer.fmt(iv)}' values cannot reach the '${lowerer.fmt(f.type)}' field '${f.name}' (the value slot must be the field type, 'unknown', or a union covering the field's arms)`);
|
|
1679
|
+
};
|
|
1680
|
+
const key = `declmerge:${type.shapeId}:${srcs.map((s) => s.shapeId).join(",")}`;
|
|
1681
|
+
let helper = lowerer.widthHelpers.get(key);
|
|
1682
|
+
if (!helper) {
|
|
1683
|
+
helper = `%rec.declmerge.${lowerer.widthHelpers.size}`;
|
|
1684
|
+
const outRef = varRef("out.0", type, loc);
|
|
1685
|
+
const ksT = arrayOf(STRING);
|
|
1686
|
+
const locals = [{ id: "out.0", name: "out", type, mutable: false }];
|
|
1687
|
+
const params = srcs.map((s, j) => {
|
|
1688
|
+
locals.push({ id: `s${j}.0`, name: `s${j}`, type: s.value.type, mutable: true });
|
|
1689
|
+
return { localId: `s${j}.0`, name: `s${j}`, type: s.value.type };
|
|
1690
|
+
});
|
|
1691
|
+
const body = [
|
|
1692
|
+
{
|
|
1693
|
+
kind: "varDecl",
|
|
1694
|
+
localId: "out.0",
|
|
1695
|
+
init: {
|
|
1696
|
+
kind: "recordLit",
|
|
1697
|
+
fields: shape.fields.map((f) => ({ name: f.name, value: lowerer.wrappedUndefined(f.type, loc) })),
|
|
1698
|
+
type,
|
|
1699
|
+
loc,
|
|
1700
|
+
},
|
|
1701
|
+
loc,
|
|
1702
|
+
},
|
|
1703
|
+
];
|
|
1704
|
+
srcs.forEach((s, j) => {
|
|
1705
|
+
const sRef = varRef(`s${j}.0`, s.value.type, loc);
|
|
1706
|
+
const kRef = varRef(`k${j}.0`, STRING, loc);
|
|
1707
|
+
const vRef = varRef(`v${j}.0`, s.iv, loc);
|
|
1708
|
+
locals.push({ id: `ks${j}.0`, name: `ks${j}`, type: ksT, mutable: false }, { id: `i${j}.0`, name: `i${j}`, type: F64, mutable: true }, { id: `k${j}.0`, name: `k${j}`, type: STRING, mutable: false }, { id: `v${j}.0`, name: `v${j}`, type: s.iv, mutable: false });
|
|
1709
|
+
// Per-key dispatch: if (k === "a") { write a } else if ... else drop.
|
|
1710
|
+
const dispatch = shape.fields.reduceRight((rest, f) => {
|
|
1711
|
+
const write = (value) => ({ kind: "recordSet", obj: outRef, shapeId: type.shapeId, field: f.name, value, loc });
|
|
1712
|
+
const c = conv(s.iv, f, vRef);
|
|
1713
|
+
const thenBody = "value" in c ? [write(c.value)] : c.stmts(write);
|
|
1714
|
+
return [{
|
|
1715
|
+
kind: "if",
|
|
1716
|
+
cond: { kind: "strEq", negated: false, left: kRef, right: { kind: "strLit", value: f.name, type: STRING, loc }, type: BOOL, loc },
|
|
1717
|
+
then: thenBody,
|
|
1718
|
+
else_: rest.length > 0 ? rest : null,
|
|
1719
|
+
loc,
|
|
1720
|
+
}];
|
|
1721
|
+
}, []);
|
|
1722
|
+
body.push({ kind: "varDecl", localId: `ks${j}.0`, init: { kind: "recordOvfKeys", obj: sRef, shapeId: s.shapeId, type: ksT, loc }, loc }, {
|
|
1723
|
+
kind: "for",
|
|
1724
|
+
init: { kind: "varDecl", localId: `i${j}.0`, init: numLit(0, loc), loc },
|
|
1725
|
+
cond: { kind: "bin", op: "<", left: varRef(`i${j}.0`, F64, loc), right: { kind: "arrIntrinsic", method: "length", receiver: varRef(`ks${j}.0`, ksT, loc), args: [], type: F64, loc }, type: BOOL, loc },
|
|
1726
|
+
update: { kind: "assign", localId: `i${j}.0`, value: { kind: "bin", op: "+", left: varRef(`i${j}.0`, F64, loc), right: numLit(1, loc), type: F64, loc }, loc },
|
|
1727
|
+
body: [
|
|
1728
|
+
{ kind: "varDecl", localId: `k${j}.0`, init: { kind: "arrayGet", arr: varRef(`ks${j}.0`, ksT, loc), index: varRef(`i${j}.0`, F64, loc), type: STRING, loc }, loc },
|
|
1729
|
+
{ kind: "varDecl", localId: `v${j}.0`, init: { kind: "recordKeyGet", obj: sRef, shapeId: s.shapeId, key: kRef, overflowOnly: true, type: s.iv, loc }, loc },
|
|
1730
|
+
...dispatch,
|
|
1731
|
+
],
|
|
1732
|
+
loc,
|
|
1733
|
+
});
|
|
1734
|
+
});
|
|
1735
|
+
body.push({ kind: "return", value: outRef, loc });
|
|
1736
|
+
lowerer.liftedFns.push({
|
|
1737
|
+
name: helper,
|
|
1738
|
+
params,
|
|
1739
|
+
returnType: type,
|
|
1740
|
+
locals,
|
|
1741
|
+
body,
|
|
1742
|
+
loc,
|
|
1743
|
+
});
|
|
1744
|
+
// Registered only after a fence-free build: a conv() fence mid-build
|
|
1745
|
+
// must not leave a phantom helper behind for the next literal.
|
|
1746
|
+
lowerer.widthHelpers.set(key, helper);
|
|
1747
|
+
}
|
|
1748
|
+
return { kind: "call", callee: helper, args: srcs.map((s) => s.value), type, loc };
|
|
1749
|
+
}
|
|
1750
|
+
/** The value of a shorthand property (`{ x }`): the binding `x` refers to.
|
|
1751
|
+
* The property name's own symbol is the PROPERTY, so resolution goes
|
|
1752
|
+
* through getShorthandAssignmentValueSymbol. */
|
|
1753
|
+
export function lowerShorthandValue(lowerer, prop) {
|
|
1754
|
+
// 7 types the shorthand's name as PropertyName; it is always an
|
|
1755
|
+
// Identifier (the grammar allows nothing else in shorthand position).
|
|
1756
|
+
const propName = prop.name;
|
|
1757
|
+
const loc = locOf(propName);
|
|
1758
|
+
const symbol = lowerer.checker.getShorthandAssignmentValueSymbol(prop);
|
|
1759
|
+
if (symbol) {
|
|
1760
|
+
if (lowerer.ctx.selfSymbol === symbol) {
|
|
1761
|
+
return { kind: "selfRef", type: lowerer.ctx.selfType, loc };
|
|
1762
|
+
}
|
|
1763
|
+
const local = lowerer.resolveKey(symbol, propName);
|
|
1764
|
+
if (local) {
|
|
1765
|
+
return lowerer.maybeNarrow({ kind: "varRef", localId: local.id, type: local.type, loc }, propName);
|
|
1766
|
+
}
|
|
1767
|
+
const resolved = symbol.flags & ts.SymbolFlags.Alias ? lowerer.checker.getAliasedSymbol(symbol) : symbol;
|
|
1768
|
+
lowerer.flushDeferred(resolved);
|
|
1769
|
+
const g = lowerer.globalsBySymbol.get(resolved);
|
|
1770
|
+
if (g) {
|
|
1771
|
+
return lowerer.maybeNarrow({ kind: "varRef", localId: g.id, type: g.type, loc }, propName);
|
|
1772
|
+
}
|
|
1773
|
+
const sig = lowerer.fnSigsBySymbol.get(resolved);
|
|
1774
|
+
const decl = lowerer.checker.declarationsOf(resolved)[0];
|
|
1775
|
+
if (sig && decl && ts.isFunctionDeclaration(decl) &&
|
|
1776
|
+
(ts.isSourceFile(decl.parent) || lowerer.nsBlocks.get(decl.parent) === "flattened")) {
|
|
1777
|
+
lowerer.noteEdge(sig.name);
|
|
1778
|
+
const funcType = {
|
|
1779
|
+
kind: "func",
|
|
1780
|
+
params: sig.params.filter((p) => p.mode !== "dynRest").map((p) => p.type),
|
|
1781
|
+
ret: sig.returnType,
|
|
1782
|
+
...(sig.params.some((p) => p.mode === "dynRest") ? { rest: true } : {}),
|
|
1783
|
+
};
|
|
1784
|
+
lowerer.requireExactArityValue(prop, propName, sig.params, funcType);
|
|
1785
|
+
return { kind: "closure", fnName: sig.name, captures: [], type: funcType, loc };
|
|
1786
|
+
}
|
|
1787
|
+
if (lowerer.genericFnsBySymbol.has(resolved)) {
|
|
1788
|
+
lowerer.unsupported("SC1090", prop, `generic functions as values (call '${propName.text}' directly)`);
|
|
1789
|
+
}
|
|
1790
|
+
}
|
|
1791
|
+
lowerer.rejectUnresolvedSymbol(symbol ? (symbol.flags & ts.SymbolFlags.Alias ? lowerer.checker.getAliasedSymbol(symbol) : symbol) : null, propName.text, prop, `the reference to '${propName.text}' (a binding form with no lowering)`);
|
|
1792
|
+
}
|
|
1793
|
+
/** Rejects any `this` inside an object-literal method body — including in
|
|
1794
|
+
* nested arrows, which inherit the method's `this` (nested function
|
|
1795
|
+
* expressions reset it, but their bare `this` is already a tsc error
|
|
1796
|
+
* under noImplicitThis, so over-rejecting them here changes nothing). */
|
|
1797
|
+
export function rejectThisInObjectMethod(lowerer, node) {
|
|
1798
|
+
if (node.kind === ts.SyntaxKind.ThisKeyword) {
|
|
1799
|
+
lowerer.unsupported("SC1090", node, "references to 'this' in object literal methods");
|
|
1800
|
+
}
|
|
1801
|
+
ts.forEachChild(node, (child) => lowerer.rejectThisInObjectMethod(child));
|
|
1802
|
+
}
|
|
1803
|
+
/** The accessor twin of rejectThisInObjectMethod: a get/set accessor body
|
|
1804
|
+
* referencing `this` (`get x() { return this._x }`). The accessor lowers
|
|
1805
|
+
* as a closure stored IN the record — passing the record as a receiver
|
|
1806
|
+
* would capture the value under construction (an RC cycle), and the
|
|
1807
|
+
* generic lexical-this walk would silently bind an ENCLOSING method's
|
|
1808
|
+
* `this`; the fence names the fix (capture a binding instead). */
|
|
1809
|
+
function rejectThisInObjectAccessor(lowerer, node) {
|
|
1810
|
+
if (node.kind === ts.SyntaxKind.ThisKeyword) {
|
|
1811
|
+
lowerer.unsupported("SC1090", node, "references to 'this' in object literal get/set accessors (the accessor lowers as a captured closure with no receiver — read a captured binding instead)");
|
|
1812
|
+
}
|
|
1813
|
+
ts.forEachChild(node, (child) => rejectThisInObjectAccessor(lowerer, child));
|
|
1814
|
+
}
|
|
1815
|
+
//# sourceMappingURL=object-literals.js.map
|