@scriptc/compiler 0.1.4 → 0.1.6
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 +75 -11
- package/dist/backend/c/c-emitter.d.ts +5 -1
- package/dist/backend/c/c-emitter.js +22 -9
- package/dist/backend/c/c-emitter.js.map +1 -1
- package/dist/backend/c/exprs.d.ts +2 -2
- package/dist/backend/c/exprs.js +298 -27
- package/dist/backend/c/exprs.js.map +1 -1
- package/dist/backend/c/stmts.js +5 -3
- package/dist/backend/c/stmts.js.map +1 -1
- package/dist/backend/c/types.d.ts +2 -3
- package/dist/backend/c/types.js +8 -1
- package/dist/backend/c/types.js.map +1 -1
- package/dist/backend/c/walkers.js +1 -1
- package/dist/backend/c/walkers.js.map +1 -1
- package/dist/backend/link-plan.d.ts +1 -0
- package/dist/backend/link-plan.js +2 -1
- package/dist/backend/link-plan.js.map +1 -1
- package/dist/backend/llvm/dyn.js +1 -1
- package/dist/backend/llvm/dyn.js.map +1 -1
- package/dist/backend/llvm/emitter.js +25 -9
- package/dist/backend/llvm/emitter.js.map +1 -1
- package/dist/backend/llvm/expr-bytes.d.ts +1 -2
- package/dist/backend/llvm/expr-bytes.js +22 -17
- package/dist/backend/llvm/expr-bytes.js.map +1 -1
- package/dist/backend/llvm/expr-containers.d.ts +1 -0
- package/dist/backend/llvm/expr-containers.js +58 -4
- package/dist/backend/llvm/expr-containers.js.map +1 -1
- package/dist/backend/llvm/expr-context.d.ts +6 -2
- package/dist/backend/llvm/expr-primitives.js +22 -7
- package/dist/backend/llvm/expr-primitives.js.map +1 -1
- package/dist/backend/llvm/lib-filesystem.js +20 -0
- package/dist/backend/llvm/lib-filesystem.js.map +1 -1
- package/dist/backend/llvm/lib-network.js +53 -10
- package/dist/backend/llvm/lib-network.js.map +1 -1
- package/dist/backend/llvm/lib-process.js +4 -3
- package/dist/backend/llvm/lib-process.js.map +1 -1
- package/dist/backend/llvm/lib-shared.js +65 -0
- package/dist/backend/llvm/lib-shared.js.map +1 -1
- package/dist/backend/native-toolchain.d.ts +2 -0
- package/dist/backend/native-toolchain.js +2 -1
- package/dist/backend/native-toolchain.js.map +1 -1
- package/dist/backend/targets.d.ts +3 -0
- package/dist/backend/targets.js +13 -0
- package/dist/backend/targets.js.map +1 -1
- package/dist/coverage/surface-manifest.js +12 -5
- package/dist/coverage/surface-manifest.js.map +1 -1
- package/dist/executable/executable-cache.d.ts +2 -0
- package/dist/executable/executable-cache.js +3 -1
- package/dist/executable/executable-cache.js.map +1 -1
- package/dist/executable/executable-cache.test.js +3 -3
- package/dist/executable/executable-cache.test.js.map +1 -1
- package/dist/frontend/lowering/array-indexed-mutation.d.ts +8 -0
- package/dist/frontend/lowering/array-indexed-mutation.js +176 -0
- package/dist/frontend/lowering/array-indexed-mutation.js.map +1 -0
- package/dist/frontend/lowering/containers/bytes.d.ts +1 -1
- package/dist/frontend/lowering/containers/bytes.js +6 -5
- package/dist/frontend/lowering/containers/bytes.js.map +1 -1
- package/dist/frontend/lowering/containers/string-and-regexp.d.ts +14 -6
- package/dist/frontend/lowering/containers/string-and-regexp.js +420 -34
- package/dist/frontend/lowering/containers/string-and-regexp.js.map +1 -1
- package/dist/frontend/lowering/expressions/object-literals.js +8 -0
- package/dist/frontend/lowering/expressions/object-literals.js.map +1 -1
- package/dist/frontend/lowering/indexed-comparison.d.ts +6 -0
- package/dist/frontend/lowering/indexed-comparison.js +30 -0
- package/dist/frontend/lowering/indexed-comparison.js.map +1 -0
- package/dist/frontend/lowering/lower-builtins.d.ts +5 -6
- package/dist/frontend/lowering/lower-builtins.js +24 -16
- package/dist/frontend/lowering/lower-builtins.js.map +1 -1
- package/dist/frontend/lowering/lower-calls.d.ts +3 -2
- package/dist/frontend/lowering/lower-calls.js +522 -89
- package/dist/frontend/lowering/lower-calls.js.map +1 -1
- package/dist/frontend/lowering/lower-classes.js +5 -29
- package/dist/frontend/lowering/lower-classes.js.map +1 -1
- package/dist/frontend/lowering/lower-containers.d.ts +15 -11
- package/dist/frontend/lowering/lower-containers.js +588 -146
- package/dist/frontend/lowering/lower-containers.js.map +1 -1
- package/dist/frontend/lowering/lower-exprs.d.ts +12 -0
- package/dist/frontend/lowering/lower-exprs.js +145 -11
- package/dist/frontend/lowering/lower-exprs.js.map +1 -1
- package/dist/frontend/lowering/lower-island.js +17 -6
- package/dist/frontend/lowering/lower-island.js.map +1 -1
- package/dist/frontend/lowering/lower-server.d.ts +2 -4
- package/dist/frontend/lowering/lower-server.js +288 -32
- package/dist/frontend/lowering/lower-server.js.map +1 -1
- package/dist/frontend/lowering/lower-stmts.js +4 -4
- package/dist/frontend/lowering/lower-stmts.js.map +1 -1
- package/dist/frontend/lowering/lowerer.js +37 -2
- package/dist/frontend/lowering/lowerer.js.map +1 -1
- package/dist/frontend/lowering/optional-arguments.d.ts +9 -1
- package/dist/frontend/lowering/optional-arguments.js +67 -1
- package/dist/frontend/lowering/optional-arguments.js.map +1 -1
- package/dist/frontend/lowering/pairs-snapshot.d.ts +3 -1
- package/dist/frontend/lowering/pairs-snapshot.js +12 -4
- package/dist/frontend/lowering/pairs-snapshot.js.map +1 -1
- package/dist/frontend/lowering/surfaces.d.ts +10 -14
- package/dist/frontend/lowering/surfaces.js +50 -28
- package/dist/frontend/lowering/surfaces.js.map +1 -1
- package/dist/frontend/npm-static-prune.d.ts +6 -0
- package/dist/frontend/npm-static-prune.js +203 -0
- package/dist/frontend/npm-static-prune.js.map +1 -0
- package/dist/frontend/npm-static-prune.test.d.ts +1 -0
- package/dist/frontend/npm-static-prune.test.js +48 -0
- package/dist/frontend/npm-static-prune.test.js.map +1 -0
- package/dist/frontend/npm.js +7 -6
- package/dist/frontend/npm.js.map +1 -1
- package/dist/frontend/program.js +15 -4
- package/dist/frontend/program.js.map +1 -1
- package/dist/frontend/provenance.js +8 -3
- package/dist/frontend/provenance.js.map +1 -1
- package/dist/frontend/type-mapper.js +16 -5
- package/dist/frontend/type-mapper.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +13 -41
- package/dist/index.js.map +1 -1
- package/dist/ir/analysis.d.ts +5 -3
- package/dist/ir/analysis.js +25 -11
- package/dist/ir/analysis.js.map +1 -1
- package/dist/ir/analysis.test.js +50 -2
- package/dist/ir/analysis.test.js.map +1 -1
- package/dist/ir/constant-tables.d.ts +17 -0
- package/dist/ir/constant-tables.js +99 -0
- package/dist/ir/constant-tables.js.map +1 -0
- package/dist/ir/constant-tables.test.d.ts +1 -0
- package/dist/ir/constant-tables.test.js +78 -0
- package/dist/ir/constant-tables.test.js.map +1 -0
- package/dist/ir/integer-ranges.d.ts +13 -0
- package/dist/ir/integer-ranges.js +122 -0
- package/dist/ir/integer-ranges.js.map +1 -0
- package/dist/ir/integer-ranges.test.d.ts +1 -0
- package/dist/ir/integer-ranges.test.js +63 -0
- package/dist/ir/integer-ranges.test.js.map +1 -0
- package/dist/ir/ir.d.ts +56 -35
- package/dist/ir/ir.js +9 -1
- package/dist/ir/ir.js.map +1 -1
- package/dist/ir/ir.test.js +6 -0
- package/dist/ir/ir.test.js.map +1 -1
- package/dist/ir/scalar-records.d.ts +8 -0
- package/dist/ir/scalar-records.js +216 -0
- package/dist/ir/scalar-records.js.map +1 -0
- package/dist/ir/scalar-records.test.d.ts +1 -0
- package/dist/ir/scalar-records.test.js +142 -0
- package/dist/ir/scalar-records.test.js.map +1 -0
- package/dist/ir/validate.js +135 -34
- package/dist/ir/validate.js.map +1 -1
- package/dist/ir/validate.test.d.ts +1 -0
- package/dist/ir/validate.test.js +48 -0
- package/dist/ir/validate.test.js.map +1 -0
- package/dist/library/cache-primitives.d.ts +1 -1
- package/dist/library/cache-primitives.js +3 -3
- package/dist/library/cache-primitives.js.map +1 -1
- package/dist/library/fence-eval.js +11 -4
- package/dist/library/fence-eval.js.map +1 -1
- package/dist/library/library-cache.js +0 -1
- package/dist/library/library-cache.js.map +1 -1
- package/dist/library/library-cache.test.js +3 -3
- package/dist/library/library-cache.test.js.map +1 -1
- package/package.json +17 -17
- package/surface-manifest.json +90 -45
|
@@ -14,8 +14,36 @@ import { islandPrimitiveExit, lowerDynDispatchMethodCall } from "./lower-calls.j
|
|
|
14
14
|
import { buildArraySortFn } from "./lower-array-sort.js";
|
|
15
15
|
import { arrayIndexPresent, arrayValueRead, arrayValueStore, arrayValueType, currentArrayIndexPresent } from "./array-values.js";
|
|
16
16
|
import { typeKey } from "../type-mapper.js";
|
|
17
|
-
import { boolLit, countedFor, numLit, varRef } from "../../ir/build.js";
|
|
18
|
-
import {
|
|
17
|
+
import { boolLit, countedFor, numLit, strLit, varRef } from "../../ir/build.js";
|
|
18
|
+
import { defaultAfterUndefined, lowerPositionArgument, lowerStaticallyUndefinedArgument, positionNumber } from "./optional-arguments.js";
|
|
19
|
+
import { lowerArrayCopyWithin, lowerArrayFill } from "./array-indexed-mutation.js";
|
|
20
|
+
function primitivePositionType(lowerer, type) {
|
|
21
|
+
if (type.kind === "union") {
|
|
22
|
+
return lowerer.unions.get(type.unionId)?.arms.every((arm) => primitivePositionType(lowerer, arm)) ?? false;
|
|
23
|
+
}
|
|
24
|
+
return type.kind === "f64" || type.kind === "string" || type.kind === "bool" ||
|
|
25
|
+
type.kind === "nullT" || type.kind === "undefinedT";
|
|
26
|
+
}
|
|
27
|
+
function lowerArrayPosition(lowerer, node, defaultValue, subject) {
|
|
28
|
+
if (!node)
|
|
29
|
+
return defaultValue;
|
|
30
|
+
if (ts.isSpreadElement(node))
|
|
31
|
+
lowerer.noLowering(`${subject} with a spread argument`, node);
|
|
32
|
+
const value = lowerPositionArgument(lowerer, node, defaultValue);
|
|
33
|
+
if (!primitivePositionType(lowerer, value.type)) {
|
|
34
|
+
lowerer.noLowering(`${subject} of '${lowerer.fmt(value.type)}' values`, node);
|
|
35
|
+
}
|
|
36
|
+
const loc = locOf(node);
|
|
37
|
+
const local = lowerer.declareHiddenLocal("%arrayPosition", value.type);
|
|
38
|
+
const ref = varRef(local.id, value.type, loc);
|
|
39
|
+
return {
|
|
40
|
+
kind: "seqExpr",
|
|
41
|
+
stmts: [{ kind: "varDecl", localId: local.id, init: value, loc }],
|
|
42
|
+
result: positionNumber(lowerer, ref, defaultValue, node, subject),
|
|
43
|
+
type: F64,
|
|
44
|
+
loc,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
19
47
|
/** Build the argument array for a mutating/copying operation while keeping
|
|
20
48
|
* the receiver's scalar payload ABI. A missing read is represented as the
|
|
21
49
|
* present UNDEFINED state in this temporary, so push/unshift and toSpliced
|
|
@@ -42,6 +70,68 @@ function lowerArrayValueItems(lowerer, values, elem, arrType, loc) {
|
|
|
42
70
|
}
|
|
43
71
|
return { kind: "seqExpr", stmts: body, result: outRef, type: arrType, loc };
|
|
44
72
|
}
|
|
73
|
+
function lowerArraySpreadItems(lowerer, nodes, elem, arrType, loc) {
|
|
74
|
+
const out = lowerer.declareHiddenLocal("%arrayItems", arrType);
|
|
75
|
+
const outRef = varRef(out.id, arrType, loc);
|
|
76
|
+
const length = () => ({ kind: "arrIntrinsic", method: "length", receiver: outRef, args: [], type: F64, loc });
|
|
77
|
+
const body = [
|
|
78
|
+
{ kind: "varDecl", localId: out.id, init: { kind: "arrayLit", elems: [], type: arrType, loc }, loc },
|
|
79
|
+
];
|
|
80
|
+
for (const node of nodes) {
|
|
81
|
+
if (ts.isSpreadElement(node)) {
|
|
82
|
+
const source = lowerer.lowerExpr(node.expression);
|
|
83
|
+
if (!typeEquals(source.type, arrType)) {
|
|
84
|
+
lowerer.noLowering(`Array insertion spread from '${lowerer.fmt(source.type)}'`, node);
|
|
85
|
+
}
|
|
86
|
+
body.push({
|
|
87
|
+
kind: "exprStmt",
|
|
88
|
+
expr: { kind: "arrIntrinsic", method: "pushSpread", receiver: outRef, args: [source], type: F64, loc },
|
|
89
|
+
loc,
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
const value = lowerer.lowerExpr(node);
|
|
94
|
+
const stored = lowerer.runtimeOptionalWidening(value.type, elem) !== null
|
|
95
|
+
? value : lowerer.coerceInto(node, value, elem);
|
|
96
|
+
body.push(arrayValueStore(lowerer, outRef, length(), stored, elem, loc));
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return { kind: "seqExpr", stmts: body, result: outRef, type: arrType, loc };
|
|
100
|
+
}
|
|
101
|
+
function lowerArrayJoinSeparator(lowerer, node, loc) {
|
|
102
|
+
const comma = strLit(",", loc);
|
|
103
|
+
if (!node)
|
|
104
|
+
return comma;
|
|
105
|
+
const undefinedValue = lowerStaticallyUndefinedArgument(lowerer, node);
|
|
106
|
+
if (undefinedValue)
|
|
107
|
+
return defaultAfterUndefined(undefinedValue, comma);
|
|
108
|
+
const value = lowerer.lowerExpr(node);
|
|
109
|
+
if (value.type.kind === "nullT")
|
|
110
|
+
return defaultAfterUndefined(value, strLit("null", loc));
|
|
111
|
+
if (value.type.kind === "union") {
|
|
112
|
+
const undefinedTag = lowerer.armTag(value.type.unionId, UNDEFINED_T);
|
|
113
|
+
if (undefinedTag >= 0) {
|
|
114
|
+
const local = lowerer.declareHiddenLocal("%joinSeparator", value.type);
|
|
115
|
+
const ref = varRef(local.id, value.type, loc);
|
|
116
|
+
return {
|
|
117
|
+
kind: "seqExpr",
|
|
118
|
+
stmts: [{ kind: "varDecl", localId: local.id, init: value, loc }],
|
|
119
|
+
result: {
|
|
120
|
+
kind: "ternary",
|
|
121
|
+
cond: { kind: "unionIsTag", unionId: value.type.unionId, tag: undefinedTag, value: ref, negated: false, type: BOOL, loc },
|
|
122
|
+
then: comma,
|
|
123
|
+
else_: lowerer.ensureString(ref, node),
|
|
124
|
+
type: STRING, loc,
|
|
125
|
+
},
|
|
126
|
+
type: STRING, loc,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
if (value.type.kind === "dyn" || value.type.kind === "jsval") {
|
|
131
|
+
lowerer.noLowering(".join separator with a runtime-dependent undefined value", node);
|
|
132
|
+
}
|
|
133
|
+
return lowerer.ensureString(value, node);
|
|
134
|
+
}
|
|
45
135
|
/** Callback-driven array producers bypass mapType's ordinary T[] mapping:
|
|
46
136
|
* the callback is lowered first, then the helper's result array is built
|
|
47
137
|
* directly from its IR return type. Fence element kinds ScrArr cannot hold
|
|
@@ -181,24 +271,51 @@ export function lowerArrayMethodCall(lowerer, call, access) {
|
|
|
181
271
|
loc,
|
|
182
272
|
};
|
|
183
273
|
}
|
|
274
|
+
if (name === "fill") {
|
|
275
|
+
if (call.arguments.length > 3 || call.arguments.some(ts.isSpreadElement)) {
|
|
276
|
+
lowerer.noLowering(`.fill with ${call.arguments.length} arguments`, call);
|
|
277
|
+
}
|
|
278
|
+
const value = call.arguments[0] ? lowerer.lowerExpr(call.arguments[0]) : null;
|
|
279
|
+
if (value && (value.type.kind === "dyn" || value.type.kind === "jsval") &&
|
|
280
|
+
isRefCounted(elem) && elem.kind !== "string" && !typeEquals(value.type, elem)) {
|
|
281
|
+
lowerer.noLowering(".fill of a checked-dynamic reference into a static array", call.arguments[0]);
|
|
282
|
+
}
|
|
283
|
+
const writeUndefined = value === null || value.type.kind === "undefinedT";
|
|
284
|
+
return lowerArrayFill(lowerer, lowerer.lowerExpr(access.expression), value, writeUndefined, lowerArrayPosition(lowerer, call.arguments[1], numLit(0, loc), "array fill start"), lowerArrayPosition(lowerer, call.arguments[2], numLit(Infinity, loc), "array fill end"), receiverIr, loc);
|
|
285
|
+
}
|
|
286
|
+
if (name === "copyWithin") {
|
|
287
|
+
if (call.arguments.length > 3 || call.arguments.some(ts.isSpreadElement)) {
|
|
288
|
+
lowerer.noLowering(`.copyWithin with ${call.arguments.length} arguments`, call);
|
|
289
|
+
}
|
|
290
|
+
return lowerArrayCopyWithin(lowerer, lowerer.lowerExpr(access.expression), lowerArrayPosition(lowerer, call.arguments[0], numLit(0, loc), "array copyWithin target"), lowerArrayPosition(lowerer, call.arguments[1], numLit(0, loc), "array copyWithin start"), lowerArrayPosition(lowerer, call.arguments[2], numLit(Infinity, loc), "array copyWithin end"), receiverIr, loc);
|
|
291
|
+
}
|
|
184
292
|
if (name === "with") {
|
|
185
293
|
if (call.arguments.length !== 2 || call.arguments.some(ts.isSpreadElement)) {
|
|
186
294
|
lowerer.noLowering(`.with with ${call.arguments.length} arguments`, call);
|
|
187
295
|
}
|
|
188
296
|
const receiver = lowerer.lowerExpr(access.expression);
|
|
189
|
-
const index = lowerer
|
|
297
|
+
const index = lowerPositionArgument(lowerer, call.arguments[0], numLit(0, loc));
|
|
298
|
+
if (!primitivePositionType(lowerer, index.type)) {
|
|
299
|
+
lowerer.noLowering(`.with index of '${lowerer.fmt(index.type)}' values`, call.arguments[0]);
|
|
300
|
+
}
|
|
190
301
|
const value = lowerer.lowerExpr(call.arguments[1]);
|
|
302
|
+
const receiverLocal = lowerer.declareHiddenLocal("%withRecv", receiverIr);
|
|
303
|
+
const indexLocal = lowerer.declareHiddenLocal("%withIndex", index.type);
|
|
304
|
+
const valueLocal = lowerer.declareHiddenLocal("%withValue", value.type);
|
|
305
|
+
const receiverRef = varRef(receiverLocal.id, receiverIr, loc);
|
|
306
|
+
const indexRef = varRef(indexLocal.id, index.type, loc);
|
|
307
|
+
const valueRef = varRef(valueLocal.id, value.type, loc);
|
|
308
|
+
const numericIndex = positionNumber(lowerer, indexRef, numLit(0, loc), call.arguments[0], "array with index");
|
|
309
|
+
const stmts = [
|
|
310
|
+
{ kind: "varDecl", localId: receiverLocal.id, init: receiver, loc },
|
|
311
|
+
{ kind: "varDecl", localId: indexLocal.id, init: index, loc },
|
|
312
|
+
{ kind: "varDecl", localId: valueLocal.id, init: value, loc },
|
|
313
|
+
];
|
|
191
314
|
if (value.type.kind === "union" && lowerer.runtimeOptionalWidening(value.type, elem) !== null) {
|
|
192
315
|
// `.with()` validates its index and returns a fresh copy. Stabilize
|
|
193
316
|
// all three operands, then select the scalar replacement or the
|
|
194
317
|
// present-undefined copy without evaluating a read twice.
|
|
195
|
-
const receiverLocal = lowerer.declareHiddenLocal("%withRecv", receiverIr);
|
|
196
|
-
const indexLocal = lowerer.declareHiddenLocal("%withIndex", F64);
|
|
197
|
-
const valueLocal = lowerer.declareHiddenLocal("%withValue", value.type);
|
|
198
318
|
const unionId = value.type.unionId;
|
|
199
|
-
const receiverRef = varRef(receiverLocal.id, receiverIr, loc);
|
|
200
|
-
const indexRef = varRef(indexLocal.id, F64, loc);
|
|
201
|
-
const valueRef = varRef(valueLocal.id, value.type, loc);
|
|
202
319
|
const undefTag = lowerer.armTag(unionId, UNDEFINED_T);
|
|
203
320
|
const condition = {
|
|
204
321
|
kind: "unionIsTag",
|
|
@@ -213,7 +330,7 @@ export function lowerArrayMethodCall(lowerer, call, access) {
|
|
|
213
330
|
kind: "arrIntrinsic",
|
|
214
331
|
method: "withUndefined",
|
|
215
332
|
receiver: receiverRef,
|
|
216
|
-
args: [
|
|
333
|
+
args: [numericIndex],
|
|
217
334
|
type: receiverIr,
|
|
218
335
|
loc,
|
|
219
336
|
};
|
|
@@ -221,62 +338,45 @@ export function lowerArrayMethodCall(lowerer, call, access) {
|
|
|
221
338
|
kind: "arrIntrinsic",
|
|
222
339
|
method: "with",
|
|
223
340
|
receiver: receiverRef,
|
|
224
|
-
args: [
|
|
341
|
+
args: [numericIndex, lowerer.coerceToExpected(valueRef, elem)],
|
|
225
342
|
type: receiverIr,
|
|
226
343
|
loc,
|
|
227
344
|
};
|
|
228
345
|
return {
|
|
229
346
|
kind: "seqExpr",
|
|
230
|
-
stmts
|
|
231
|
-
{ kind: "varDecl", localId: receiverLocal.id, init: receiver, loc },
|
|
232
|
-
{ kind: "varDecl", localId: indexLocal.id, init: index, loc },
|
|
233
|
-
{ kind: "varDecl", localId: valueLocal.id, init: value, loc },
|
|
234
|
-
],
|
|
347
|
+
stmts,
|
|
235
348
|
result: { kind: "ternary", cond: condition, then: missing, else_: present, type: receiverIr, loc },
|
|
236
349
|
type: receiverIr,
|
|
237
350
|
loc,
|
|
238
351
|
};
|
|
239
352
|
}
|
|
240
|
-
const coercedValue = lowerer.coerceInto(call.arguments[1],
|
|
353
|
+
const coercedValue = lowerer.coerceInto(call.arguments[1], valueRef, elem);
|
|
241
354
|
return {
|
|
242
|
-
kind: "
|
|
243
|
-
|
|
244
|
-
receiver,
|
|
245
|
-
args: [index, coercedValue],
|
|
355
|
+
kind: "seqExpr",
|
|
356
|
+
stmts,
|
|
357
|
+
result: { kind: "arrIntrinsic", method: "with", receiver: receiverRef, args: [numericIndex, coercedValue], type: receiverIr, loc },
|
|
246
358
|
type: receiverIr,
|
|
247
359
|
loc,
|
|
248
360
|
};
|
|
249
361
|
}
|
|
250
362
|
if (name === "toSpliced") {
|
|
251
|
-
if (call.arguments.some(ts.isSpreadElement)) {
|
|
252
|
-
lowerer.unsupported("SC1090", call, "spread arguments to Array.toSpliced");
|
|
253
|
-
}
|
|
254
363
|
const receiver = lowerer.lowerExpr(access.expression);
|
|
255
|
-
const start = call.arguments[0]
|
|
256
|
-
|
|
257
|
-
: { kind: "numLit", value: 0, type: F64, loc };
|
|
258
|
-
const deleteCountDefault = {
|
|
259
|
-
kind: "numLit",
|
|
260
|
-
value: NaN,
|
|
261
|
-
type: F64,
|
|
262
|
-
loc,
|
|
263
|
-
};
|
|
264
|
-
const deleteCount = call.arguments.length === 0
|
|
265
|
-
? { kind: "numLit", value: 0, type: F64, loc }
|
|
266
|
-
: call.arguments[1]
|
|
267
|
-
? lowerOptionalArgument(lowerer, call.arguments[1], F64, deleteCountDefault)
|
|
268
|
-
: { kind: "numLit", value: Infinity, type: F64, loc };
|
|
364
|
+
const start = lowerArrayPosition(lowerer, call.arguments[0], numLit(0, loc), "array toSpliced start");
|
|
365
|
+
const deleteCount = lowerArrayPosition(lowerer, call.arguments[1], numLit(call.arguments.length === 1 ? Infinity : 0, loc), "array toSpliced deleteCount");
|
|
269
366
|
const itemNodes = call.arguments.slice(2);
|
|
270
|
-
const
|
|
367
|
+
const hasSpread = itemNodes.some(ts.isSpreadElement);
|
|
368
|
+
const itemProbes = hasSpread ? [] : itemNodes.map((arg) => tryLowerExpression(lowerer, arg));
|
|
271
369
|
const statefulItems = itemProbes.some((probe) => probe !== null && lowerer.runtimeOptionalWidening(probe.type, elem) !== null);
|
|
272
|
-
const items =
|
|
273
|
-
?
|
|
274
|
-
:
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
370
|
+
const items = hasSpread
|
|
371
|
+
? lowerArraySpreadItems(lowerer, itemNodes, elem, receiverIr, loc)
|
|
372
|
+
: statefulItems
|
|
373
|
+
? lowerArrayValueItems(lowerer, itemNodes.map((arg) => lowerer.lowerExpr(arg)), elem, receiverIr, loc)
|
|
374
|
+
: {
|
|
375
|
+
kind: "arrayLit",
|
|
376
|
+
elems: itemNodes.map((arg) => lowerer.coerceInto(arg, lowerer.lowerExpr(arg), elem)),
|
|
377
|
+
type: receiverIr,
|
|
378
|
+
loc,
|
|
379
|
+
};
|
|
280
380
|
return {
|
|
281
381
|
kind: "arrIntrinsic",
|
|
282
382
|
method: "toSpliced",
|
|
@@ -286,32 +386,27 @@ export function lowerArrayMethodCall(lowerer, call, access) {
|
|
|
286
386
|
loc,
|
|
287
387
|
};
|
|
288
388
|
}
|
|
289
|
-
|
|
290
|
-
|
|
389
|
+
if (name === "flat")
|
|
390
|
+
return lowerArrayFlatCall(lowerer, call, access, receiverIr);
|
|
391
|
+
// The lib declares wider call forms than the lowered surface — the
|
|
291
392
|
// predicate/mapping HOFs take a thisArg. Each unlowered form is fenced
|
|
292
393
|
// per site (SC2020), never silently truncated to the supported
|
|
293
394
|
// arguments. push/unshift lower every declared form (variadic, 0 args
|
|
294
395
|
// included — Node returns the unchanged length); reduce/reduceRight
|
|
295
396
|
// lower both declared forms (with and without an initial value).
|
|
296
397
|
const arity = {
|
|
297
|
-
push: [0, Number.MAX_SAFE_INTEGER], unshift: [0, Number.MAX_SAFE_INTEGER], pop: [0, 0], indexOf: [1, 1], includes: [
|
|
398
|
+
push: [0, Number.MAX_SAFE_INTEGER], unshift: [0, Number.MAX_SAFE_INTEGER], pop: [0, 0], indexOf: [1, 2], lastIndexOf: [1, 2], includes: [0, 2], join: [0, 1],
|
|
298
399
|
concat: [0, Number.MAX_SAFE_INTEGER],
|
|
299
|
-
slice: [0, 2], shift: [0, 0], splice: [
|
|
400
|
+
slice: [0, 2], shift: [0, 0], splice: [0, Number.MAX_SAFE_INTEGER], at: [0, 1],
|
|
300
401
|
map: [1, 1], filter: [1, 1], forEach: [1, 1], find: [1, 1], findIndex: [1, 1], some: [1, 1],
|
|
301
402
|
findLast: [1, 1], findLastIndex: [1, 1],
|
|
302
403
|
every: [1, 1], flatMap: [1, 1], reduce: [1, 2], reduceRight: [1, 2],
|
|
303
404
|
}[name];
|
|
304
405
|
if (call.arguments.length < arity[0] || call.arguments.length > arity[1]) {
|
|
305
|
-
const hint = name === "
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
: name === "indexOf" || name === "includes"
|
|
310
|
-
? "the fromIndex parameter has no lowering — slice first, or loop"
|
|
311
|
-
: name === "map" || name === "filter" || name === "forEach" ||
|
|
312
|
-
name === "find" || name === "some" || name === "every" || name === "flatMap"
|
|
313
|
-
? "the thisArg parameter has no lowering — use an arrow function"
|
|
314
|
-
: undefined;
|
|
406
|
+
const hint = name === "map" || name === "filter" || name === "forEach" ||
|
|
407
|
+
name === "find" || name === "some" || name === "every" || name === "flatMap"
|
|
408
|
+
? "the thisArg parameter has no lowering — use an arrow function"
|
|
409
|
+
: undefined;
|
|
315
410
|
lowerer.noLowering(`.${name} with ${call.arguments.length} argument${call.arguments.length === 1 ? "" : "s"}`, call, hint);
|
|
316
411
|
}
|
|
317
412
|
if (name === "push" || name === "unshift") {
|
|
@@ -393,7 +488,7 @@ export function lowerArrayMethodCall(lowerer, call, access) {
|
|
|
393
488
|
loc,
|
|
394
489
|
};
|
|
395
490
|
}
|
|
396
|
-
if (name === "indexOf" || name === "includes") {
|
|
491
|
+
if (name === "indexOf" || name === "lastIndexOf" || name === "includes") {
|
|
397
492
|
return lowerArraySearchCall(lowerer, call, access, name, elem);
|
|
398
493
|
}
|
|
399
494
|
if (name === "concat") {
|
|
@@ -452,33 +547,37 @@ export function lowerArrayMethodCall(lowerer, call, access) {
|
|
|
452
547
|
return { kind: "call", callee: helper, args: [receiver, ...args], type: receiverIr, loc };
|
|
453
548
|
}
|
|
454
549
|
if (name === "slice") {
|
|
455
|
-
// `a.slice(start?, end?)` — a fresh shallow copy of the index range,
|
|
456
|
-
// JS-exact index handling (ToIntegerOrInfinity, negatives from the
|
|
457
|
-
// end, clamping; omitted args are omitted from the IR — the backend
|
|
458
|
-
// fills 0 / +Infinity, the string-slice convention). Ref elements
|
|
459
|
-
// are RETAINED into the copy: the same references, exactly JS's
|
|
460
|
-
// shallow copy. Every element kind slices — the receiver's own type
|
|
461
|
-
// is the result type.
|
|
462
550
|
const receiver = lowerer.lowerExpr(access.expression);
|
|
463
|
-
const args =
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
}
|
|
551
|
+
const args = [
|
|
552
|
+
lowerArrayPosition(lowerer, call.arguments[0], numLit(0, loc), "array slice start"),
|
|
553
|
+
lowerArrayPosition(lowerer, call.arguments[1], numLit(Infinity, loc), "array slice end"),
|
|
554
|
+
];
|
|
468
555
|
return { kind: "arrIntrinsic", method: "slice", receiver, args, type: receiverIr, loc };
|
|
469
556
|
}
|
|
470
557
|
if (name === "splice") {
|
|
471
|
-
// The REMOVAL forms: splice(start) and splice(start, deleteCount) —
|
|
472
|
-
// Node-exact relative/clamped indices, the removed elements back in
|
|
473
|
-
// order (their ownership moves out of the receiver). Insertion
|
|
474
|
-
// (3+ args) fenced by arity above.
|
|
475
558
|
const receiver = lowerer.lowerExpr(access.expression);
|
|
476
|
-
const args =
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
559
|
+
const args = [
|
|
560
|
+
lowerArrayPosition(lowerer, call.arguments[0], numLit(0, loc), "array splice start"),
|
|
561
|
+
lowerArrayPosition(lowerer, call.arguments[1], numLit(call.arguments.length === 1 ? Infinity : 0, loc), "array splice deleteCount"),
|
|
562
|
+
];
|
|
563
|
+
if (call.arguments.length <= 2) {
|
|
564
|
+
return { kind: "arrIntrinsic", method: "splice", receiver, args, type: receiverIr, loc };
|
|
480
565
|
}
|
|
481
|
-
|
|
566
|
+
const itemNodes = call.arguments.slice(2);
|
|
567
|
+
const hasSpread = itemNodes.some(ts.isSpreadElement);
|
|
568
|
+
const itemProbes = hasSpread ? [] : itemNodes.map((arg) => tryLowerExpression(lowerer, arg));
|
|
569
|
+
const statefulItems = itemProbes.some((probe) => probe !== null && lowerer.runtimeOptionalWidening(probe.type, elem) !== null);
|
|
570
|
+
const items = hasSpread
|
|
571
|
+
? lowerArraySpreadItems(lowerer, itemNodes, elem, receiverIr, loc)
|
|
572
|
+
: statefulItems
|
|
573
|
+
? lowerArrayValueItems(lowerer, itemNodes.map((arg) => lowerer.lowerExpr(arg)), elem, receiverIr, loc)
|
|
574
|
+
: {
|
|
575
|
+
kind: "arrayLit",
|
|
576
|
+
elems: itemNodes.map((arg) => lowerer.coerceInto(arg, lowerer.lowerExpr(arg), elem)),
|
|
577
|
+
type: receiverIr,
|
|
578
|
+
loc,
|
|
579
|
+
};
|
|
580
|
+
return { kind: "arrIntrinsic", method: "spliceInsert", receiver, args: [...args, items], type: receiverIr, loc };
|
|
482
581
|
}
|
|
483
582
|
if (name === "shift") {
|
|
484
583
|
// JS shift exactly: undefined on an empty array, else the first
|
|
@@ -511,7 +610,7 @@ export function lowerArrayMethodCall(lowerer, call, access) {
|
|
|
511
610
|
lowerer.unsupported("SC1090", call, "'.join()' on arrays of this element type (number, string, and boolean arrays join — unions of those with undefined/null arms too, the units printing empty like JS)");
|
|
512
611
|
}
|
|
513
612
|
const receiver = lowerer.lowerExpr(access.expression);
|
|
514
|
-
const sep = lowerer
|
|
613
|
+
const sep = lowerArrayJoinSeparator(lowerer, call.arguments[0], loc);
|
|
515
614
|
return { kind: "arrIntrinsic", method: "join", receiver, args: [sep], type: STRING, loc };
|
|
516
615
|
}
|
|
517
616
|
if (name === "map" || name === "filter" || name === "forEach") {
|
|
@@ -528,17 +627,99 @@ export function lowerArrayMethodCall(lowerer, call, access) {
|
|
|
528
627
|
// reduce / reduceRight
|
|
529
628
|
return lowerArrayReduceCall(lowerer, call, access, name, elem);
|
|
530
629
|
}
|
|
630
|
+
function literalFlatDepth(value) {
|
|
631
|
+
if (value.kind === "numLit")
|
|
632
|
+
return value.value;
|
|
633
|
+
if (value.kind === "strLit")
|
|
634
|
+
return Number(value.value);
|
|
635
|
+
if (value.kind === "boolLit")
|
|
636
|
+
return value.value ? 1 : 0;
|
|
637
|
+
if (value.kind === "unitLit")
|
|
638
|
+
return value.unit === "undefined" ? 1 : 0;
|
|
639
|
+
if (value.kind === "unary" && value.op === "-") {
|
|
640
|
+
const operand = literalFlatDepth(value.operand);
|
|
641
|
+
return operand === null ? null : -operand;
|
|
642
|
+
}
|
|
643
|
+
return null;
|
|
644
|
+
}
|
|
645
|
+
function lowerArrayFlatCall(lowerer, call, access, receiverType) {
|
|
646
|
+
const loc = locOf(call);
|
|
647
|
+
if (call.arguments.length > 1 || call.arguments.some(ts.isSpreadElement)) {
|
|
648
|
+
lowerer.noLowering(`.flat with ${call.arguments.length} arguments`, call);
|
|
649
|
+
}
|
|
650
|
+
const elemCouldNest = (elem) => elem.kind === "array" || elem.kind === "jsval" || elem.kind === "dyn" ||
|
|
651
|
+
(elem.kind === "union" && (lowerer.unions.get(elem.unionId)?.arms.some((arm) => arm.kind === "array" || arm.kind === "jsval" || arm.kind === "dyn") ?? true));
|
|
652
|
+
if (!elemCouldNest(receiverType.elem) && call.arguments[0]) {
|
|
653
|
+
const receiver = lowerer.lowerExpr(access.expression);
|
|
654
|
+
const depth = lowerArrayPosition(lowerer, call.arguments[0], numLit(1, loc), "array flat depth");
|
|
655
|
+
const local = lowerer.declareHiddenLocal("%flatReceiver", receiverType);
|
|
656
|
+
const ref = varRef(local.id, receiverType, loc);
|
|
657
|
+
return {
|
|
658
|
+
kind: "seqExpr",
|
|
659
|
+
stmts: [
|
|
660
|
+
{ kind: "varDecl", localId: local.id, init: receiver, loc },
|
|
661
|
+
{ kind: "exprStmt", expr: depth, loc },
|
|
662
|
+
],
|
|
663
|
+
result: {
|
|
664
|
+
kind: "arrIntrinsic", method: "flatCopy", receiver: ref,
|
|
665
|
+
args: [{ kind: "arrayLit", elems: [], type: receiverType, loc }],
|
|
666
|
+
type: receiverType, loc,
|
|
667
|
+
},
|
|
668
|
+
type: receiverType, loc,
|
|
669
|
+
};
|
|
670
|
+
}
|
|
671
|
+
const depth = call.arguments[0] ? literalFlatDepth(lowerer.lowerExpr(call.arguments[0])) : 1;
|
|
672
|
+
if (depth === null)
|
|
673
|
+
lowerer.noLowering(".flat with a nonconstant depth", call);
|
|
674
|
+
let remaining = Number.isNaN(depth) || depth <= 0 ? 0 : Math.trunc(depth);
|
|
675
|
+
let result = lowerer.lowerExpr(access.expression);
|
|
676
|
+
let currentType = receiverType;
|
|
677
|
+
let flattened = false;
|
|
678
|
+
while (remaining > 0) {
|
|
679
|
+
const elem = currentType.elem;
|
|
680
|
+
if (elem.kind !== "array" && elemCouldNest(elem)) {
|
|
681
|
+
lowerer.noLowering(".flat over elements with runtime-dependent array shape", call);
|
|
682
|
+
}
|
|
683
|
+
if (elem.kind !== "array")
|
|
684
|
+
break;
|
|
685
|
+
result = {
|
|
686
|
+
kind: "arrIntrinsic", method: "flatOne", receiver: result,
|
|
687
|
+
args: [{ kind: "arrayLit", elems: [], type: elem, loc }],
|
|
688
|
+
type: elem, loc,
|
|
689
|
+
};
|
|
690
|
+
currentType = elem;
|
|
691
|
+
remaining--;
|
|
692
|
+
flattened = true;
|
|
693
|
+
}
|
|
694
|
+
if (!flattened) {
|
|
695
|
+
result = {
|
|
696
|
+
kind: "arrIntrinsic", method: "flatCopy", receiver: result,
|
|
697
|
+
args: [{ kind: "arrayLit", elems: [], type: currentType, loc }],
|
|
698
|
+
type: currentType, loc,
|
|
699
|
+
};
|
|
700
|
+
}
|
|
701
|
+
return result;
|
|
702
|
+
}
|
|
531
703
|
/** `indexOf` and `includes` normally use the runtime's typed search helpers,
|
|
532
704
|
* but an indexed array read is a `T | undefined` value. Keep that union
|
|
533
705
|
* through a small lowered loop so the needle's missing state is observable:
|
|
534
|
-
* indexOf
|
|
535
|
-
* loop also gives union-element arrays their value
|
|
536
|
-
* comparing the compiler's union boxes by pointer. */
|
|
706
|
+
* indexOf and lastIndexOf skip holes, while includes treats holes as
|
|
707
|
+
* undefined. The same loop also gives union-element arrays their value
|
|
708
|
+
* equality rather than comparing the compiler's union boxes by pointer. */
|
|
537
709
|
function lowerArraySearchCall(lowerer, call, access, method, elem) {
|
|
538
710
|
const loc = locOf(call);
|
|
539
711
|
const receiver = lowerer.lowerExpr(access.expression);
|
|
540
|
-
const argNode = call.arguments[0];
|
|
541
|
-
|
|
712
|
+
const argNode = call.arguments[0] ?? call;
|
|
713
|
+
if (call.arguments.some(ts.isSpreadElement))
|
|
714
|
+
lowerer.noLowering(`.${method} with spread arguments`, call);
|
|
715
|
+
let needle = call.arguments[0]
|
|
716
|
+
? lowerer.lowerExpr(call.arguments[0])
|
|
717
|
+
: { kind: "unitLit", unit: "undefined", type: UNDEFINED_T, loc };
|
|
718
|
+
const fromIndex = call.arguments[1]
|
|
719
|
+
? lowerPositionArgument(lowerer, call.arguments[1], numLit(0, loc))
|
|
720
|
+
: method === "lastIndexOf"
|
|
721
|
+
? { kind: "bin", op: "/", left: numLit(1, loc), right: numLit(0, loc), type: F64, loc }
|
|
722
|
+
: numLit(0, loc);
|
|
542
723
|
// A derived CLASS VALUE against a base-classval element widens (the same
|
|
543
724
|
// pointer — identity search is exact); the coercion path owns the ABI gate.
|
|
544
725
|
if (needle.type.kind === "classval" && elem.kind === "classval" && !typeEquals(needle.type, elem)) {
|
|
@@ -546,21 +727,25 @@ function lowerArraySearchCall(lowerer, call, access, method, elem) {
|
|
|
546
727
|
}
|
|
547
728
|
// Preserve the existing fast path for an ordinary, non-union needle. Its
|
|
548
729
|
// runtime implementation already has exact primitive/reference equality.
|
|
549
|
-
if (elem.kind !== "union" && elem.kind !== "bigint" && typeEquals(needle.type, elem)) {
|
|
730
|
+
if (method !== "lastIndexOf" && call.arguments.length === 1 && elem.kind !== "union" && elem.kind !== "bigint" && typeEquals(needle.type, elem)) {
|
|
550
731
|
return {
|
|
551
732
|
kind: "arrIntrinsic",
|
|
552
733
|
method,
|
|
553
734
|
receiver,
|
|
554
735
|
args: [needle],
|
|
555
|
-
type: method === "
|
|
736
|
+
type: method === "includes" ? BOOL : F64,
|
|
556
737
|
loc,
|
|
557
738
|
};
|
|
558
739
|
}
|
|
559
|
-
|
|
740
|
+
let valueT = arrayValueType(lowerer, elem);
|
|
560
741
|
// jsval/dyn arrays have a single engine value representation rather than a
|
|
561
742
|
// static undefined arm. Keep their existing runtime path and its explicit
|
|
562
743
|
// type fence; typed static arrays take the state-aware helper below.
|
|
563
744
|
if (valueT.kind !== "union") {
|
|
745
|
+
if (method === "lastIndexOf")
|
|
746
|
+
lowerer.noLowering(".lastIndexOf on an array of engine values", call);
|
|
747
|
+
if (call.arguments.length > 1)
|
|
748
|
+
lowerer.noLowering(`.${method} with fromIndex on '${lowerer.fmt(elem)}' elements`, call);
|
|
564
749
|
if (!typeEquals(needle.type, elem))
|
|
565
750
|
lowerer.badType(argNode, lowerer.typeOf(argNode));
|
|
566
751
|
return {
|
|
@@ -568,15 +753,38 @@ function lowerArraySearchCall(lowerer, call, access, method, elem) {
|
|
|
568
753
|
method,
|
|
569
754
|
receiver,
|
|
570
755
|
args: [needle],
|
|
571
|
-
type: method === "
|
|
756
|
+
type: method === "includes" ? BOOL : F64,
|
|
572
757
|
loc,
|
|
573
758
|
};
|
|
574
759
|
}
|
|
575
|
-
|
|
576
|
-
|
|
760
|
+
// Search uses strict equality, never conversion to the array's element
|
|
761
|
+
// type. Retain primitive needles of a different kind in the comparison
|
|
762
|
+
// union so a nonmatching kind returns a miss instead of throwing.
|
|
763
|
+
const needleArms = needle.type.kind === "union"
|
|
764
|
+
? lowerer.unions.get(needle.type.unionId).arms
|
|
765
|
+
: [needle.type];
|
|
766
|
+
const scalarNeedle = needleArms.every((arm) => arm.kind === "f64" || arm.kind === "string" || arm.kind === "bool" ||
|
|
767
|
+
arm.kind === "bigint" || arm.kind === "symbol" || isUnitType(arm));
|
|
768
|
+
const valueArms = lowerer.unions.get(valueT.unionId).arms;
|
|
769
|
+
if (scalarNeedle && !valueArms.some((arm) => arm.kind === "func" || arm.kind === "set")) {
|
|
770
|
+
const arms = [...valueArms];
|
|
771
|
+
for (const arm of needleArms)
|
|
772
|
+
if (!arms.some((existing) => typeEquals(existing, arm)))
|
|
773
|
+
arms.push(arm);
|
|
774
|
+
if (arms.length !== valueArms.length)
|
|
775
|
+
valueT = { kind: "union", unionId: lowerer.unions.intern(arms) };
|
|
776
|
+
}
|
|
777
|
+
const directNeedle = method === "lastIndexOf" && elem.kind !== "union" && typeEquals(needle.type, elem);
|
|
778
|
+
const searchNeedle = directNeedle ? needle : lowerer.coerceInto(argNode, needle, valueT);
|
|
779
|
+
// Checked dynamic extraction can copy a composite. Its new identity cannot
|
|
780
|
+
// be compared to an array element as though it were the original value.
|
|
781
|
+
if (method === "lastIndexOf" && searchNeedle.kind === "dynCheck" && (elem.kind === "record" || elem.kind === "array")) {
|
|
782
|
+
lowerer.noLowering(".lastIndexOf with a checked dynamic reference needle", call);
|
|
783
|
+
}
|
|
784
|
+
if (!directNeedle && !typeEquals(searchNeedle.type, valueT))
|
|
577
785
|
lowerer.badType(argNode, lowerer.typeOf(argNode));
|
|
578
|
-
const helper = arraySearchHelper(lowerer, method, elem, valueT, loc);
|
|
579
|
-
return { kind: "call", callee: helper, args: [receiver, searchNeedle], type: method === "
|
|
786
|
+
const helper = arraySearchHelper(lowerer, method, elem, valueT, searchNeedle.type, directNeedle, fromIndex.type, call.arguments[1] ?? call, loc);
|
|
787
|
+
return { kind: "call", callee: helper, args: [receiver, searchNeedle, fromIndex], type: method === "includes" ? BOOL : F64, loc };
|
|
580
788
|
}
|
|
581
789
|
/** SameValueZero over a static value union. `unionEq` supplies strict
|
|
582
790
|
* equality for every arm; the extra f64-arm test is the only difference for
|
|
@@ -620,25 +828,65 @@ function arraySearchEquality(lowerer, left, right, sameValueZero, loc) {
|
|
|
620
828
|
};
|
|
621
829
|
return { kind: "logical", op: "||", left: strict, right: bothNaN, type: BOOL, loc };
|
|
622
830
|
}
|
|
623
|
-
function arraySearchHelper(lowerer, method, elem, valueT, loc) {
|
|
624
|
-
const key = `${method}:${typeKey(elem)}:${typeKey(valueT)}`;
|
|
831
|
+
function arraySearchHelper(lowerer, method, elem, valueT, needleT, directNeedle, fromT, fromNode, loc) {
|
|
832
|
+
const key = `${method}:${typeKey(elem)}:${typeKey(valueT)}:${typeKey(needleT)}:${directNeedle}:${typeKey(fromT)}`;
|
|
625
833
|
const existing = lowerer.arrHofHelpers.get(key);
|
|
626
834
|
if (existing)
|
|
627
835
|
return existing;
|
|
628
836
|
const name = `%arr.${method}.${lowerer.arrHofHelpers.size}`;
|
|
629
837
|
lowerer.arrHofHelpers.set(key, name);
|
|
630
838
|
const arrT = arrayOf(elem);
|
|
631
|
-
const resultT = method === "
|
|
839
|
+
const resultT = method === "includes" ? BOOL : F64;
|
|
632
840
|
const arrRef = varRef("a.0", arrT, loc);
|
|
633
841
|
const indexRef = varRef("i.0", F64, loc);
|
|
634
|
-
const needleRef = varRef("needle.0",
|
|
842
|
+
const needleRef = varRef("needle.0", needleT, loc);
|
|
635
843
|
const stateRef = varRef("state.0", F64, loc);
|
|
636
844
|
const nextRef = varRef("next.0", F64, loc);
|
|
845
|
+
const fromRef = varRef("from.0", fromT, loc);
|
|
846
|
+
const numberRef = varRef("number.0", F64, loc);
|
|
847
|
+
const integerRef = varRef("integer.0", F64, loc);
|
|
848
|
+
// Snapshot length before coercing fromIndex, then apply ToIntegerOrInfinity.
|
|
849
|
+
const number = positionNumber(lowerer, fromRef, numLit(0, loc), fromNode, "array search position");
|
|
850
|
+
const integer = {
|
|
851
|
+
kind: "ternary",
|
|
852
|
+
cond: { kind: "libCall", fn: "num.isNaN", args: [numberRef], type: BOOL, loc },
|
|
853
|
+
then: numLit(0, loc),
|
|
854
|
+
else_: { kind: "libCall", fn: "math.trunc", args: [numberRef], type: F64, loc },
|
|
855
|
+
type: F64,
|
|
856
|
+
loc,
|
|
857
|
+
};
|
|
858
|
+
const forwardStart = {
|
|
859
|
+
kind: "ternary",
|
|
860
|
+
cond: { kind: "bin", op: "<", left: integerRef, right: numLit(0, loc), type: BOOL, loc },
|
|
861
|
+
then: {
|
|
862
|
+
kind: "libCall", fn: "math.max",
|
|
863
|
+
args: [{ kind: "bin", op: "+", left: varRef("n.0", F64, loc), right: integerRef, type: F64, loc }, numLit(0, loc)],
|
|
864
|
+
type: F64, loc,
|
|
865
|
+
},
|
|
866
|
+
// Adding positive zero canonicalizes a truncated negative zero.
|
|
867
|
+
else_: { kind: "bin", op: "+", left: integerRef, right: numLit(0, loc), type: F64, loc },
|
|
868
|
+
type: F64,
|
|
869
|
+
loc,
|
|
870
|
+
};
|
|
871
|
+
const start = method === "lastIndexOf" ? {
|
|
872
|
+
kind: "ternary",
|
|
873
|
+
cond: { kind: "bin", op: "<", left: integerRef, right: numLit(0, loc), type: BOOL, loc },
|
|
874
|
+
then: { kind: "bin", op: "+", left: varRef("n.0", F64, loc), right: integerRef, type: F64, loc },
|
|
875
|
+
else_: {
|
|
876
|
+
kind: "libCall", fn: "math.min",
|
|
877
|
+
args: [
|
|
878
|
+
{ kind: "bin", op: "+", left: integerRef, right: numLit(0, loc), type: F64, loc },
|
|
879
|
+
{ kind: "bin", op: "-", left: varRef("n.0", F64, loc), right: numLit(1, loc), type: F64, loc },
|
|
880
|
+
],
|
|
881
|
+
type: F64, loc,
|
|
882
|
+
},
|
|
883
|
+
type: F64, loc,
|
|
884
|
+
} : forwardStart;
|
|
637
885
|
const rawRead = { kind: "arrayGet", arr: arrRef, index: indexRef, type: elem, loc };
|
|
638
886
|
const missing = lowerer.wrappedUndefined(valueT, loc);
|
|
639
887
|
if (!missing)
|
|
640
888
|
throw new InternalCompilerError("array search helper needs an undefined arm");
|
|
641
|
-
const
|
|
889
|
+
const optionalValue = {
|
|
642
890
|
kind: "ternary",
|
|
643
891
|
cond: { kind: "bin", op: "===", left: stateRef, right: numLit(1, loc), type: BOOL, loc },
|
|
644
892
|
then: typeEquals(elem, valueT) ? rawRead : lowerer.coerceToExpected(rawRead, valueT),
|
|
@@ -646,12 +894,14 @@ function arraySearchHelper(lowerer, method, elem, valueT, loc) {
|
|
|
646
894
|
type: valueT,
|
|
647
895
|
loc,
|
|
648
896
|
};
|
|
649
|
-
const
|
|
650
|
-
const
|
|
897
|
+
const value = directNeedle ? rawRead : optionalValue;
|
|
898
|
+
const readT = directNeedle ? elem : valueT;
|
|
899
|
+
const equal = arraySearchEquality(lowerer, varRef("value.0", readT, loc), needleRef, method === "includes", loc);
|
|
900
|
+
const match = method !== "includes"
|
|
651
901
|
? {
|
|
652
902
|
kind: "logical",
|
|
653
903
|
op: "&&",
|
|
654
|
-
left: { kind: "bin", op: "!==", left: stateRef, right: numLit(0, loc), type: BOOL, loc },
|
|
904
|
+
left: { kind: "bin", op: directNeedle ? "===" : "!==", left: stateRef, right: numLit(directNeedle ? 1 : 0, loc), type: BOOL, loc },
|
|
655
905
|
right: equal,
|
|
656
906
|
type: BOOL,
|
|
657
907
|
loc,
|
|
@@ -660,7 +910,7 @@ function arraySearchHelper(lowerer, method, elem, valueT, loc) {
|
|
|
660
910
|
const undefinedTag = lowerer.armTag(valueT.unionId, UNDEFINED_T);
|
|
661
911
|
if (undefinedTag < 0)
|
|
662
912
|
throw new InternalCompilerError("array search helper needs an undefined tag");
|
|
663
|
-
const needleIsUndefined = {
|
|
913
|
+
const needleIsUndefined = directNeedle ? boolLit(false, loc) : {
|
|
664
914
|
kind: "unionIsTag",
|
|
665
915
|
unionId: valueT.unionId,
|
|
666
916
|
tag: undefinedTag,
|
|
@@ -672,7 +922,7 @@ function arraySearchHelper(lowerer, method, elem, valueT, loc) {
|
|
|
672
922
|
const advance = {
|
|
673
923
|
kind: "assign",
|
|
674
924
|
localId: "i.0",
|
|
675
|
-
value: { kind: "bin", op: "+", left: indexRef, right: numLit(1, loc), type: F64, loc },
|
|
925
|
+
value: { kind: "bin", op: method === "lastIndexOf" ? "-" : "+", left: indexRef, right: numLit(1, loc), type: F64, loc },
|
|
676
926
|
loc,
|
|
677
927
|
};
|
|
678
928
|
const visitPresent = [
|
|
@@ -680,13 +930,13 @@ function arraySearchHelper(lowerer, method, elem, valueT, loc) {
|
|
|
680
930
|
{
|
|
681
931
|
kind: "if",
|
|
682
932
|
cond: match,
|
|
683
|
-
then: [{ kind: "return", value: method === "
|
|
933
|
+
then: [{ kind: "return", value: method === "includes" ? boolLit(true, loc) : indexRef, loc }],
|
|
684
934
|
else_: null,
|
|
685
935
|
loc,
|
|
686
936
|
},
|
|
687
937
|
advance,
|
|
688
938
|
];
|
|
689
|
-
const skipHole = [
|
|
939
|
+
const skipHole = method === "lastIndexOf" ? [advance] : [
|
|
690
940
|
{
|
|
691
941
|
kind: "varDecl",
|
|
692
942
|
localId: "next.0",
|
|
@@ -712,28 +962,36 @@ function arraySearchHelper(lowerer, method, elem, valueT, loc) {
|
|
|
712
962
|
name,
|
|
713
963
|
params: [
|
|
714
964
|
{ localId: "a.0", name: "a", type: arrT },
|
|
715
|
-
{ localId: "needle.0", name: "needle", type:
|
|
965
|
+
{ localId: "needle.0", name: "needle", type: needleT },
|
|
966
|
+
{ localId: "from.0", name: "from", type: fromT },
|
|
716
967
|
],
|
|
717
968
|
returnType: resultT,
|
|
718
969
|
locals: [
|
|
719
970
|
{ id: "a.0", name: "a", type: arrT, mutable: true },
|
|
720
|
-
{ id: "needle.0", name: "needle", type:
|
|
971
|
+
{ id: "needle.0", name: "needle", type: needleT, mutable: true },
|
|
972
|
+
{ id: "from.0", name: "from", type: fromT, mutable: false },
|
|
973
|
+
{ id: "number.0", name: "number", type: F64, mutable: false },
|
|
974
|
+
{ id: "integer.0", name: "integer", type: F64, mutable: false },
|
|
721
975
|
{ id: "n.0", name: "n", type: F64, mutable: false },
|
|
722
976
|
{ id: "i.0", name: "i", type: F64, mutable: true },
|
|
723
977
|
{ id: "state.0", name: "state", type: F64, mutable: false },
|
|
724
978
|
{ id: "next.0", name: "next", type: F64, mutable: false },
|
|
725
|
-
{ id: "value.0", name: "value", type:
|
|
979
|
+
{ id: "value.0", name: "value", type: readT, mutable: false },
|
|
726
980
|
],
|
|
727
981
|
body: [
|
|
728
982
|
readLenStmt(arrT, loc),
|
|
729
|
-
{ kind: "varDecl", localId: "
|
|
983
|
+
{ kind: "varDecl", localId: "number.0", init: number, loc },
|
|
984
|
+
{ kind: "varDecl", localId: "integer.0", init: integer, loc },
|
|
985
|
+
{ kind: "varDecl", localId: "i.0", init: start, loc },
|
|
730
986
|
{
|
|
731
987
|
kind: "while",
|
|
732
|
-
cond:
|
|
988
|
+
cond: method === "lastIndexOf"
|
|
989
|
+
? { kind: "bin", op: ">=", left: indexRef, right: numLit(0, loc), type: BOOL, loc }
|
|
990
|
+
: { kind: "bin", op: "<", left: indexRef, right: varRef("n.0", F64, loc), type: BOOL, loc },
|
|
733
991
|
body: loopBody,
|
|
734
992
|
loc,
|
|
735
993
|
},
|
|
736
|
-
{ kind: "return", value: method === "
|
|
994
|
+
{ kind: "return", value: method === "includes" ? boolLit(false, loc) : numLit(-1, loc), loc },
|
|
737
995
|
],
|
|
738
996
|
loc,
|
|
739
997
|
});
|
|
@@ -1291,15 +1549,8 @@ export function lowerTupleReadMethodCall(lowerer, call, access) {
|
|
|
1291
1549
|
const helper = arrayHofHelper(lowerer, "map", snapshotElem, fnRet, arity, loc, outElem);
|
|
1292
1550
|
return { kind: "call", callee: helper, args: [snapshot, fnArg], type: arrayOf(outElem), loc };
|
|
1293
1551
|
}
|
|
1294
|
-
/** An
|
|
1295
|
-
|
|
1296
|
-
* is an integer in [0, len), JS's property-miss undefined otherwise —
|
|
1297
|
-
* instead of the trap divergence 4 documents for program code.
|
|
1298
|
-
* Package JS is inference-typed, guard-style code (`registeredArguments
|
|
1299
|
-
* .slice(-1)[0]`, commander's last-element probe), and the trap would
|
|
1300
|
-
* fire on working Node idioms; program files keep the documented trap
|
|
1301
|
-
* (their annotations can prove bounds). Existing element unions retag
|
|
1302
|
-
* into the canonical union with an added undefined arm. */
|
|
1552
|
+
/** An ordinary indexed read: values retain their element type, while
|
|
1553
|
+
* holes, missing properties, and present undefined yield undefined. */
|
|
1303
1554
|
export function lowerSafeIndexRead(lowerer, arr, index, loc) {
|
|
1304
1555
|
const elem = arr.type.elem;
|
|
1305
1556
|
if (elem.kind === "void" || elem.kind === "dyn")
|
|
@@ -1328,6 +1579,21 @@ export function lowerSafeIndexRead(lowerer, arr, index, loc) {
|
|
|
1328
1579
|
}
|
|
1329
1580
|
return { kind: "call", callee: name, args: [arr, index], type: resultT, loc };
|
|
1330
1581
|
}
|
|
1582
|
+
/** Fuse only our own f64-array read helper with immediate ToNumber.
|
|
1583
|
+
* Reuse its arguments verbatim so the receiver and index still evaluate
|
|
1584
|
+
* exactly once, in order, before the read. Do not specialize user calls,
|
|
1585
|
+
* union-element arrays, or optional values stored in locals. */
|
|
1586
|
+
export function tryLowerNumericIndexRead(lowerer, operand, loc) {
|
|
1587
|
+
if (operand.kind !== "call" || operand.callee !== lowerer.arrHofHelpers.get(`idxOr:${typeKey(F64)}`))
|
|
1588
|
+
return null;
|
|
1589
|
+
const [arr, index] = operand.args;
|
|
1590
|
+
if (operand.args.length !== 2 || arr?.type.kind !== "array" || arr.type.elem.kind !== "f64" || index?.type.kind !== "f64")
|
|
1591
|
+
return null;
|
|
1592
|
+
// The intrinsic owns the evaluated receiver through index evaluation,
|
|
1593
|
+
// then borrows it for one slot lookup. No extra helper parameter or
|
|
1594
|
+
// separate state/getter expressions need to retain the array again.
|
|
1595
|
+
return { kind: "arrIntrinsic", method: "getNumber", receiver: arr, args: [index], type: F64, loc };
|
|
1596
|
+
}
|
|
1331
1597
|
/** Backward-compatible name for the npm-static probe path. */
|
|
1332
1598
|
export const lowerNpmStaticSafeIndexRead = lowerSafeIndexRead;
|
|
1333
1599
|
/** Interned synthetic function for one (elem, argument-shape) concat —
|
|
@@ -2182,22 +2448,26 @@ function reverseCountedForLoop(loc, body) {
|
|
|
2182
2448
|
* through). */
|
|
2183
2449
|
function lowerArrayAtCall(lowerer, call, access, elem) {
|
|
2184
2450
|
const loc = locOf(call);
|
|
2451
|
+
if (call.arguments.some(ts.isSpreadElement))
|
|
2452
|
+
lowerer.noLowering(".at with spread arguments", call);
|
|
2185
2453
|
const receiver = lowerer.lowerExpr(access.expression);
|
|
2186
|
-
const
|
|
2454
|
+
const indexNode = call.arguments[0];
|
|
2455
|
+
const index = lowerPositionArgument(lowerer, indexNode, numLit(0, loc));
|
|
2187
2456
|
const resultT = lowerer.irTypeOf(call);
|
|
2188
2457
|
if (resultT.kind !== "union")
|
|
2189
2458
|
lowerer.badType(call, lowerer.typeOf(call)); // defensive: T | undefined always maps to a union
|
|
2190
2459
|
const undefTag = lowerer.armTag(resultT.unionId, UNDEFINED_T);
|
|
2191
2460
|
if (undefTag < 0)
|
|
2192
2461
|
lowerer.badType(call, lowerer.typeOf(call));
|
|
2193
|
-
const key = `at:${typeKey(elem)}:${typeKey(resultT)}`;
|
|
2462
|
+
const key = `at:${typeKey(elem)}:${typeKey(resultT)}:${typeKey(index.type)}`;
|
|
2194
2463
|
let name = lowerer.arrHofHelpers.get(key);
|
|
2195
2464
|
if (!name) {
|
|
2196
2465
|
name = `%arr.at.${lowerer.arrHofHelpers.size}`;
|
|
2197
2466
|
lowerer.arrHofHelpers.set(key, name);
|
|
2198
2467
|
const arrT = arrayOf(elem);
|
|
2199
2468
|
const t = varRef("t.0", F64, loc);
|
|
2200
|
-
const i = varRef("i.0",
|
|
2469
|
+
const i = varRef("i.0", index.type, loc);
|
|
2470
|
+
const number = varRef("number.0", F64, loc);
|
|
2201
2471
|
const n = varRef("n.0", F64, loc);
|
|
2202
2472
|
const found = lowerer.coerceToExpected(arrayValueRead(lowerer, varRef("a.0", arrT, loc), t, elem, loc), resultT);
|
|
2203
2473
|
const miss = {
|
|
@@ -2212,20 +2482,21 @@ function lowerArrayAtCall(lowerer, call, access, elem) {
|
|
|
2212
2482
|
const lt = (l, r) => ({ kind: "bin", op: "<", left: l, right: r, type: BOOL, loc });
|
|
2213
2483
|
const body = [
|
|
2214
2484
|
readLenStmt(arrT, loc),
|
|
2485
|
+
{ kind: "varDecl", localId: "number.0", init: positionNumber(lowerer, i, numLit(0, loc), indexNode ?? call, "array at position"), loc },
|
|
2215
2486
|
// ToIntegerOrInfinity: floor is trunc for i >= 0; negatives mirror
|
|
2216
2487
|
// (trunc(i) = -floor(-i)); NaN becomes 0. ±Infinity floors to
|
|
2217
2488
|
// itself and falls out of range below, exactly the spec.
|
|
2218
|
-
{ kind: "varDecl", localId: "t.0", init: floorOf(
|
|
2489
|
+
{ kind: "varDecl", localId: "t.0", init: floorOf(number), loc },
|
|
2219
2490
|
{
|
|
2220
2491
|
kind: "if",
|
|
2221
|
-
cond: lt(
|
|
2222
|
-
then: [{ kind: "assign", localId: "t.0", value: { kind: "bin", op: "-", left: numLit(0, loc), right: floorOf({ kind: "bin", op: "-", left: numLit(0, loc), right:
|
|
2492
|
+
cond: lt(number, numLit(0, loc)),
|
|
2493
|
+
then: [{ kind: "assign", localId: "t.0", value: { kind: "bin", op: "-", left: numLit(0, loc), right: floorOf({ kind: "bin", op: "-", left: numLit(0, loc), right: number, type: F64, loc }), type: F64, loc }, loc }],
|
|
2223
2494
|
else_: null,
|
|
2224
2495
|
loc,
|
|
2225
2496
|
},
|
|
2226
2497
|
{
|
|
2227
2498
|
kind: "if",
|
|
2228
|
-
cond: { kind: "libCall", fn: "num.isNaN", args: [
|
|
2499
|
+
cond: { kind: "libCall", fn: "num.isNaN", args: [number], type: BOOL, loc },
|
|
2229
2500
|
then: [{ kind: "assign", localId: "t.0", value: numLit(0, loc), loc }],
|
|
2230
2501
|
else_: null,
|
|
2231
2502
|
loc,
|
|
@@ -2257,13 +2528,14 @@ function lowerArrayAtCall(lowerer, call, access, elem) {
|
|
|
2257
2528
|
name,
|
|
2258
2529
|
params: [
|
|
2259
2530
|
{ localId: "a.0", name: "a", type: arrT },
|
|
2260
|
-
{ localId: "i.0", name: "i", type:
|
|
2531
|
+
{ localId: "i.0", name: "i", type: index.type },
|
|
2261
2532
|
],
|
|
2262
2533
|
returnType: resultT,
|
|
2263
2534
|
locals: [
|
|
2264
2535
|
{ id: "a.0", name: "a", type: arrT, mutable: true },
|
|
2265
|
-
{ id: "i.0", name: "i", type:
|
|
2536
|
+
{ id: "i.0", name: "i", type: index.type, mutable: false },
|
|
2266
2537
|
{ id: "n.0", name: "n", type: F64, mutable: false },
|
|
2538
|
+
{ id: "number.0", name: "number", type: F64, mutable: false },
|
|
2267
2539
|
{ id: "t.0", name: "t", type: F64, mutable: true },
|
|
2268
2540
|
],
|
|
2269
2541
|
body,
|
|
@@ -2272,6 +2544,84 @@ function lowerArrayAtCall(lowerer, call, access, elem) {
|
|
|
2272
2544
|
}
|
|
2273
2545
|
return { kind: "call", callee: name, args: [receiver, index], type: resultT, loc };
|
|
2274
2546
|
}
|
|
2547
|
+
/** The Array constructor's element and count forms, shared by calls and new. */
|
|
2548
|
+
export function lowerArrayConstructor(lowerer, expr, args) {
|
|
2549
|
+
const loc = locOf(expr);
|
|
2550
|
+
if (args.some(ts.isSpreadElement)) {
|
|
2551
|
+
lowerer.noLowering("Array constructor with spread arguments", expr);
|
|
2552
|
+
}
|
|
2553
|
+
let result = lowerer.mapTypeOf(lowerer.typeOf(expr));
|
|
2554
|
+
if (result?.kind !== "array") {
|
|
2555
|
+
const contextual = lowerer.checker.getContextualType(expr);
|
|
2556
|
+
if (contextual)
|
|
2557
|
+
result = lowerer.mapTypeOf(contextual);
|
|
2558
|
+
}
|
|
2559
|
+
if (result?.kind !== "array" || !isSupportedArrayElem(result.elem)) {
|
|
2560
|
+
lowerer.badType(expr, lowerer.typeOf(expr));
|
|
2561
|
+
}
|
|
2562
|
+
const argType = args.length === 1 ? lowerer.mapTypeOf(lowerer.typeOf(args[0])) : null;
|
|
2563
|
+
if (argType?.kind === "dyn" || argType?.kind === "jsval") {
|
|
2564
|
+
lowerer.noLowering("Array constructor with a dynamically typed sole argument", args[0]);
|
|
2565
|
+
}
|
|
2566
|
+
if (args.length === 1 && (argType?.kind === "f64" ||
|
|
2567
|
+
(argType?.kind === "union" && lowerer.armTag(argType.unionId, F64) >= 0))) {
|
|
2568
|
+
const count = lowerer.lowerExpr(args[0]);
|
|
2569
|
+
const numberTag = count.type.kind === "union" ? lowerer.armTag(count.type.unionId, F64) : -1;
|
|
2570
|
+
if (count.type.kind !== "f64" && numberTag < 0) {
|
|
2571
|
+
lowerer.noLowering("Array constructor length with a value that may be missing", args[0]);
|
|
2572
|
+
}
|
|
2573
|
+
const input = count.type.kind === "union" ? lowerer.declareHiddenLocal("%arrayCtorArg", count.type) : null;
|
|
2574
|
+
const inputRef = input ? varRef(input.id, count.type, loc) : null;
|
|
2575
|
+
const local = lowerer.declareHiddenLocal("%arrayLengthCtor", result);
|
|
2576
|
+
const ref = varRef(local.id, result, loc);
|
|
2577
|
+
const length = inputRef && count.type.kind === "union"
|
|
2578
|
+
? { kind: "unionNarrow", unionId: count.type.unionId, tag: numberTag, value: inputRef, type: F64, loc }
|
|
2579
|
+
: count;
|
|
2580
|
+
const resize = { kind: "arraySetLength", arr: ref, length, loc };
|
|
2581
|
+
return {
|
|
2582
|
+
kind: "seqExpr",
|
|
2583
|
+
stmts: [
|
|
2584
|
+
...(input ? [{ kind: "varDecl", localId: input.id, init: count, loc }] : []),
|
|
2585
|
+
{ kind: "varDecl", localId: local.id, init: { kind: "arrayLit", elems: [], type: result, loc }, loc },
|
|
2586
|
+
...(inputRef && count.type.kind === "union"
|
|
2587
|
+
? [{ kind: "if",
|
|
2588
|
+
cond: { kind: "unionIsTag", unionId: count.type.unionId, tag: numberTag, value: inputRef, negated: false, type: BOOL, loc },
|
|
2589
|
+
then: [resize],
|
|
2590
|
+
else_: [arrayValueStore(lowerer, ref, numLit(0, loc), lowerer.coerceInto(args[0], inputRef, result.elem), result.elem, loc)],
|
|
2591
|
+
loc }]
|
|
2592
|
+
: [resize]),
|
|
2593
|
+
],
|
|
2594
|
+
result: ref,
|
|
2595
|
+
type: result,
|
|
2596
|
+
loc,
|
|
2597
|
+
};
|
|
2598
|
+
}
|
|
2599
|
+
return { kind: "arrayLit", elems: args.map((arg) => lowerer.lowerExprExpecting(arg, result.elem)), type: result, loc };
|
|
2600
|
+
}
|
|
2601
|
+
/** `Array.of` creates an array of its arguments, including a lone number. */
|
|
2602
|
+
export function lowerArrayOfCall(lowerer, call, access) {
|
|
2603
|
+
if (call.questionDotToken || access.questionDotToken || access.name.text !== "of" ||
|
|
2604
|
+
!lowerer.isStdlibGlobal(access.expression, "Array"))
|
|
2605
|
+
return null;
|
|
2606
|
+
let result = lowerer.mapTypeOf(lowerer.typeOf(call));
|
|
2607
|
+
if (result?.kind !== "array") {
|
|
2608
|
+
const contextual = lowerer.checker.getContextualType(call);
|
|
2609
|
+
if (contextual)
|
|
2610
|
+
result = lowerer.mapTypeOf(contextual);
|
|
2611
|
+
}
|
|
2612
|
+
if (result?.kind !== "array" || !isSupportedArrayElem(result.elem)) {
|
|
2613
|
+
lowerer.badType(call, lowerer.typeOf(call));
|
|
2614
|
+
}
|
|
2615
|
+
if (call.arguments.some(ts.isSpreadElement)) {
|
|
2616
|
+
return lowerArraySpreadItems(lowerer, call.arguments, result.elem, result, locOf(call));
|
|
2617
|
+
}
|
|
2618
|
+
return {
|
|
2619
|
+
kind: "arrayLit",
|
|
2620
|
+
elems: call.arguments.map((arg) => lowerer.lowerExprExpecting(arg, result.elem)),
|
|
2621
|
+
type: result,
|
|
2622
|
+
loc: locOf(call),
|
|
2623
|
+
};
|
|
2624
|
+
}
|
|
2275
2625
|
/** `Array.from({ length: n }, mapfn)` — the counted-generation idiom — on
|
|
2276
2626
|
* THE stdlib Array global. The source must be an OBJECT LITERAL whose
|
|
2277
2627
|
* single property is `length` (the shape the idiom always spells; the
|
|
@@ -2281,8 +2631,10 @@ function lowerArrayAtCall(lowerer, call, access, elem) {
|
|
|
2281
2631
|
* checker's own `unknown` for it — and pushing each result. The loop
|
|
2282
2632
|
* bound is `i <= n - 1`, which IS ToLength for the finite lengths that
|
|
2283
2633
|
* terminate (fractional lengths truncate, negative/NaN produce an empty
|
|
2284
|
-
* array — Node-exact).
|
|
2285
|
-
*
|
|
2634
|
+
* array — Node-exact). Array inputs copy through the iterator's indexed
|
|
2635
|
+
* values, including holes as present undefined, and may map each value.
|
|
2636
|
+
* Set inputs copy their values; strings iterate by code point and may map.
|
|
2637
|
+
* Other iterable shapes keep the fence. Null when the callee isn't an
|
|
2286
2638
|
* Array-static access. */
|
|
2287
2639
|
export function lowerArrayFromCall(lowerer, call, access) {
|
|
2288
2640
|
if (call.questionDotToken || access.questionDotToken)
|
|
@@ -2317,23 +2669,70 @@ export function lowerArrayFromCall(lowerer, call, access) {
|
|
|
2317
2669
|
return { kind: "arrayNewLen", length: n, type: arrT, loc };
|
|
2318
2670
|
}
|
|
2319
2671
|
}
|
|
2320
|
-
//
|
|
2321
|
-
//
|
|
2322
|
-
//
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
const src =
|
|
2326
|
-
if (src.type.kind === "
|
|
2672
|
+
// Array iteration reads length again after every mapper call. Without
|
|
2673
|
+
// a mapper, a dense reversed copy followed by reversal preserves order
|
|
2674
|
+
// and materializes holes while keeping element reference identity.
|
|
2675
|
+
if ((args.length === 1 || args.length === 2) && !ts.isObjectLiteralExpression(args[0])) {
|
|
2676
|
+
const source = lowerer.lowerExpr(args[0]);
|
|
2677
|
+
const src = source.type.kind === "string" && args.length === 2 ? strCharsCall(lowerer, source, loc) : source;
|
|
2678
|
+
if (src.type.kind === "array") {
|
|
2679
|
+
const arrT = src.type;
|
|
2680
|
+
if (args.length === 1) {
|
|
2681
|
+
const dense = { kind: "arrIntrinsic", method: "toReversed", receiver: src, args: [], type: arrT, loc };
|
|
2682
|
+
return { kind: "arrIntrinsic", method: "reverse", receiver: dense, args: [], type: arrT, loc };
|
|
2683
|
+
}
|
|
2684
|
+
const mapper = args[1];
|
|
2685
|
+
const valueT = arrayValueType(lowerer, arrT.elem);
|
|
2686
|
+
const firstParam = ts.isArrowFunction(mapper) || ts.isFunctionExpression(mapper)
|
|
2687
|
+
? mapper.parameters[0]?.name : undefined;
|
|
2688
|
+
const paramSymbol = firstParam && ts.isIdentifier(firstParam)
|
|
2689
|
+
? lowerer.checker.getSymbolAtLocation(firstParam) : undefined;
|
|
2690
|
+
const previous = paramSymbol ? lowerer.runtimeOptionalBindingTypes.get(paramSymbol) : undefined;
|
|
2691
|
+
if (paramSymbol)
|
|
2692
|
+
lowerer.runtimeOptionalBindingTypes.set(paramSymbol, valueT);
|
|
2693
|
+
let callback;
|
|
2694
|
+
try {
|
|
2695
|
+
callback = hofCallbackArg(lowerer, mapper, [valueT], arrT);
|
|
2696
|
+
}
|
|
2697
|
+
finally {
|
|
2698
|
+
if (paramSymbol) {
|
|
2699
|
+
if (previous === undefined)
|
|
2700
|
+
lowerer.runtimeOptionalBindingTypes.delete(paramSymbol);
|
|
2701
|
+
else
|
|
2702
|
+
lowerer.runtimeOptionalBindingTypes.set(paramSymbol, previous);
|
|
2703
|
+
}
|
|
2704
|
+
}
|
|
2705
|
+
const { fnArg, arity } = callback;
|
|
2706
|
+
if (arity > 2)
|
|
2707
|
+
lowerer.noLowering("Array.from mapper with an array parameter", args[1]);
|
|
2708
|
+
const fnRet = fnArg.type.ret;
|
|
2709
|
+
if (fnRet.kind === "void" || fnRet.kind === "func")
|
|
2710
|
+
lowerer.badType(call, lowerer.typeOf(call));
|
|
2711
|
+
fenceProducedArrayElem(lowerer, call, "'Array.from(array, mapper)'", fnRet);
|
|
2712
|
+
const outElem = callbackArrayElem(lowerer, call, fnRet);
|
|
2713
|
+
const key = `fromArray:${typeKey(arrT.elem)}:${typeKey(outElem)}:${typeKey(fnRet)}:${arity}`;
|
|
2714
|
+
let helper = lowerer.arrHofHelpers.get(key);
|
|
2715
|
+
if (!helper) {
|
|
2716
|
+
helper = `%arr.fromArray.${lowerer.arrHofHelpers.size}`;
|
|
2717
|
+
lowerer.arrHofHelpers.set(key, helper);
|
|
2718
|
+
lowerer.liftedFns.push(buildArrayFromArrayFn(lowerer, helper, arrT.elem, outElem, fnRet, arity, loc));
|
|
2719
|
+
}
|
|
2720
|
+
return { kind: "call", callee: helper, args: [src, fnArg], type: arrayOf(outElem), loc };
|
|
2721
|
+
}
|
|
2722
|
+
if (src.type.kind === "set" && args.length === 1) {
|
|
2723
|
+
return { kind: "setIntrinsic", method: "toArray", receiver: src, args: [], type: arrayOf(src.type.elem), loc };
|
|
2724
|
+
}
|
|
2725
|
+
// String iteration advances by code point; the same helper serves
|
|
2726
|
+
// `[...s]` and both Array.from(s) forms.
|
|
2727
|
+
if (src.type.kind === "string" && args.length === 1)
|
|
2327
2728
|
return strCharsCall(lowerer, src, loc);
|
|
2328
|
-
lowerer.noLowering("Array.from with this argument shape", call, "Array.from(
|
|
2329
|
-
"forms — copy arrays with [...a] and drain Map/Set iterators where they are made");
|
|
2729
|
+
lowerer.noLowering("Array.from with this argument shape", call, "Array.from(array), Array.from(array, mapper), Array.from(aString), Array.from(aString, mapper), and Array.from({ length: n }, mapper) are the lowered forms");
|
|
2330
2730
|
}
|
|
2331
2731
|
const n = args.length === 2 && ts.isObjectLiteralExpression(args[0]) && args[0].properties.length === 1
|
|
2332
2732
|
? lowerLengthProp(lowerer, args[0].properties[0])
|
|
2333
2733
|
: null;
|
|
2334
2734
|
if (!n) {
|
|
2335
|
-
lowerer.noLowering("Array.from with this argument shape", call, "Array.from(
|
|
2336
|
-
"with [...a] and drain Map/Set iterators where they are made");
|
|
2735
|
+
lowerer.noLowering("Array.from with this argument shape", call, "Array.from(array), Array.from(array, mapper), Array.from(aString), Array.from(aString, mapper), and Array.from({ length: n }, mapper) are the lowered forms");
|
|
2337
2736
|
}
|
|
2338
2737
|
if (n.type.kind !== "f64")
|
|
2339
2738
|
lowerer.badType(args[0], lowerer.typeOf(args[0]));
|
|
@@ -2362,6 +2761,49 @@ export function lowerArrayFromCall(lowerer, call, access) {
|
|
|
2362
2761
|
}
|
|
2363
2762
|
return { kind: "call", callee: helper, args: [n, fnArg], type: arrayOf(fnRet), loc };
|
|
2364
2763
|
}
|
|
2764
|
+
/** Map an array iterator to a fresh dense array. The source length remains
|
|
2765
|
+
* live so mapper mutations affect which later indexes are visited. */
|
|
2766
|
+
function buildArrayFromArrayFn(lowerer, name, elem, outElem, fnRet, arity, loc) {
|
|
2767
|
+
const arrT = arrayOf(elem);
|
|
2768
|
+
const outT = arrayOf(outElem);
|
|
2769
|
+
const fnT = funcOf([arrayValueType(lowerer, elem), F64].slice(0, arity), fnRet);
|
|
2770
|
+
const a = varRef("a.0", arrT, loc);
|
|
2771
|
+
const i = varRef("i.0", F64, loc);
|
|
2772
|
+
const out = varRef("out.0", outT, loc);
|
|
2773
|
+
const mapped = {
|
|
2774
|
+
kind: "callValue",
|
|
2775
|
+
callee: varRef("f.0", fnT, loc),
|
|
2776
|
+
args: [arrayValueRead(lowerer, a, i, elem, loc), i].slice(0, arity),
|
|
2777
|
+
type: fnRet,
|
|
2778
|
+
loc,
|
|
2779
|
+
};
|
|
2780
|
+
return {
|
|
2781
|
+
name,
|
|
2782
|
+
params: [{ localId: "a.0", name: "a", type: arrT }, { localId: "f.0", name: "f", type: fnT }],
|
|
2783
|
+
returnType: outT,
|
|
2784
|
+
locals: [
|
|
2785
|
+
{ id: "a.0", name: "a", type: arrT, mutable: true },
|
|
2786
|
+
{ id: "f.0", name: "f", type: fnT, mutable: true },
|
|
2787
|
+
{ id: "out.0", name: "out", type: outT, mutable: false },
|
|
2788
|
+
{ id: "i.0", name: "i", type: F64, mutable: true },
|
|
2789
|
+
],
|
|
2790
|
+
body: [
|
|
2791
|
+
{ kind: "varDecl", localId: "out.0", init: { kind: "arrayLit", elems: [], type: outT, loc }, loc },
|
|
2792
|
+
{ kind: "varDecl", localId: "i.0", init: numLit(0, loc), loc },
|
|
2793
|
+
{
|
|
2794
|
+
kind: "while",
|
|
2795
|
+
cond: { kind: "bin", op: "<", left: i, right: { kind: "arrIntrinsic", method: "length", receiver: a, args: [], type: F64, loc }, type: BOOL, loc },
|
|
2796
|
+
body: [
|
|
2797
|
+
arrayValueStore(lowerer, out, i, mapped, outElem, loc),
|
|
2798
|
+
{ kind: "assign", localId: "i.0", value: { kind: "bin", op: "+", left: i, right: numLit(1, loc), type: F64, loc }, loc },
|
|
2799
|
+
],
|
|
2800
|
+
loc,
|
|
2801
|
+
},
|
|
2802
|
+
{ kind: "return", value: out, loc },
|
|
2803
|
+
],
|
|
2804
|
+
loc,
|
|
2805
|
+
};
|
|
2806
|
+
}
|
|
2365
2807
|
/** `Array.from(s)` / `[...s]` on a STRING: the code-point split into a
|
|
2366
2808
|
* fresh string[], through one interned helper per module. */
|
|
2367
2809
|
export function strCharsCall(lowerer, src, loc) {
|