@scriptc/compiler 0.0.36 → 0.1.1
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 +7 -5
- package/ambient/scriptc-overrides.d.ts +0 -7
- package/ambient/scriptc.d.ts +1 -1
- package/dist/backend/c/exprs.js +69 -45
- package/dist/backend/c/exprs.js.map +1 -1
- package/dist/backend/c/may-throw.js +3 -0
- package/dist/backend/c/may-throw.js.map +1 -1
- package/dist/backend/c/stmts.js +18 -0
- package/dist/backend/c/stmts.js.map +1 -1
- package/dist/backend/c/walkers.js +5 -7
- package/dist/backend/c/walkers.js.map +1 -1
- package/dist/backend/external-c.d.ts +33 -0
- package/dist/backend/external-c.js +51 -0
- package/dist/backend/external-c.js.map +1 -0
- package/dist/backend/link-plan.d.ts +37 -0
- package/dist/backend/link-plan.js +26 -0
- package/dist/backend/link-plan.js.map +1 -0
- package/dist/backend/linker.d.ts +22 -0
- package/dist/backend/linker.js +236 -0
- package/dist/backend/linker.js.map +1 -0
- package/dist/backend/llvm/dyn.js +7 -0
- package/dist/backend/llvm/dyn.js.map +1 -1
- package/dist/backend/llvm/emitter.js +34 -7
- package/dist/backend/llvm/emitter.js.map +1 -1
- package/dist/backend/llvm/expr-containers.js +68 -54
- package/dist/backend/llvm/expr-containers.js.map +1 -1
- package/dist/backend/llvm/expr-context.d.ts +1 -1
- package/dist/backend/llvm/expr-dispatch.js +2 -0
- package/dist/backend/llvm/expr-dispatch.js.map +1 -1
- package/dist/backend/llvm/expr-primitives.d.ts +1 -1
- package/dist/backend/llvm/expr-primitives.js +20 -0
- package/dist/backend/llvm/expr-primitives.js.map +1 -1
- package/dist/backend/llvm/expr-stream-bridges.js +25 -1
- package/dist/backend/llvm/expr-stream-bridges.js.map +1 -1
- package/dist/backend/llvm/walkers.js +31 -4
- package/dist/backend/llvm/walkers.js.map +1 -1
- package/dist/backend/native-codegen.d.ts +8 -1
- package/dist/backend/native-codegen.js +39 -25
- package/dist/backend/native-codegen.js.map +1 -1
- package/dist/backend/native-codegen.test.js +42 -4
- package/dist/backend/native-codegen.test.js.map +1 -1
- package/dist/backend/native-link-info.d.ts +1 -1
- package/dist/backend/native-link-info.js +42 -18
- package/dist/backend/native-link-info.js.map +1 -1
- package/dist/backend/native-toolchain.test.js +5 -1
- package/dist/backend/native-toolchain.test.js.map +1 -1
- package/dist/backend/runtime-pack-targets.test.d.ts +1 -0
- package/dist/backend/runtime-pack-targets.test.js +54 -0
- package/dist/backend/runtime-pack-targets.test.js.map +1 -0
- package/dist/backend/runtime-pack.d.ts +5 -30
- package/dist/backend/runtime-pack.js +46 -141
- package/dist/backend/runtime-pack.js.map +1 -1
- package/dist/backend/runtime-pack.test.js +49 -14
- package/dist/backend/runtime-pack.test.js.map +1 -1
- package/dist/backend/targets.d.ts +63 -17
- package/dist/backend/targets.js +251 -35
- package/dist/backend/targets.js.map +1 -1
- package/dist/backend/targets.test.js +27 -5
- package/dist/backend/targets.test.js.map +1 -1
- package/dist/coverage/surface-manifest.js +10 -1
- package/dist/coverage/surface-manifest.js.map +1 -1
- package/dist/frontend/lowering/array-values.d.ts +8 -0
- package/dist/frontend/lowering/array-values.js +60 -0
- package/dist/frontend/lowering/array-values.js.map +1 -0
- package/dist/frontend/lowering/lower-array-sort.d.ts +22 -0
- package/dist/frontend/lowering/lower-array-sort.js +619 -0
- package/dist/frontend/lowering/lower-array-sort.js.map +1 -0
- package/dist/frontend/lowering/lower-builtins.js +164 -4
- package/dist/frontend/lowering/lower-builtins.js.map +1 -1
- package/dist/frontend/lowering/lower-calls.d.ts +3 -4
- package/dist/frontend/lowering/lower-calls.js +260 -27
- package/dist/frontend/lowering/lower-calls.js.map +1 -1
- package/dist/frontend/lowering/lower-containers.js +728 -655
- package/dist/frontend/lowering/lower-containers.js.map +1 -1
- package/dist/frontend/lowering/lower-exprs.d.ts +4 -0
- package/dist/frontend/lowering/lower-exprs.js +769 -101
- package/dist/frontend/lowering/lower-exprs.js.map +1 -1
- package/dist/frontend/lowering/lower-inspect.js +47 -33
- package/dist/frontend/lowering/lower-inspect.js.map +1 -1
- package/dist/frontend/lowering/lower-island.d.ts +5 -4
- package/dist/frontend/lowering/lower-island.js +10 -5
- package/dist/frontend/lowering/lower-island.js.map +1 -1
- package/dist/frontend/lowering/lower-server.js +10 -0
- package/dist/frontend/lowering/lower-server.js.map +1 -1
- package/dist/frontend/lowering/lower-stmts.js +302 -71
- package/dist/frontend/lowering/lower-stmts.js.map +1 -1
- package/dist/frontend/lowering/lowerer.d.ts +74 -2
- package/dist/frontend/lowering/lowerer.js +1087 -10
- package/dist/frontend/lowering/lowerer.js.map +1 -1
- package/dist/frontend/lowering/surfaces.d.ts +10 -2
- package/dist/frontend/lowering/surfaces.js +15 -3
- package/dist/frontend/lowering/surfaces.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +45 -23
- package/dist/index.js.map +1 -1
- package/dist/ir/ir.d.ts +65 -11
- package/dist/ir/ir.js +25 -25
- package/dist/ir/ir.js.map +1 -1
- package/dist/ir/serialize.d.ts +1 -1
- package/dist/ir/serialize.js +1 -1
- package/dist/ir/validate.js +84 -37
- package/dist/ir/validate.js.map +1 -1
- package/dist/library/fence-eval.js +3 -1
- package/dist/library/fence-eval.js.map +1 -1
- package/dist/library/int-infer.js +12 -1
- package/dist/library/int-infer.js.map +1 -1
- package/dist/library/int-infer.test.js +4 -4
- package/dist/library/library-cache.test.js +5 -5
- package/dist/startup-cache.d.ts +9 -1
- package/dist/startup-cache.js +16 -1
- package/dist/startup-cache.js.map +1 -1
- package/package.json +17 -4
- package/surface-manifest.json +5 -5
|
@@ -10,6 +10,8 @@ import { droppableStatic, isRequireMainFilename, lowerDynObjectLiteral, probeLow
|
|
|
10
10
|
import { forOfVarTarget, lowerDestructuringAssign } from "./lower-stmts.js";
|
|
11
11
|
import { isJsSourceFile, locOf } from "../program.js";
|
|
12
12
|
import { islandPrimitiveExit, lowerDynDispatchMethodCall } from "./lower-calls.js";
|
|
13
|
+
import { buildArraySortFn, buildBytesSortFn } from "./lower-array-sort.js";
|
|
14
|
+
import { arrayValueRead, arrayValueStore, arrayValueType } from "./array-values.js";
|
|
13
15
|
import { typeKey } from "../type-mapper.js";
|
|
14
16
|
import { dynUndefinedExpr, own } from "./lowerer.js";
|
|
15
17
|
import { boolLit, countedFor, numLit, varRef } from "../../ir/build.js";
|
|
@@ -89,10 +91,49 @@ function lowerOptionalDefaultArg(lowerer, node, expected, defaultValue) {
|
|
|
89
91
|
}
|
|
90
92
|
return lowerer.coerceInto(node, value, expected);
|
|
91
93
|
}
|
|
94
|
+
/** Build the argument array for a mutating/copying operation while keeping
|
|
95
|
+
* the receiver's scalar payload ABI. A missing read is represented as the
|
|
96
|
+
* present UNDEFINED state in this temporary, so push/unshift and toSpliced
|
|
97
|
+
* materialize it through their ordinary indexed/iterator copies. */
|
|
98
|
+
function lowerArrayValueItems(lowerer, values, elem, arrType, loc) {
|
|
99
|
+
const out = lowerer.declareHiddenLocal("%arrayItems", arrType);
|
|
100
|
+
const outRef = varRef(out.id, arrType, loc);
|
|
101
|
+
const length = () => ({
|
|
102
|
+
kind: "arrIntrinsic",
|
|
103
|
+
method: "length",
|
|
104
|
+
receiver: outRef,
|
|
105
|
+
args: [],
|
|
106
|
+
type: F64,
|
|
107
|
+
loc,
|
|
108
|
+
});
|
|
109
|
+
const body = [
|
|
110
|
+
{ kind: "varDecl", localId: out.id, init: { kind: "arrayLit", elems: [], type: arrType, loc }, loc },
|
|
111
|
+
];
|
|
112
|
+
for (const value of values) {
|
|
113
|
+
const stored = lowerer.runtimeOptionalWidening(value.type, elem) !== null
|
|
114
|
+
? value
|
|
115
|
+
: lowerer.coerceToExpected(value, elem);
|
|
116
|
+
body.push(arrayValueStore(lowerer, outRef, length(), stored, elem, loc));
|
|
117
|
+
}
|
|
118
|
+
return { kind: "seqExpr", stmts: body, result: outRef, type: arrType, loc };
|
|
119
|
+
}
|
|
92
120
|
function lowerSplitLimitArg(lowerer, node, loc) {
|
|
93
121
|
const defaultValue = { kind: "numLit", value: 4294967295, type: F64, loc };
|
|
94
122
|
return node ? lowerOptionalDefaultArg(lowerer, node, F64, defaultValue) : defaultValue;
|
|
95
123
|
}
|
|
124
|
+
/** Extract the checker-proven receiver arm from a runtime-optional array
|
|
125
|
+
* read. The receiver is stabilized once before its tag is tested, and a
|
|
126
|
+
* missing value throws the same property-read TypeError Node throws before
|
|
127
|
+
* evaluating method arguments. */
|
|
128
|
+
function lowerMethodReceiver(lowerer, node, expected, member) {
|
|
129
|
+
const lowered = lowerer.lowerExpr(node);
|
|
130
|
+
const optional = lowerer.runtimeOptionalPropertyReceiver(node, lowered, expected, member);
|
|
131
|
+
if (optional)
|
|
132
|
+
return optional;
|
|
133
|
+
if (typeEquals(lowered.type, expected))
|
|
134
|
+
return lowered;
|
|
135
|
+
return lowerer.coerceInto(node, lowered, expected);
|
|
136
|
+
}
|
|
96
137
|
/** Callback-driven array producers bypass mapType's ordinary T[] mapping:
|
|
97
138
|
* the callback is lowered first, then the helper's result array is built
|
|
98
139
|
* directly from its IR return type. Fence element kinds ScrArr cannot hold
|
|
@@ -227,12 +268,62 @@ export function lowerArrayMethodCall(lowerer, call, access) {
|
|
|
227
268
|
}
|
|
228
269
|
const receiver = lowerer.lowerExpr(access.expression);
|
|
229
270
|
const index = lowerer.lowerExprExpecting(call.arguments[0], F64);
|
|
230
|
-
const value = lowerer.
|
|
271
|
+
const value = lowerer.lowerExpr(call.arguments[1]);
|
|
272
|
+
if (value.type.kind === "union" && lowerer.runtimeOptionalWidening(value.type, elem) !== null) {
|
|
273
|
+
// `.with()` validates its index and returns a fresh copy. Stabilize
|
|
274
|
+
// all three operands, then select the scalar replacement or the
|
|
275
|
+
// present-undefined copy without evaluating a read twice.
|
|
276
|
+
const receiverLocal = lowerer.declareHiddenLocal("%withRecv", receiverIr);
|
|
277
|
+
const indexLocal = lowerer.declareHiddenLocal("%withIndex", F64);
|
|
278
|
+
const valueLocal = lowerer.declareHiddenLocal("%withValue", value.type);
|
|
279
|
+
const unionId = value.type.unionId;
|
|
280
|
+
const receiverRef = varRef(receiverLocal.id, receiverIr, loc);
|
|
281
|
+
const indexRef = varRef(indexLocal.id, F64, loc);
|
|
282
|
+
const valueRef = varRef(valueLocal.id, value.type, loc);
|
|
283
|
+
const undefTag = lowerer.armTag(unionId, UNDEFINED_T);
|
|
284
|
+
const condition = {
|
|
285
|
+
kind: "unionIsTag",
|
|
286
|
+
unionId,
|
|
287
|
+
tag: undefTag,
|
|
288
|
+
negated: false,
|
|
289
|
+
value: valueRef,
|
|
290
|
+
type: BOOL,
|
|
291
|
+
loc,
|
|
292
|
+
};
|
|
293
|
+
const missing = {
|
|
294
|
+
kind: "arrIntrinsic",
|
|
295
|
+
method: "withUndefined",
|
|
296
|
+
receiver: receiverRef,
|
|
297
|
+
args: [indexRef],
|
|
298
|
+
type: receiverIr,
|
|
299
|
+
loc,
|
|
300
|
+
};
|
|
301
|
+
const present = {
|
|
302
|
+
kind: "arrIntrinsic",
|
|
303
|
+
method: "with",
|
|
304
|
+
receiver: receiverRef,
|
|
305
|
+
args: [indexRef, lowerer.coerceToExpected(valueRef, elem)],
|
|
306
|
+
type: receiverIr,
|
|
307
|
+
loc,
|
|
308
|
+
};
|
|
309
|
+
return {
|
|
310
|
+
kind: "seqExpr",
|
|
311
|
+
stmts: [
|
|
312
|
+
{ kind: "varDecl", localId: receiverLocal.id, init: receiver, loc },
|
|
313
|
+
{ kind: "varDecl", localId: indexLocal.id, init: index, loc },
|
|
314
|
+
{ kind: "varDecl", localId: valueLocal.id, init: value, loc },
|
|
315
|
+
],
|
|
316
|
+
result: { kind: "ternary", cond: condition, then: missing, else_: present, type: receiverIr, loc },
|
|
317
|
+
type: receiverIr,
|
|
318
|
+
loc,
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
const coercedValue = lowerer.coerceInto(call.arguments[1], value, elem);
|
|
231
322
|
return {
|
|
232
323
|
kind: "arrIntrinsic",
|
|
233
324
|
method: "with",
|
|
234
325
|
receiver,
|
|
235
|
-
args: [index,
|
|
326
|
+
args: [index, coercedValue],
|
|
236
327
|
type: receiverIr,
|
|
237
328
|
loc,
|
|
238
329
|
};
|
|
@@ -256,12 +347,17 @@ export function lowerArrayMethodCall(lowerer, call, access) {
|
|
|
256
347
|
: call.arguments[1]
|
|
257
348
|
? lowerOptionalDefaultArg(lowerer, call.arguments[1], F64, deleteCountDefault)
|
|
258
349
|
: { kind: "numLit", value: Infinity, type: F64, loc };
|
|
259
|
-
const
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
loc
|
|
264
|
-
|
|
350
|
+
const itemNodes = call.arguments.slice(2);
|
|
351
|
+
const itemProbes = itemNodes.map((arg) => probeLower(lowerer, arg));
|
|
352
|
+
const statefulItems = itemProbes.some((probe) => probe !== null && lowerer.runtimeOptionalWidening(probe.type, elem) !== null);
|
|
353
|
+
const items = statefulItems
|
|
354
|
+
? lowerArrayValueItems(lowerer, itemNodes.map((arg) => lowerer.lowerExpr(arg)), elem, receiverIr, loc)
|
|
355
|
+
: {
|
|
356
|
+
kind: "arrayLit",
|
|
357
|
+
elems: itemNodes.map((arg) => lowerer.coerceInto(arg, lowerer.lowerExpr(arg), elem)),
|
|
358
|
+
type: receiverIr,
|
|
359
|
+
loc,
|
|
360
|
+
};
|
|
265
361
|
return {
|
|
266
362
|
kind: "arrIntrinsic",
|
|
267
363
|
method: "toSpliced",
|
|
@@ -299,8 +395,17 @@ export function lowerArrayMethodCall(lowerer, call, access) {
|
|
|
299
395
|
: undefined;
|
|
300
396
|
lowerer.noLowering(`.${name} with ${call.arguments.length} argument${call.arguments.length === 1 ? "" : "s"}`, call, hint);
|
|
301
397
|
}
|
|
302
|
-
if (name === "push" || name === "unshift"
|
|
303
|
-
|
|
398
|
+
if (name === "push" || name === "unshift") {
|
|
399
|
+
let receiver = lowerer.lowerExpr(access.expression);
|
|
400
|
+
if (receiver.type.kind === "union" && lowerer.armTag(receiver.type.unionId, UNDEFINED_T) >= 0) {
|
|
401
|
+
const present = lowerer.stripUndefinedArm(receiver.type);
|
|
402
|
+
const helper = present.kind === "array"
|
|
403
|
+
? lowerer.narrowedArmHelper(receiver.type.unionId, present, loc)
|
|
404
|
+
: null;
|
|
405
|
+
receiver = helper
|
|
406
|
+
? { kind: "call", callee: helper, args: [receiver], type: present, loc }
|
|
407
|
+
: lowerer.maybeNarrow(receiver, access.expression);
|
|
408
|
+
}
|
|
304
409
|
// `a.push(...src)` / `a.unshift(...src)`: copy src's elements in
|
|
305
410
|
// order (count snapshotted first, so the self-spread forms duplicate
|
|
306
411
|
// like JS) and return the new length.
|
|
@@ -326,6 +431,18 @@ export function lowerArrayMethodCall(lowerer, call, access) {
|
|
|
326
431
|
loc,
|
|
327
432
|
};
|
|
328
433
|
}
|
|
434
|
+
const valueProbes = call.arguments.map((arg) => probeLower(lowerer, arg));
|
|
435
|
+
if (valueProbes.some((probe) => probe !== null && lowerer.runtimeOptionalWidening(probe.type, elem) !== null)) {
|
|
436
|
+
const items = lowerArrayValueItems(lowerer, call.arguments.map((arg) => lowerer.lowerExpr(arg)), elem, receiverIr, loc);
|
|
437
|
+
return {
|
|
438
|
+
kind: "arrIntrinsic",
|
|
439
|
+
method: name === "push" ? "pushSpread" : "unshiftSpread",
|
|
440
|
+
receiver,
|
|
441
|
+
args: [items],
|
|
442
|
+
type: F64,
|
|
443
|
+
loc,
|
|
444
|
+
};
|
|
445
|
+
}
|
|
329
446
|
// Inserted values flow into the element slot like an assignment would:
|
|
330
447
|
// union-element arrays wrap plain arm values (coerceInto is inert
|
|
331
448
|
// when the types already agree).
|
|
@@ -335,39 +452,31 @@ export function lowerArrayMethodCall(lowerer, call, access) {
|
|
|
335
452
|
method: name,
|
|
336
453
|
receiver,
|
|
337
454
|
args,
|
|
338
|
-
type:
|
|
455
|
+
type: F64,
|
|
339
456
|
loc,
|
|
340
457
|
};
|
|
341
458
|
}
|
|
342
|
-
if (name === "
|
|
343
|
-
//
|
|
344
|
-
//
|
|
345
|
-
//
|
|
346
|
-
//
|
|
347
|
-
if (elem.kind === "union") {
|
|
348
|
-
lowerer.unsupported("SC1090", call,
|
|
349
|
-
"loop and compare the narrowed values instead)");
|
|
459
|
+
if (name === "pop") {
|
|
460
|
+
// Like shift(), pop() returns undefined for an empty array. Union
|
|
461
|
+
// element arrays remain fenced here because their existing boxed
|
|
462
|
+
// element already carries the result union and a second undefined
|
|
463
|
+
// wrapper would lose the element identity contract.
|
|
464
|
+
if (elem.kind === "union" && lowerer.armTag(elem.unionId, UNDEFINED_T) < 0) {
|
|
465
|
+
lowerer.unsupported("SC1090", call, "'.pop()' on union-element arrays without an undefined arm (read the last element and splice it after narrowing instead)");
|
|
350
466
|
}
|
|
351
467
|
const receiver = lowerer.lowerExpr(access.expression);
|
|
352
|
-
let needle = lowerer.lowerExpr(call.arguments[0]);
|
|
353
|
-
// A derived CLASS VALUE against a base-classval element widens (the
|
|
354
|
-
// same pointer — identity search is exact); the coercion path owns
|
|
355
|
-
// the ABI gate and its pointed fences.
|
|
356
|
-
if (needle.type.kind === "classval" && elem.kind === "classval" && !typeEquals(needle.type, elem)) {
|
|
357
|
-
needle = lowerer.coerceInto(call.arguments[0], needle, elem);
|
|
358
|
-
}
|
|
359
|
-
if (!typeEquals(needle.type, elem)) {
|
|
360
|
-
lowerer.badType(call.arguments[0], lowerer.typeOf(call.arguments[0]));
|
|
361
|
-
}
|
|
362
468
|
return {
|
|
363
469
|
kind: "arrIntrinsic",
|
|
364
|
-
method:
|
|
470
|
+
method: "pop",
|
|
365
471
|
receiver,
|
|
366
|
-
args: [
|
|
367
|
-
type:
|
|
472
|
+
args: [],
|
|
473
|
+
type: arrayValueType(lowerer, elem),
|
|
368
474
|
loc,
|
|
369
475
|
};
|
|
370
476
|
}
|
|
477
|
+
if (name === "indexOf" || name === "includes") {
|
|
478
|
+
return lowerArraySearchCall(lowerer, call, access, name, elem);
|
|
479
|
+
}
|
|
371
480
|
if (name === "concat") {
|
|
372
481
|
// `a.concat(x, ys, ...)` — a fresh array of a's elements followed by
|
|
373
482
|
// each argument in order: JS spreads array arguments one level
|
|
@@ -455,11 +564,11 @@ export function lowerArrayMethodCall(lowerer, call, access) {
|
|
|
455
564
|
// arrays are fenced: their shift result would collapse arms
|
|
456
565
|
// (`(string | undefined)[]`'s shift is `string | undefined` too, and
|
|
457
566
|
// the box can't say which world the undefined came from).
|
|
458
|
-
if (elem.kind === "union") {
|
|
459
|
-
lowerer.unsupported("SC1090", call, "'.shift()' on union-element arrays (read [0] and splice(0, 1) with the narrowed value instead)");
|
|
567
|
+
if (elem.kind === "union" && lowerer.armTag(elem.unionId, UNDEFINED_T) < 0) {
|
|
568
|
+
lowerer.unsupported("SC1090", call, "'.shift()' on union-element arrays without an undefined arm (read [0] and splice(0, 1) with the narrowed value instead)");
|
|
460
569
|
}
|
|
461
570
|
const receiver = lowerer.lowerExpr(access.expression);
|
|
462
|
-
return { kind: "arrIntrinsic", method: "shift", receiver, args: [], type: lowerer
|
|
571
|
+
return { kind: "arrIntrinsic", method: "shift", receiver, args: [], type: arrayValueType(lowerer, elem), loc };
|
|
463
572
|
}
|
|
464
573
|
if (name === "join") {
|
|
465
574
|
// The ambient declares join on every Array<T> (a per-element-type
|
|
@@ -496,6 +605,217 @@ export function lowerArrayMethodCall(lowerer, call, access) {
|
|
|
496
605
|
// reduce / reduceRight
|
|
497
606
|
return lowerArrayReduceCall(lowerer, call, access, name, elem);
|
|
498
607
|
}
|
|
608
|
+
/** `indexOf` and `includes` normally use the runtime's typed search helpers,
|
|
609
|
+
* but an indexed array read is a `T | undefined` value. Keep that union
|
|
610
|
+
* through a small lowered loop so the needle's missing state is observable:
|
|
611
|
+
* indexOf skips holes, while includes treats holes as undefined. The same
|
|
612
|
+
* loop also gives union-element arrays their value equality rather than
|
|
613
|
+
* comparing the compiler's union boxes by pointer. */
|
|
614
|
+
function lowerArraySearchCall(lowerer, call, access, method, elem) {
|
|
615
|
+
const loc = locOf(call);
|
|
616
|
+
const receiver = lowerer.lowerExpr(access.expression);
|
|
617
|
+
const argNode = call.arguments[0];
|
|
618
|
+
let needle = lowerer.lowerExpr(argNode);
|
|
619
|
+
// A derived CLASS VALUE against a base-classval element widens (the same
|
|
620
|
+
// pointer — identity search is exact); the coercion path owns the ABI gate.
|
|
621
|
+
if (needle.type.kind === "classval" && elem.kind === "classval" && !typeEquals(needle.type, elem)) {
|
|
622
|
+
needle = lowerer.coerceInto(argNode, needle, elem);
|
|
623
|
+
}
|
|
624
|
+
// Preserve the existing fast path for an ordinary, non-union needle. Its
|
|
625
|
+
// runtime implementation already has exact primitive/reference equality.
|
|
626
|
+
if (elem.kind !== "union" && typeEquals(needle.type, elem)) {
|
|
627
|
+
return {
|
|
628
|
+
kind: "arrIntrinsic",
|
|
629
|
+
method,
|
|
630
|
+
receiver,
|
|
631
|
+
args: [needle],
|
|
632
|
+
type: method === "indexOf" ? F64 : BOOL,
|
|
633
|
+
loc,
|
|
634
|
+
};
|
|
635
|
+
}
|
|
636
|
+
const valueT = arrayValueType(lowerer, elem);
|
|
637
|
+
// jsval/dyn arrays have a single engine value representation rather than a
|
|
638
|
+
// static undefined arm. Keep their existing runtime path and its explicit
|
|
639
|
+
// type fence; typed static arrays take the state-aware helper below.
|
|
640
|
+
if (valueT.kind !== "union") {
|
|
641
|
+
if (!typeEquals(needle.type, elem))
|
|
642
|
+
lowerer.badType(argNode, lowerer.typeOf(argNode));
|
|
643
|
+
return {
|
|
644
|
+
kind: "arrIntrinsic",
|
|
645
|
+
method,
|
|
646
|
+
receiver,
|
|
647
|
+
args: [needle],
|
|
648
|
+
type: method === "indexOf" ? F64 : BOOL,
|
|
649
|
+
loc,
|
|
650
|
+
};
|
|
651
|
+
}
|
|
652
|
+
const searchNeedle = lowerer.coerceInto(argNode, needle, valueT);
|
|
653
|
+
if (!typeEquals(searchNeedle.type, valueT))
|
|
654
|
+
lowerer.badType(argNode, lowerer.typeOf(argNode));
|
|
655
|
+
const helper = arraySearchHelper(lowerer, method, elem, valueT, loc);
|
|
656
|
+
return { kind: "call", callee: helper, args: [receiver, searchNeedle], type: method === "indexOf" ? F64 : BOOL, loc };
|
|
657
|
+
}
|
|
658
|
+
/** SameValueZero over a static value union. `unionEq` supplies strict
|
|
659
|
+
* equality for every arm; the extra f64-arm test is the only difference for
|
|
660
|
+
* includes, where NaN equals NaN while +0 and -0 remain equal. */
|
|
661
|
+
function arraySearchEquality(lowerer, left, right, sameValueZero, loc) {
|
|
662
|
+
if (left.type.kind !== "union" || right.type.kind !== "union" || left.type.unionId !== right.type.unionId) {
|
|
663
|
+
return { kind: "bin", op: "===", left, right, type: BOOL, loc };
|
|
664
|
+
}
|
|
665
|
+
const unionId = left.type.unionId;
|
|
666
|
+
const strict = { kind: "unionEq", unionId, negated: false, sameValue: false, left, right, type: BOOL, loc };
|
|
667
|
+
if (!sameValueZero)
|
|
668
|
+
return strict;
|
|
669
|
+
const numberTag = lowerer.unions.get(unionId)?.arms.findIndex((arm) => arm.kind === "f64") ?? -1;
|
|
670
|
+
if (numberTag < 0)
|
|
671
|
+
return strict;
|
|
672
|
+
const leftIsNumber = { kind: "unionIsTag", unionId, tag: numberTag, negated: false, value: left, type: BOOL, loc };
|
|
673
|
+
const rightIsNumber = { kind: "unionIsTag", unionId, tag: numberTag, negated: false, value: right, type: BOOL, loc };
|
|
674
|
+
const leftNumber = { kind: "unionNarrow", unionId, tag: numberTag, value: left, type: F64, loc };
|
|
675
|
+
const rightNumber = { kind: "unionNarrow", unionId, tag: numberTag, value: right, type: F64, loc };
|
|
676
|
+
const bothNaN = {
|
|
677
|
+
kind: "logical",
|
|
678
|
+
op: "&&",
|
|
679
|
+
left: {
|
|
680
|
+
kind: "logical",
|
|
681
|
+
op: "&&",
|
|
682
|
+
left: leftIsNumber,
|
|
683
|
+
right: rightIsNumber,
|
|
684
|
+
type: BOOL,
|
|
685
|
+
loc,
|
|
686
|
+
},
|
|
687
|
+
right: {
|
|
688
|
+
kind: "logical",
|
|
689
|
+
op: "&&",
|
|
690
|
+
left: { kind: "libCall", fn: "num.isNaN", args: [leftNumber], type: BOOL, loc },
|
|
691
|
+
right: { kind: "libCall", fn: "num.isNaN", args: [rightNumber], type: BOOL, loc },
|
|
692
|
+
type: BOOL,
|
|
693
|
+
loc,
|
|
694
|
+
},
|
|
695
|
+
type: BOOL,
|
|
696
|
+
loc,
|
|
697
|
+
};
|
|
698
|
+
return { kind: "logical", op: "||", left: strict, right: bothNaN, type: BOOL, loc };
|
|
699
|
+
}
|
|
700
|
+
function arraySearchHelper(lowerer, method, elem, valueT, loc) {
|
|
701
|
+
const key = `${method}:${typeKey(elem)}:${typeKey(valueT)}`;
|
|
702
|
+
const existing = lowerer.arrHofHelpers.get(key);
|
|
703
|
+
if (existing)
|
|
704
|
+
return existing;
|
|
705
|
+
const name = `%arr.${method}.${lowerer.arrHofHelpers.size}`;
|
|
706
|
+
lowerer.arrHofHelpers.set(key, name);
|
|
707
|
+
const arrT = arrayOf(elem);
|
|
708
|
+
const resultT = method === "indexOf" ? F64 : BOOL;
|
|
709
|
+
const arrRef = varRef("a.0", arrT, loc);
|
|
710
|
+
const indexRef = varRef("i.0", F64, loc);
|
|
711
|
+
const needleRef = varRef("needle.0", valueT, loc);
|
|
712
|
+
const stateRef = varRef("state.0", F64, loc);
|
|
713
|
+
const nextRef = varRef("next.0", F64, loc);
|
|
714
|
+
const rawRead = { kind: "arrayGet", arr: arrRef, index: indexRef, type: elem, loc };
|
|
715
|
+
const missing = lowerer.wrappedUndefined(valueT, loc);
|
|
716
|
+
if (!missing)
|
|
717
|
+
throw new InternalCompilerError("array search helper needs an undefined arm");
|
|
718
|
+
const value = {
|
|
719
|
+
kind: "ternary",
|
|
720
|
+
cond: { kind: "bin", op: "===", left: stateRef, right: numLit(1, loc), type: BOOL, loc },
|
|
721
|
+
then: typeEquals(elem, valueT) ? rawRead : lowerer.coerceToExpected(rawRead, valueT),
|
|
722
|
+
else_: missing,
|
|
723
|
+
type: valueT,
|
|
724
|
+
loc,
|
|
725
|
+
};
|
|
726
|
+
const equal = arraySearchEquality(lowerer, varRef("value.0", valueT, loc), needleRef, method === "includes", loc);
|
|
727
|
+
const match = method === "indexOf"
|
|
728
|
+
? {
|
|
729
|
+
kind: "logical",
|
|
730
|
+
op: "&&",
|
|
731
|
+
left: { kind: "bin", op: "!==", left: stateRef, right: numLit(0, loc), type: BOOL, loc },
|
|
732
|
+
right: equal,
|
|
733
|
+
type: BOOL,
|
|
734
|
+
loc,
|
|
735
|
+
}
|
|
736
|
+
: equal;
|
|
737
|
+
const undefinedTag = lowerer.armTag(valueT.unionId, UNDEFINED_T);
|
|
738
|
+
if (undefinedTag < 0)
|
|
739
|
+
throw new InternalCompilerError("array search helper needs an undefined tag");
|
|
740
|
+
const needleIsUndefined = {
|
|
741
|
+
kind: "unionIsTag",
|
|
742
|
+
unionId: valueT.unionId,
|
|
743
|
+
tag: undefinedTag,
|
|
744
|
+
negated: false,
|
|
745
|
+
value: needleRef,
|
|
746
|
+
type: BOOL,
|
|
747
|
+
loc,
|
|
748
|
+
};
|
|
749
|
+
const advance = {
|
|
750
|
+
kind: "assign",
|
|
751
|
+
localId: "i.0",
|
|
752
|
+
value: { kind: "bin", op: "+", left: indexRef, right: numLit(1, loc), type: F64, loc },
|
|
753
|
+
loc,
|
|
754
|
+
};
|
|
755
|
+
const visitPresent = [
|
|
756
|
+
{ kind: "varDecl", localId: "value.0", init: value, loc },
|
|
757
|
+
{
|
|
758
|
+
kind: "if",
|
|
759
|
+
cond: match,
|
|
760
|
+
then: [{ kind: "return", value: method === "indexOf" ? indexRef : boolLit(true, loc), loc }],
|
|
761
|
+
else_: null,
|
|
762
|
+
loc,
|
|
763
|
+
},
|
|
764
|
+
advance,
|
|
765
|
+
];
|
|
766
|
+
const skipHole = [
|
|
767
|
+
{
|
|
768
|
+
kind: "varDecl",
|
|
769
|
+
localId: "next.0",
|
|
770
|
+
init: { kind: "arrIntrinsic", method: "nextPresent", receiver: arrRef, args: [indexRef], type: F64, loc },
|
|
771
|
+
loc,
|
|
772
|
+
},
|
|
773
|
+
...(method === "includes"
|
|
774
|
+
? [{ kind: "if", cond: needleIsUndefined, then: [{ kind: "return", value: boolLit(true, loc), loc }], else_: null, loc }]
|
|
775
|
+
: []),
|
|
776
|
+
{ kind: "assign", localId: "i.0", value: nextRef, loc },
|
|
777
|
+
];
|
|
778
|
+
const loopBody = [
|
|
779
|
+
{ kind: "varDecl", localId: "state.0", init: { kind: "arrayState", arr: arrRef, index: indexRef, type: F64, loc }, loc },
|
|
780
|
+
{
|
|
781
|
+
kind: "if",
|
|
782
|
+
cond: { kind: "bin", op: "===", left: stateRef, right: numLit(0, loc), type: BOOL, loc },
|
|
783
|
+
then: skipHole,
|
|
784
|
+
else_: visitPresent,
|
|
785
|
+
loc,
|
|
786
|
+
},
|
|
787
|
+
];
|
|
788
|
+
lowerer.liftedFns.push({
|
|
789
|
+
name,
|
|
790
|
+
params: [
|
|
791
|
+
{ localId: "a.0", name: "a", type: arrT },
|
|
792
|
+
{ localId: "needle.0", name: "needle", type: valueT },
|
|
793
|
+
],
|
|
794
|
+
returnType: resultT,
|
|
795
|
+
locals: [
|
|
796
|
+
{ id: "a.0", name: "a", type: arrT, mutable: true },
|
|
797
|
+
{ id: "needle.0", name: "needle", type: valueT, mutable: true },
|
|
798
|
+
{ id: "n.0", name: "n", type: F64, mutable: false },
|
|
799
|
+
{ id: "i.0", name: "i", type: F64, mutable: true },
|
|
800
|
+
{ id: "state.0", name: "state", type: F64, mutable: false },
|
|
801
|
+
{ id: "next.0", name: "next", type: F64, mutable: false },
|
|
802
|
+
{ id: "value.0", name: "value", type: valueT, mutable: false },
|
|
803
|
+
],
|
|
804
|
+
body: [
|
|
805
|
+
readLenStmt(arrT, loc),
|
|
806
|
+
{ kind: "varDecl", localId: "i.0", init: numLit(0, loc), loc },
|
|
807
|
+
{
|
|
808
|
+
kind: "while",
|
|
809
|
+
cond: { kind: "bin", op: "<", left: indexRef, right: varRef("n.0", F64, loc), type: BOOL, loc },
|
|
810
|
+
body: loopBody,
|
|
811
|
+
loc,
|
|
812
|
+
},
|
|
813
|
+
{ kind: "return", value: method === "indexOf" ? numLit(-1, loc) : boolLit(false, loc), loc },
|
|
814
|
+
],
|
|
815
|
+
loc,
|
|
816
|
+
});
|
|
817
|
+
return name;
|
|
818
|
+
}
|
|
499
819
|
/** Lowers and validates a HOF callback argument. The lib declares optional
|
|
500
820
|
* trailing (index, array) parameters after `lead` (the element for the
|
|
501
821
|
* map family, accumulator + element for reduce); a callback may declare
|
|
@@ -572,7 +892,22 @@ function lowerArrayHofCall(lowerer, call, access, method, elem) {
|
|
|
572
892
|
const argNode = call.arguments[0];
|
|
573
893
|
if (!argNode)
|
|
574
894
|
lowerer.unsupported("SC1090", call, "this call form"); // tsc-guarded
|
|
575
|
-
const
|
|
895
|
+
const booleanFilter = method === "filter" &&
|
|
896
|
+
ts.isIdentifier(argNode) &&
|
|
897
|
+
argNode.text === "Boolean" &&
|
|
898
|
+
lowerer.isStdlibSymbol(lowerer.resolveValueSymbol(argNode) ?? undefined);
|
|
899
|
+
const valueT = arrayValueType(lowerer, elem);
|
|
900
|
+
if (booleanFilter) {
|
|
901
|
+
if (valueT.kind === "union")
|
|
902
|
+
lowerer.requireTruthyUnion(valueT.unionId, argNode);
|
|
903
|
+
if (valueT.kind === "dyn" || valueT.kind === "jsval" || valueT.kind === "void" || isUnitType(valueT)) {
|
|
904
|
+
lowerer.badType(argNode, lowerer.typeOf(argNode));
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
const callback = booleanFilter
|
|
908
|
+
? { fnArg: truthyFilterCallback(lowerer, valueT, loc), arity: 1 }
|
|
909
|
+
: hofCallbackArg(lowerer, argNode, [valueT], arrayOf(elem));
|
|
910
|
+
const { fnArg, arity } = callback;
|
|
576
911
|
const fnRet = fnArg.type.ret;
|
|
577
912
|
if (method === "map" && (fnRet.kind === "void" || fnRet.kind === "func")) {
|
|
578
913
|
// The result array U[] is unrepresentable here (no void elements;
|
|
@@ -595,10 +930,98 @@ function lowerArrayHofCall(lowerer, call, access, method, elem) {
|
|
|
595
930
|
}
|
|
596
931
|
lowerer.badType(argNode, lowerer.typeOf(argNode));
|
|
597
932
|
}
|
|
598
|
-
const
|
|
599
|
-
|
|
933
|
+
const outElem = method === "filter"
|
|
934
|
+
? filterResultElem(lowerer, call, argNode, elem, valueT, booleanFilter)
|
|
935
|
+
: callbackArrayElem(lowerer, call, fnRet);
|
|
936
|
+
const helper = arrayHofHelper(lowerer, method, elem, fnRet, arity, loc, outElem);
|
|
937
|
+
const resultType = method === "map" || method === "filter" ? arrayOf(outElem) : VOID;
|
|
600
938
|
return { kind: "call", callee: helper, args: [receiver, fnArg], type: resultType, loc };
|
|
601
939
|
}
|
|
940
|
+
/** Preserve the checker-selected payload ABI when a callback's runtime
|
|
941
|
+
* return gained only undefined. The array state byte carries that value. */
|
|
942
|
+
function callbackArrayElem(lowerer, call, fnRet) {
|
|
943
|
+
const result = lowerer.mapTypeOf(lowerer.typeOf(call));
|
|
944
|
+
return result?.kind === "array" && lowerer.runtimeOptionalWidening(fnRet, result.elem)
|
|
945
|
+
? result.elem : fnRet;
|
|
946
|
+
}
|
|
947
|
+
/** The checker-selected element of a filter result, with the same trust
|
|
948
|
+
* boundary as the historical narrowing filter: an inferred inline type
|
|
949
|
+
* predicate may prove one source arm; a written predicate annotation may
|
|
950
|
+
* not. Boolean is safe when every discarded arm is a nullish unit. */
|
|
951
|
+
function filterResultElem(lowerer, call, argNode, elem, valueT, booleanFilter) {
|
|
952
|
+
const callT = lowerer.typeOf(call);
|
|
953
|
+
const result = lowerer.mapTypeOf(callT);
|
|
954
|
+
let outElem = result?.kind === "array" ? result.elem : elem;
|
|
955
|
+
const access = ts.isPropertyAccessExpression(call.expression) ? call.expression : null;
|
|
956
|
+
const checkerReceiver = access
|
|
957
|
+
? lowerer.mapTypeOf(lowerer.typeOf(access.expression))
|
|
958
|
+
: null;
|
|
959
|
+
// Evolving-any arrays of island handles retain their jsval element ABI,
|
|
960
|
+
// while the checker later describes both the receiver and unchanged
|
|
961
|
+
// filter result by the evolved callable type. Equal checker elements mean
|
|
962
|
+
// no predicate narrowing occurred; preserve the runtime element home.
|
|
963
|
+
if (elem.kind === "jsval" && valueT.kind === "jsval" &&
|
|
964
|
+
result?.kind === "array" && checkerReceiver?.kind === "array" &&
|
|
965
|
+
typeEquals(result.elem, checkerReceiver.elem)) {
|
|
966
|
+
return elem;
|
|
967
|
+
}
|
|
968
|
+
if (!typeEquals(outElem, elem)) {
|
|
969
|
+
const contextual = lowerer.mapTypeOf(lowerer.checker.getContextualType(call) ?? callT);
|
|
970
|
+
if (contextual?.kind === "array" && typeEquals(contextual.elem, elem))
|
|
971
|
+
outElem = elem;
|
|
972
|
+
}
|
|
973
|
+
if (typeEquals(outElem, elem))
|
|
974
|
+
return elem;
|
|
975
|
+
const annotateEscape = "keep the receiver's element type instead — annotate the callback's return ': boolean' " +
|
|
976
|
+
"(the checker then skips the predicate) or annotate the result with the receiver's own " +
|
|
977
|
+
"element type — and narrow the elements after";
|
|
978
|
+
if (!booleanFilter) {
|
|
979
|
+
if (!ts.isArrowFunction(argNode) && !ts.isFunctionExpression(argNode)) {
|
|
980
|
+
lowerer.unsupported("SC1090", argNode, `narrowing '.filter' through a callback VALUE (only an inline callback whose predicate ` +
|
|
981
|
+
`the checker inferred can re-tag — ${annotateEscape})`);
|
|
982
|
+
}
|
|
983
|
+
if (argNode.type) {
|
|
984
|
+
lowerer.unsupported("SC1090", argNode, `narrowing '.filter' with a hand-written type predicate (a written 'x is T' is an ` +
|
|
985
|
+
`unchecked assertion nothing validates at runtime — ${annotateEscape})`);
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
if (valueT.kind !== "union" || lowerer.armTag(valueT.unionId, outElem) < 0) {
|
|
989
|
+
const multiArm = outElem.kind === "union";
|
|
990
|
+
lowerer.unsupported("SC1090", call, `'.filter' narrowing '${lowerer.fmt(elem)}' elements to ` +
|
|
991
|
+
`${multiArm ? `the multi-arm '${lowerer.fmt(outElem)}'` : `'${lowerer.fmt(outElem)}'`} ` +
|
|
992
|
+
`(${multiArm ? "only a SINGLE arm re-tags" : "only a single runtime arm re-tags"} — ${annotateEscape})`);
|
|
993
|
+
}
|
|
994
|
+
if (booleanFilter) {
|
|
995
|
+
const arms = lowerer.unions.get(valueT.unionId)?.arms ?? [];
|
|
996
|
+
if (arms.some((arm) => !typeEquals(arm, outElem) && !isUnitType(arm))) {
|
|
997
|
+
lowerer.unsupported("SC1090", call, `'.filter(Boolean)' narrowing '${lowerer.fmt(elem)}' to '${lowerer.fmt(outElem)}' ` +
|
|
998
|
+
"when another truthy arm could survive");
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
return outElem;
|
|
1002
|
+
}
|
|
1003
|
+
/** A native closure for the ambient Boolean callback. Reifying the
|
|
1004
|
+
* JavaScript constructor itself is unnecessary; Array#filter only observes
|
|
1005
|
+
* its one-argument ToBoolean result. */
|
|
1006
|
+
function truthyFilterCallback(lowerer, valueT, loc) {
|
|
1007
|
+
const fnT = { kind: "func", params: [valueT], ret: BOOL };
|
|
1008
|
+
const key = `filterBoolean:${typeKey(valueT)}`;
|
|
1009
|
+
let name = lowerer.arrHofHelpers.get(key);
|
|
1010
|
+
if (!name) {
|
|
1011
|
+
name = `%arr.filterBoolean.${lowerer.arrHofHelpers.size}`;
|
|
1012
|
+
lowerer.arrHofHelpers.set(key, name);
|
|
1013
|
+
const value = varRef("v.0", valueT, loc);
|
|
1014
|
+
lowerer.liftedFns.push({
|
|
1015
|
+
name,
|
|
1016
|
+
params: [{ localId: "v.0", name: "v", type: valueT }],
|
|
1017
|
+
returnType: BOOL,
|
|
1018
|
+
locals: [{ id: "v.0", name: "v", type: valueT, mutable: false }],
|
|
1019
|
+
body: [{ kind: "return", value: { kind: "toBool", operand: value, type: BOOL, loc }, loc }],
|
|
1020
|
+
loc,
|
|
1021
|
+
});
|
|
1022
|
+
}
|
|
1023
|
+
return { kind: "closure", fnName: name, captures: [], type: fnT, loc };
|
|
1024
|
+
}
|
|
602
1025
|
/** The predicate result kinds `.filter()` accepts. JS applies ToBoolean to
|
|
603
1026
|
* whatever the callback answers, so a bool is not required: the scalars and
|
|
604
1027
|
* the reference kinds have constant or by-value answers, and a union is fine
|
|
@@ -644,14 +1067,14 @@ function filterCond(call, fnRet, loc) {
|
|
|
644
1067
|
* combo. Named `%arr.<method>.<n>` ('%' keeps it out of the user
|
|
645
1068
|
* namespace); rides `liftedFns` into the module like a lifted lambda (it
|
|
646
1069
|
* is a plain function — no captures). */
|
|
647
|
-
function arrayHofHelper(lowerer, method, elem, fnRet, arity, loc) {
|
|
648
|
-
const key = `${method}:${typeKey(elem)}:${typeKey(fnRet)}:${arity}`;
|
|
1070
|
+
function arrayHofHelper(lowerer, method, elem, fnRet, arity, loc, outElem = fnRet) {
|
|
1071
|
+
const key = `${method}:${typeKey(elem)}:${typeKey(fnRet)}:${arity}:${typeKey(outElem)}`;
|
|
649
1072
|
const existing = lowerer.arrHofHelpers.get(key);
|
|
650
1073
|
if (existing)
|
|
651
1074
|
return existing;
|
|
652
1075
|
const name = `%arr.${method}.${lowerer.arrHofHelpers.size}`;
|
|
653
1076
|
lowerer.arrHofHelpers.set(key, name);
|
|
654
|
-
lowerer.liftedFns.push(buildArrayHofFn(lowerer, name, method, elem, fnRet, arity, loc));
|
|
1077
|
+
lowerer.liftedFns.push(buildArrayHofFn(lowerer, name, method, elem, fnRet, arity, loc, outElem));
|
|
655
1078
|
return name;
|
|
656
1079
|
}
|
|
657
1080
|
/** READ-ONLY array methods on TUPLE receivers — `t.slice(...)` and
|
|
@@ -743,13 +1166,14 @@ export function lowerTupleReadMethodCall(lowerer, call, access) {
|
|
|
743
1166
|
lowerer.noLowering(`.map with ${call.arguments.length} arguments`, call, "the thisArg parameter has no lowering — use an arrow function");
|
|
744
1167
|
}
|
|
745
1168
|
const argNode = call.arguments[0];
|
|
746
|
-
const { fnArg, arity } = hofCallbackArg(lowerer, argNode, [snapshotElem], arrayOf(snapshotElem));
|
|
1169
|
+
const { fnArg, arity } = hofCallbackArg(lowerer, argNode, [arrayValueType(lowerer, snapshotElem)], arrayOf(snapshotElem));
|
|
747
1170
|
const fnRet = fnArg.type.ret;
|
|
748
1171
|
if (fnRet.kind === "void" || fnRet.kind === "func")
|
|
749
1172
|
lowerer.badType(call, lowerer.typeOf(call));
|
|
750
1173
|
fenceProducedArrayElem(lowerer, call, "'.map()'", fnRet);
|
|
751
|
-
const
|
|
752
|
-
|
|
1174
|
+
const outElem = callbackArrayElem(lowerer, call, fnRet);
|
|
1175
|
+
const helper = arrayHofHelper(lowerer, "map", snapshotElem, fnRet, arity, loc, outElem);
|
|
1176
|
+
return { kind: "call", callee: helper, args: [snapshot, fnArg], type: arrayOf(outElem), loc };
|
|
753
1177
|
}
|
|
754
1178
|
/** An OOB-SAFE indexed read: `xs[i]`
|
|
755
1179
|
* answers the interned `elem | undefined` union — the element when `i`
|
|
@@ -764,54 +1188,13 @@ export function lowerSafeIndexRead(lowerer, arr, index, loc) {
|
|
|
764
1188
|
const elem = arr.type.elem;
|
|
765
1189
|
if (elem.kind === "void" || elem.kind === "dyn")
|
|
766
1190
|
return null;
|
|
767
|
-
const resultT =
|
|
768
|
-
if (!resultT || resultT.kind !== "union")
|
|
769
|
-
return null;
|
|
770
|
-
const undefTag = lowerer.armTag(resultT.unionId, UNDEFINED_T);
|
|
771
|
-
const foundTag = elem.kind === "union" ? -1 : lowerer.armTag(resultT.unionId, elem);
|
|
772
|
-
if (undefTag < 0 || (elem.kind !== "union" && foundTag < 0))
|
|
773
|
-
return null;
|
|
774
|
-
const arrT = arr.type;
|
|
1191
|
+
const resultT = arrayValueType(lowerer, elem);
|
|
775
1192
|
const key = `idxOr:${typeKey(elem)}`;
|
|
776
1193
|
let name = lowerer.arrHofHelpers.get(key);
|
|
777
1194
|
if (!name) {
|
|
778
1195
|
name = `%arr.idxOr.${lowerer.arrHofHelpers.size}`;
|
|
779
1196
|
lowerer.arrHofHelpers.set(key, name);
|
|
780
|
-
const
|
|
781
|
-
const n = varRef("n.0", F64, loc);
|
|
782
|
-
const v = varRef("v.0", elem, loc);
|
|
783
|
-
const lt = (l, r) => ({ kind: "bin", op: "<", left: l, right: r, type: BOOL, loc });
|
|
784
|
-
const miss = {
|
|
785
|
-
kind: "unionWrap",
|
|
786
|
-
unionId: resultT.unionId,
|
|
787
|
-
tag: undefTag,
|
|
788
|
-
value: { kind: "unitLit", unit: "undefined", type: UNDEFINED_T, loc },
|
|
789
|
-
type: resultT,
|
|
790
|
-
loc,
|
|
791
|
-
};
|
|
792
|
-
const body = [
|
|
793
|
-
readLenStmt(arrT, loc),
|
|
794
|
-
{ kind: "if", cond: { kind: "bin", op: "!==", left: i, right: i, type: BOOL, loc }, then: [{ kind: "return", value: miss, loc }], else_: null, loc },
|
|
795
|
-
{ kind: "if", cond: lt(i, numLit(0, loc)), then: [{ kind: "return", value: miss, loc }], else_: null, loc },
|
|
796
|
-
{ kind: "if", cond: { kind: "bin", op: ">=", left: i, right: n, type: BOOL, loc }, then: [{ kind: "return", value: miss, loc }], else_: null, loc },
|
|
797
|
-
// A fractional index is a property miss too (floor(i) < i exactly
|
|
798
|
-
// when i has a fraction; negatives already returned above).
|
|
799
|
-
{
|
|
800
|
-
kind: "if",
|
|
801
|
-
cond: lt({ kind: "libCall", fn: "math.floor", args: [i], type: F64, loc }, i),
|
|
802
|
-
then: [{ kind: "return", value: miss, loc }],
|
|
803
|
-
else_: null,
|
|
804
|
-
loc,
|
|
805
|
-
},
|
|
806
|
-
{ kind: "varDecl", localId: "v.0", init: { kind: "arrayGet", arr: varRef("a.0", arrT, loc), index: i, type: elem, loc }, loc },
|
|
807
|
-
{
|
|
808
|
-
kind: "return",
|
|
809
|
-
value: elem.kind === "union"
|
|
810
|
-
? lowerer.coerceToExpected(v, resultT)
|
|
811
|
-
: { kind: "unionWrap", unionId: resultT.unionId, tag: foundTag, value: v, type: resultT, loc },
|
|
812
|
-
loc,
|
|
813
|
-
},
|
|
814
|
-
];
|
|
1197
|
+
const arrT = arr.type;
|
|
815
1198
|
lowerer.liftedFns.push({
|
|
816
1199
|
name,
|
|
817
1200
|
params: [
|
|
@@ -820,12 +1203,10 @@ export function lowerSafeIndexRead(lowerer, arr, index, loc) {
|
|
|
820
1203
|
],
|
|
821
1204
|
returnType: resultT,
|
|
822
1205
|
locals: [
|
|
823
|
-
{ id: "a.0", name: "a", type: arrT, mutable:
|
|
824
|
-
{ id: "i.0", name: "i", type: F64, mutable:
|
|
825
|
-
{ id: "n.0", name: "n", type: F64, mutable: false },
|
|
826
|
-
{ id: "v.0", name: "v", type: elem, mutable: false },
|
|
1206
|
+
{ id: "a.0", name: "a", type: arrT, mutable: false },
|
|
1207
|
+
{ id: "i.0", name: "i", type: F64, mutable: false },
|
|
827
1208
|
],
|
|
828
|
-
body,
|
|
1209
|
+
body: [{ kind: "return", value: arrayValueRead(lowerer, varRef("a.0", arrT, loc), varRef("i.0", F64, loc), elem, loc), loc }],
|
|
829
1210
|
loc,
|
|
830
1211
|
});
|
|
831
1212
|
}
|
|
@@ -859,7 +1240,7 @@ function arrayConcatHelper(lowerer, elem, shape, loc) {
|
|
|
859
1240
|
kind: "exprStmt",
|
|
860
1241
|
expr: {
|
|
861
1242
|
kind: "arrIntrinsic",
|
|
862
|
-
method: s === "a" ? "
|
|
1243
|
+
method: s === "a" ? "concatSpread" : "push",
|
|
863
1244
|
receiver: varRef("out.0", arrT, loc),
|
|
864
1245
|
args: [src],
|
|
865
1246
|
type: F64,
|
|
@@ -887,9 +1268,10 @@ function arrayConcatHelper(lowerer, elem, shape, loc) {
|
|
|
887
1268
|
* `arity` is the callback's declared parameter count (1–3): the loop
|
|
888
1269
|
* passes the index and the receiver itself after the element when the
|
|
889
1270
|
* callback names them, exactly the arguments JS supplies. */
|
|
890
|
-
function buildArrayHofFn(lowerer, name, method, elem, fnRet, arity, loc) {
|
|
1271
|
+
function buildArrayHofFn(lowerer, name, method, elem, fnRet, arity, loc, outElem) {
|
|
891
1272
|
const arrT = arrayOf(elem);
|
|
892
|
-
const
|
|
1273
|
+
const valueT = arrayValueType(lowerer, elem);
|
|
1274
|
+
const fnT = funcOf([arrayValueType(lowerer, elem), F64, arrT].slice(0, arity), fnRet);
|
|
893
1275
|
const locals = [
|
|
894
1276
|
{ id: "a.0", name: "a", type: arrT, mutable: true },
|
|
895
1277
|
{ id: "f.0", name: "f", type: fnT, mutable: true },
|
|
@@ -907,25 +1289,14 @@ function buildArrayHofFn(lowerer, name, method, elem, fnRet, arity, loc) {
|
|
|
907
1289
|
type: fnRet,
|
|
908
1290
|
loc,
|
|
909
1291
|
});
|
|
910
|
-
const getElem =
|
|
911
|
-
|
|
1292
|
+
const getElem = arrayValueRead(lowerer, varRef("a.0", arrT, loc), varRef("i.0", F64, loc), elem, loc);
|
|
1293
|
+
const hasElem = {
|
|
1294
|
+
kind: "arrayHas",
|
|
912
1295
|
arr: varRef("a.0", arrT, loc),
|
|
913
1296
|
index: varRef("i.0", F64, loc),
|
|
914
|
-
type:
|
|
1297
|
+
type: BOOL,
|
|
915
1298
|
loc,
|
|
916
1299
|
};
|
|
917
|
-
const push = (outT, value) => ({
|
|
918
|
-
kind: "exprStmt",
|
|
919
|
-
expr: {
|
|
920
|
-
kind: "arrIntrinsic",
|
|
921
|
-
method: "push",
|
|
922
|
-
receiver: varRef("out.0", outT, loc),
|
|
923
|
-
args: [value],
|
|
924
|
-
type: F64,
|
|
925
|
-
loc,
|
|
926
|
-
},
|
|
927
|
-
loc,
|
|
928
|
-
});
|
|
929
1300
|
const readLen = {
|
|
930
1301
|
kind: "varDecl",
|
|
931
1302
|
localId: "n.0",
|
|
@@ -935,41 +1306,85 @@ function buildArrayHofFn(lowerer, name, method, elem, fnRet, arity, loc) {
|
|
|
935
1306
|
const forLoop = (body) => countedFor(loc, varRef("n.0", F64, loc), () => body);
|
|
936
1307
|
let returnType;
|
|
937
1308
|
let body;
|
|
938
|
-
if (method === "map") {
|
|
939
|
-
const outT = arrayOf(
|
|
940
|
-
locals.push({ id: "out.0", name: "out", type: outT, mutable: false });
|
|
1309
|
+
if (method === "map" || method === "flatMap") {
|
|
1310
|
+
const outT = arrayOf(outElem);
|
|
1311
|
+
locals.push({ id: "out.0", name: "out", type: outT, mutable: false }, { id: "mapped.0", name: "mapped", type: fnRet, mutable: false });
|
|
941
1312
|
returnType = outT;
|
|
942
1313
|
body = [
|
|
943
1314
|
{ kind: "varDecl", localId: "out.0", init: { kind: "arrayLit", elems: [], type: outT, loc }, loc },
|
|
944
1315
|
readLen,
|
|
945
|
-
|
|
1316
|
+
...(method === "map" ? [{ kind: "arraySetLength", arr: varRef("out.0", outT, loc), length: varRef("n.0", F64, loc), loc }] : []),
|
|
1317
|
+
forLoop([
|
|
1318
|
+
{
|
|
1319
|
+
kind: "if",
|
|
1320
|
+
cond: hasElem,
|
|
1321
|
+
then: [
|
|
1322
|
+
{ kind: "varDecl", localId: "mapped.0", init: callF(getElem), loc },
|
|
1323
|
+
arrayValueStore(lowerer, varRef("out.0", outT, loc), method === "map" ? varRef("i.0", F64, loc) : { kind: "arrIntrinsic", method: "length", receiver: varRef("out.0", outT, loc), args: [], type: F64, loc }, varRef("mapped.0", fnRet, loc), outElem, loc),
|
|
1324
|
+
],
|
|
1325
|
+
else_: null,
|
|
1326
|
+
loc,
|
|
1327
|
+
},
|
|
1328
|
+
]),
|
|
946
1329
|
{ kind: "return", value: varRef("out.0", outT, loc), loc },
|
|
947
1330
|
];
|
|
948
1331
|
}
|
|
949
1332
|
else if (method === "filter") {
|
|
950
|
-
|
|
951
|
-
|
|
1333
|
+
const outT = arrayOf(outElem);
|
|
1334
|
+
locals.push({ id: "out.0", name: "out", type: outT, mutable: false }, { id: "v.0", name: "v", type: valueT, mutable: false });
|
|
1335
|
+
returnType = outT;
|
|
1336
|
+
const retained = typeEquals(outElem, elem)
|
|
1337
|
+
? varRef("v.0", valueT, loc)
|
|
1338
|
+
: valueT.kind === "union"
|
|
1339
|
+
? {
|
|
1340
|
+
kind: "unionNarrow",
|
|
1341
|
+
unionId: valueT.unionId,
|
|
1342
|
+
tag: lowerer.armTag(valueT.unionId, outElem),
|
|
1343
|
+
value: varRef("v.0", valueT, loc),
|
|
1344
|
+
type: outElem,
|
|
1345
|
+
loc,
|
|
1346
|
+
}
|
|
1347
|
+
: varRef("v.0", valueT, loc);
|
|
952
1348
|
body = [
|
|
953
|
-
{ kind: "varDecl", localId: "out.0", init: { kind: "arrayLit", elems: [], type:
|
|
1349
|
+
{ kind: "varDecl", localId: "out.0", init: { kind: "arrayLit", elems: [], type: outT, loc }, loc },
|
|
954
1350
|
readLen,
|
|
955
1351
|
forLoop([
|
|
956
|
-
{ kind: "varDecl", localId: "v.0", init: getElem, loc },
|
|
957
1352
|
{
|
|
958
1353
|
kind: "if",
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
1354
|
+
cond: hasElem,
|
|
1355
|
+
then: [
|
|
1356
|
+
{ kind: "varDecl", localId: "v.0", init: getElem, loc },
|
|
1357
|
+
{
|
|
1358
|
+
kind: "if",
|
|
1359
|
+
// ToBoolean over the predicate's answer — inert when it already
|
|
1360
|
+
// returned bool, the per-union helper when it returned a union.
|
|
1361
|
+
cond: filterCond(callF(varRef("v.0", valueT, loc)), fnRet, loc),
|
|
1362
|
+
then: [arrayValueStore(lowerer, varRef("out.0", outT, loc), { kind: "arrIntrinsic", method: "length", receiver: varRef("out.0", outT, loc), args: [], type: F64, loc }, retained, outElem, loc)],
|
|
1363
|
+
else_: null,
|
|
1364
|
+
loc,
|
|
1365
|
+
},
|
|
1366
|
+
],
|
|
963
1367
|
else_: null,
|
|
964
1368
|
loc,
|
|
965
1369
|
},
|
|
966
1370
|
]),
|
|
967
|
-
{ kind: "return", value: varRef("out.0",
|
|
1371
|
+
{ kind: "return", value: varRef("out.0", outT, loc), loc },
|
|
968
1372
|
];
|
|
969
1373
|
}
|
|
970
1374
|
else {
|
|
971
1375
|
returnType = VOID;
|
|
972
|
-
body = [
|
|
1376
|
+
body = [
|
|
1377
|
+
readLen,
|
|
1378
|
+
forLoop([
|
|
1379
|
+
{
|
|
1380
|
+
kind: "if",
|
|
1381
|
+
cond: hasElem,
|
|
1382
|
+
then: [{ kind: "exprStmt", expr: callF(getElem), loc }],
|
|
1383
|
+
else_: null,
|
|
1384
|
+
loc,
|
|
1385
|
+
},
|
|
1386
|
+
]),
|
|
1387
|
+
];
|
|
973
1388
|
}
|
|
974
1389
|
return { name, params, returnType, locals, body, loc };
|
|
975
1390
|
}
|
|
@@ -992,7 +1407,7 @@ function lowerArrayFindLikeCall(lowerer, call, access, method, elem) {
|
|
|
992
1407
|
const argNode = call.arguments[0];
|
|
993
1408
|
if (!argNode)
|
|
994
1409
|
lowerer.unsupported("SC1090", call, "this call form"); // tsc-guarded
|
|
995
|
-
const { fnArg, arity } = hofCallbackArg(lowerer, argNode, [elem], arrT);
|
|
1410
|
+
const { fnArg, arity } = hofCallbackArg(lowerer, argNode, [arrayValueType(lowerer, elem)], arrT);
|
|
996
1411
|
// JS takes the ToBoolean of the predicate's result — allowed wherever
|
|
997
1412
|
// that ToBoolean has a static answer (bool passes through; f64/string
|
|
998
1413
|
// by value; a truthy-answerable union by its arm — the
|
|
@@ -1028,15 +1443,7 @@ function lowerArrayFindLikeCall(lowerer, call, access, method, elem) {
|
|
|
1028
1443
|
const undefTag = lowerer.armTag(resultT.unionId, UNDEFINED_T);
|
|
1029
1444
|
if (undefTag < 0)
|
|
1030
1445
|
lowerer.badType(call, lowerer.typeOf(call));
|
|
1031
|
-
|
|
1032
|
-
if (!typeEquals(resultT, elem)) {
|
|
1033
|
-
foundTag = lowerer.armTag(resultT.unionId, elem);
|
|
1034
|
-
if (foundTag < 0) {
|
|
1035
|
-
lowerer.unsupported("SC1090", call, `'.${method}' on '${lowerer.fmt(elem)}'-element arrays (the found element would need a union ` +
|
|
1036
|
-
"re-tag that is not supported yet — loop and test instead)");
|
|
1037
|
-
}
|
|
1038
|
-
}
|
|
1039
|
-
const helper = findHelper(lowerer, elem, resultT, foundTag, undefTag, fnRet, arity, last, loc);
|
|
1446
|
+
const helper = findHelper(lowerer, elem, resultT, undefTag, fnRet, arity, last, loc);
|
|
1040
1447
|
return { kind: "call", callee: helper, args: [receiver, fnArg], type: resultT, loc };
|
|
1041
1448
|
}
|
|
1042
1449
|
/** ToBoolean of a predicate result inside a synthesized HOF helper — the
|
|
@@ -1053,7 +1460,7 @@ function predToBool(e) {
|
|
|
1053
1460
|
* for (i = 0; i < n; i++) { v = a[i]; if (f(v)) return <v as result arm>; }
|
|
1054
1461
|
* return <undefined arm>;
|
|
1055
1462
|
*/
|
|
1056
|
-
function findHelper(lowerer, elem, resultT,
|
|
1463
|
+
function findHelper(lowerer, elem, resultT, undefTag, fnRet, arity, last, loc) {
|
|
1057
1464
|
const method = last ? "findLast" : "find";
|
|
1058
1465
|
const key = `${method}:${typeKey(elem)}:${typeKey(resultT)}:${typeKey(fnRet)}:${arity}`;
|
|
1059
1466
|
const existing = lowerer.arrHofHelpers.get(key);
|
|
@@ -1062,11 +1469,10 @@ function findHelper(lowerer, elem, resultT, foundTag, undefTag, fnRet, arity, la
|
|
|
1062
1469
|
const name = `%arr.${method}.${lowerer.arrHofHelpers.size}`;
|
|
1063
1470
|
lowerer.arrHofHelpers.set(key, name);
|
|
1064
1471
|
const arrT = arrayOf(elem);
|
|
1065
|
-
const fnT = funcOf([elem, F64, arrT].slice(0, arity), fnRet);
|
|
1066
|
-
const
|
|
1067
|
-
const
|
|
1068
|
-
|
|
1069
|
-
: { kind: "unionWrap", unionId: resultT.unionId, tag: foundTag, value: v, type: resultT, loc };
|
|
1472
|
+
const fnT = funcOf([arrayValueType(lowerer, elem), F64, arrT].slice(0, arity), fnRet);
|
|
1473
|
+
const valueT = arrayValueType(lowerer, elem);
|
|
1474
|
+
const v = varRef("v.0", valueT, loc);
|
|
1475
|
+
const found = lowerer.coerceToExpected(v, resultT);
|
|
1070
1476
|
const miss = {
|
|
1071
1477
|
kind: "unionWrap",
|
|
1072
1478
|
unionId: resultT.unionId,
|
|
@@ -1075,8 +1481,8 @@ function findHelper(lowerer, elem, resultT, foundTag, undefTag, fnRet, arity, la
|
|
|
1075
1481
|
type: resultT,
|
|
1076
1482
|
loc,
|
|
1077
1483
|
};
|
|
1078
|
-
const
|
|
1079
|
-
{ kind: "varDecl", localId: "v.0", init:
|
|
1484
|
+
const visitBody = [
|
|
1485
|
+
{ kind: "varDecl", localId: "v.0", init: arrayValueRead(lowerer, varRef("a.0", arrT, loc), varRef("i.0", F64, loc), elem, loc), loc },
|
|
1080
1486
|
{
|
|
1081
1487
|
kind: "if",
|
|
1082
1488
|
cond: predToBool({
|
|
@@ -1091,6 +1497,7 @@ function findHelper(lowerer, elem, resultT, foundTag, undefTag, fnRet, arity, la
|
|
|
1091
1497
|
loc,
|
|
1092
1498
|
},
|
|
1093
1499
|
];
|
|
1500
|
+
const visit = visitBody;
|
|
1094
1501
|
const loop = last
|
|
1095
1502
|
? reverseCountedForLoop(loc, visit)
|
|
1096
1503
|
: countedFor(loc, varRef("n.0", F64, loc), () => visit);
|
|
@@ -1111,7 +1518,7 @@ function findHelper(lowerer, elem, resultT, foundTag, undefTag, fnRet, arity, la
|
|
|
1111
1518
|
{ id: "f.0", name: "f", type: fnT, mutable: true },
|
|
1112
1519
|
{ id: "n.0", name: "n", type: F64, mutable: false },
|
|
1113
1520
|
{ id: "i.0", name: "i", type: F64, mutable: true },
|
|
1114
|
-
{ id: "v.0", name: "v", type:
|
|
1521
|
+
{ id: "v.0", name: "v", type: valueT, mutable: false },
|
|
1115
1522
|
],
|
|
1116
1523
|
body,
|
|
1117
1524
|
loc,
|
|
@@ -1132,13 +1539,13 @@ function findIndexHelper(lowerer, elem, fnRet, arity, last, loc) {
|
|
|
1132
1539
|
const name = `%arr.${method}.${lowerer.arrHofHelpers.size}`;
|
|
1133
1540
|
lowerer.arrHofHelpers.set(key, name);
|
|
1134
1541
|
const arrT = arrayOf(elem);
|
|
1135
|
-
const fnT = funcOf([elem, F64, arrT].slice(0, arity), fnRet);
|
|
1542
|
+
const fnT = funcOf([arrayValueType(lowerer, elem), F64, arrT].slice(0, arity), fnRet);
|
|
1136
1543
|
const visit = [{
|
|
1137
1544
|
kind: "if",
|
|
1138
1545
|
cond: predToBool({
|
|
1139
1546
|
kind: "callValue",
|
|
1140
1547
|
callee: varRef("f.0", fnT, loc),
|
|
1141
|
-
args: [
|
|
1548
|
+
args: [arrayValueRead(lowerer, varRef("a.0", arrT, loc), varRef("i.0", F64, loc), elem, loc), varRef("i.0", F64, loc), varRef("a.0", arrT, loc)].slice(0, arity),
|
|
1142
1549
|
type: fnRet,
|
|
1143
1550
|
loc,
|
|
1144
1551
|
}),
|
|
@@ -1185,11 +1592,11 @@ function someEveryHelper(lowerer, method, elem, fnRet, arity, loc) {
|
|
|
1185
1592
|
const name = `%arr.${method}.${lowerer.arrHofHelpers.size}`;
|
|
1186
1593
|
lowerer.arrHofHelpers.set(key, name);
|
|
1187
1594
|
const arrT = arrayOf(elem);
|
|
1188
|
-
const fnT = funcOf([elem, F64, arrT].slice(0, arity), fnRet);
|
|
1595
|
+
const fnT = funcOf([arrayValueType(lowerer, elem), F64, arrT].slice(0, arity), fnRet);
|
|
1189
1596
|
const callF = predToBool({
|
|
1190
1597
|
kind: "callValue",
|
|
1191
1598
|
callee: varRef("f.0", fnT, loc),
|
|
1192
|
-
args: [
|
|
1599
|
+
args: [arrayValueRead(lowerer, varRef("a.0", arrT, loc), varRef("i.0", F64, loc), elem, loc), varRef("i.0", F64, loc), varRef("a.0", arrT, loc)].slice(0, arity),
|
|
1193
1600
|
type: fnRet,
|
|
1194
1601
|
loc,
|
|
1195
1602
|
});
|
|
@@ -1198,8 +1605,14 @@ function someEveryHelper(lowerer, method, elem, fnRet, arity, loc) {
|
|
|
1198
1605
|
countedFor(loc, varRef("n.0", F64, loc), () => [
|
|
1199
1606
|
{
|
|
1200
1607
|
kind: "if",
|
|
1201
|
-
cond:
|
|
1202
|
-
then: [{
|
|
1608
|
+
cond: getHasExpr(arrT, loc),
|
|
1609
|
+
then: [{
|
|
1610
|
+
kind: "if",
|
|
1611
|
+
cond: method === "some" ? callF : { kind: "unary", op: "!", operand: callF, type: BOOL, loc },
|
|
1612
|
+
then: [{ kind: "return", value: boolLit(method === "some", loc), loc }],
|
|
1613
|
+
else_: null,
|
|
1614
|
+
loc,
|
|
1615
|
+
}],
|
|
1203
1616
|
else_: null,
|
|
1204
1617
|
loc,
|
|
1205
1618
|
},
|
|
@@ -1237,7 +1650,7 @@ function lowerArrayFlatMapCall(lowerer, call, access, elem) {
|
|
|
1237
1650
|
const argNode = call.arguments[0];
|
|
1238
1651
|
if (!argNode)
|
|
1239
1652
|
lowerer.unsupported("SC1090", call, "this call form"); // tsc-guarded
|
|
1240
|
-
const { fnArg, arity } = hofCallbackArg(lowerer, argNode, [elem], arrT);
|
|
1653
|
+
const { fnArg, arity } = hofCallbackArg(lowerer, argNode, [arrayValueType(lowerer, elem)], arrT);
|
|
1241
1654
|
const fnRet = fnArg.type.ret;
|
|
1242
1655
|
if (fnRet.kind === "union") {
|
|
1243
1656
|
const def = lowerer.unions.get(fnRet.unionId);
|
|
@@ -1247,12 +1660,13 @@ function lowerArrayFlatMapCall(lowerer, call, access, elem) {
|
|
|
1247
1660
|
}
|
|
1248
1661
|
}
|
|
1249
1662
|
if (fnRet.kind !== "array") {
|
|
1250
|
-
//
|
|
1663
|
+
// Scalar results append densely; source holes do not produce output slots.
|
|
1251
1664
|
if (fnRet.kind === "void" || fnRet.kind === "func")
|
|
1252
1665
|
lowerer.badType(call, lowerer.typeOf(call));
|
|
1253
1666
|
fenceProducedArrayElem(lowerer, call, "'.flatMap()'", fnRet);
|
|
1254
|
-
const
|
|
1255
|
-
|
|
1667
|
+
const outElem = callbackArrayElem(lowerer, call, fnRet);
|
|
1668
|
+
const helper = arrayHofHelper(lowerer, "flatMap", elem, fnRet, arity, loc, outElem);
|
|
1669
|
+
return { kind: "call", callee: helper, args: [receiver, fnArg], type: arrayOf(outElem), loc };
|
|
1256
1670
|
}
|
|
1257
1671
|
const helper = flatMapHelper(lowerer, elem, fnRet, arity, loc);
|
|
1258
1672
|
return { kind: "call", callee: helper, args: [receiver, fnArg], type: fnRet, loc };
|
|
@@ -1275,7 +1689,7 @@ function flatMapHelper(lowerer, elem, fnRet, arity, loc) {
|
|
|
1275
1689
|
lowerer.arrHofHelpers.set(key, name);
|
|
1276
1690
|
const arrT = arrayOf(elem);
|
|
1277
1691
|
const inner = fnRet.elem;
|
|
1278
|
-
const fnT = funcOf([elem, F64, arrT].slice(0, arity), fnRet);
|
|
1692
|
+
const fnT = funcOf([arrayValueType(lowerer, elem), F64, arrT].slice(0, arity), fnRet);
|
|
1279
1693
|
const innerLoop = {
|
|
1280
1694
|
kind: "for",
|
|
1281
1695
|
init: { kind: "varDecl", localId: "j.0", init: numLit(0, loc), loc },
|
|
@@ -1288,15 +1702,13 @@ function flatMapHelper(lowerer, elem, fnRet, arity, loc) {
|
|
|
1288
1702
|
},
|
|
1289
1703
|
body: [
|
|
1290
1704
|
{
|
|
1291
|
-
kind: "
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
receiver: varRef("out.0", fnRet, loc),
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
loc,
|
|
1299
|
-
},
|
|
1705
|
+
kind: "if",
|
|
1706
|
+
cond: arrayHasExpr(varRef("r.0", fnRet, loc), varRef("j.0", F64, loc), loc),
|
|
1707
|
+
then: [
|
|
1708
|
+
{ kind: "varDecl", localId: "inner.0", init: arrayValueRead(lowerer, varRef("r.0", fnRet, loc), varRef("j.0", F64, loc), inner, loc), loc },
|
|
1709
|
+
arrayValueStore(lowerer, varRef("out.0", fnRet, loc), { kind: "arrIntrinsic", method: "length", receiver: varRef("out.0", fnRet, loc), args: [], type: F64, loc }, varRef("inner.0", arrayValueType(lowerer, inner), loc), inner, loc),
|
|
1710
|
+
],
|
|
1711
|
+
else_: null,
|
|
1300
1712
|
loc,
|
|
1301
1713
|
},
|
|
1302
1714
|
],
|
|
@@ -1307,24 +1719,32 @@ function flatMapHelper(lowerer, elem, fnRet, arity, loc) {
|
|
|
1307
1719
|
readLenStmt(arrT, loc),
|
|
1308
1720
|
countedFor(loc, varRef("n.0", F64, loc), () => [
|
|
1309
1721
|
{
|
|
1310
|
-
kind: "
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1722
|
+
kind: "if",
|
|
1723
|
+
cond: getHasExpr(arrT, loc),
|
|
1724
|
+
then: [
|
|
1725
|
+
{
|
|
1726
|
+
kind: "varDecl",
|
|
1727
|
+
localId: "r.0",
|
|
1728
|
+
init: {
|
|
1729
|
+
kind: "callValue",
|
|
1730
|
+
callee: varRef("f.0", fnT, loc),
|
|
1731
|
+
args: [arrayValueRead(lowerer, varRef("a.0", arrT, loc), varRef("i.0", F64, loc), elem, loc), varRef("i.0", F64, loc), varRef("a.0", arrT, loc)].slice(0, arity),
|
|
1732
|
+
type: fnRet,
|
|
1733
|
+
loc,
|
|
1734
|
+
},
|
|
1735
|
+
loc,
|
|
1736
|
+
},
|
|
1737
|
+
{
|
|
1738
|
+
kind: "varDecl",
|
|
1739
|
+
localId: "m.0",
|
|
1740
|
+
init: { kind: "arrIntrinsic", method: "length", receiver: varRef("r.0", fnRet, loc), args: [], type: F64, loc },
|
|
1741
|
+
loc,
|
|
1742
|
+
},
|
|
1743
|
+
innerLoop,
|
|
1744
|
+
],
|
|
1745
|
+
else_: null,
|
|
1325
1746
|
loc,
|
|
1326
1747
|
},
|
|
1327
|
-
innerLoop,
|
|
1328
1748
|
]),
|
|
1329
1749
|
{ kind: "return", value: varRef("out.0", fnRet, loc), loc },
|
|
1330
1750
|
];
|
|
@@ -1344,6 +1764,7 @@ function flatMapHelper(lowerer, elem, fnRet, arity, loc) {
|
|
|
1344
1764
|
{ id: "r.0", name: "r", type: fnRet, mutable: false },
|
|
1345
1765
|
{ id: "m.0", name: "m", type: F64, mutable: false },
|
|
1346
1766
|
{ id: "j.0", name: "j", type: F64, mutable: true },
|
|
1767
|
+
{ id: "inner.0", name: "inner", type: arrayValueType(lowerer, inner), mutable: false },
|
|
1347
1768
|
],
|
|
1348
1769
|
body,
|
|
1349
1770
|
loc,
|
|
@@ -1363,13 +1784,12 @@ function lowerArrayReduceCall(lowerer, call, access, method, elem) {
|
|
|
1363
1784
|
if (!argNode)
|
|
1364
1785
|
lowerer.unsupported("SC1090", call, "this call form"); // tsc-guarded
|
|
1365
1786
|
const hasInit = call.arguments.length === 2;
|
|
1366
|
-
const accT =
|
|
1787
|
+
const accT = lowerer.runtimeOptionalReduceTypes.get(call) ??
|
|
1788
|
+
(hasInit ? lowerer.irTypeOf(call) : arrayValueType(lowerer, elem));
|
|
1367
1789
|
if (accT.kind === "void" || accT.kind === "func")
|
|
1368
1790
|
lowerer.badType(call, lowerer.typeOf(call));
|
|
1369
|
-
const { fnArg, arity } = hofCallbackArg(lowerer, argNode, [accT, elem], arrT);
|
|
1370
|
-
|
|
1371
|
-
lowerer.badType(argNode, lowerer.typeOf(argNode));
|
|
1372
|
-
const helper = reduceHelper(lowerer, method, elem, accT, arity, hasInit, loc);
|
|
1791
|
+
const { fnArg, arity } = hofCallbackArg(lowerer, argNode, [accT, arrayValueType(lowerer, elem)], arrT);
|
|
1792
|
+
const helper = reduceHelper(lowerer, method, elem, accT, fnArg.type.ret, arity, hasInit, loc);
|
|
1373
1793
|
const args = [receiver, fnArg];
|
|
1374
1794
|
if (hasInit)
|
|
1375
1795
|
args.push(lowerer.lowerExprExpecting(call.arguments[1], accT));
|
|
@@ -1385,76 +1805,53 @@ function lowerArrayReduceCall(lowerer, call, access, method, elem) {
|
|
|
1385
1805
|
* reduce walks 0→n-1, reduceRight n-1→0; the seed without an initial
|
|
1386
1806
|
* value is a[0] / a[n-1] and the loop starts one past it. The empty-array
|
|
1387
1807
|
* TypeError message is Node's, byte for byte. */
|
|
1388
|
-
function reduceHelper(lowerer, method, elem, accT, arity, hasInit, loc) {
|
|
1389
|
-
const key = `${method}:${typeKey(elem)}:${typeKey(accT)}:${arity}:${hasInit ? "init" : "seed"}`;
|
|
1808
|
+
function reduceHelper(lowerer, method, elem, accT, fnRet, arity, hasInit, loc) {
|
|
1809
|
+
const key = `${method}:${typeKey(elem)}:${typeKey(accT)}:${typeKey(fnRet)}:${arity}:${hasInit ? "init" : "seed"}`;
|
|
1390
1810
|
const existing = lowerer.arrHofHelpers.get(key);
|
|
1391
1811
|
if (existing)
|
|
1392
1812
|
return existing;
|
|
1393
1813
|
const name = `%arr.${method}.${lowerer.arrHofHelpers.size}`;
|
|
1394
1814
|
lowerer.arrHofHelpers.set(key, name);
|
|
1395
1815
|
const arrT = arrayOf(elem);
|
|
1396
|
-
const fnT = funcOf([accT, elem, F64, arrT].slice(0, arity),
|
|
1816
|
+
const fnT = funcOf([accT, arrayValueType(lowerer, elem), F64, arrT].slice(0, arity), fnRet);
|
|
1397
1817
|
const n = varRef("n.0", F64, loc);
|
|
1398
1818
|
const i = varRef("i.0", F64, loc);
|
|
1399
1819
|
const right = method === "reduceRight";
|
|
1400
|
-
const at = (index) => (
|
|
1820
|
+
const at = (index) => arrayValueRead(lowerer, varRef("a.0", arrT, loc), index, elem, loc);
|
|
1401
1821
|
const locals = [
|
|
1402
1822
|
{ id: "a.0", name: "a", type: arrT, mutable: true },
|
|
1403
1823
|
{ id: "f.0", name: "f", type: fnT, mutable: true },
|
|
1404
1824
|
...(hasInit ? [{ id: "z.0", name: "z", type: accT, mutable: true }] : []),
|
|
1405
1825
|
{ id: "acc.0", name: "acc", type: accT, mutable: true },
|
|
1406
1826
|
{ id: "n.0", name: "n", type: F64, mutable: false },
|
|
1407
|
-
{ id: "i.0", name: "i", type: F64, mutable: true },
|
|
1408
|
-
];
|
|
1409
|
-
const params = [
|
|
1410
|
-
{ localId: "a.0", name: "a", type: arrT },
|
|
1411
|
-
{ localId: "f.0", name: "f", type: fnT },
|
|
1412
|
-
...(hasInit ? [{ localId: "z.0", name: "z", type: accT }] : []),
|
|
1413
|
-
];
|
|
1414
|
-
const seedStmts = hasInit
|
|
1415
|
-
? [{ kind: "varDecl", localId: "acc.0", init: varRef("z.0", accT, loc), loc }]
|
|
1416
|
-
: [
|
|
1417
|
-
{
|
|
1418
|
-
kind: "if",
|
|
1419
|
-
cond: { kind: "bin", op: "===", left: n, right: numLit(0, loc), type: BOOL, loc },
|
|
1420
|
-
then: [
|
|
1421
|
-
{
|
|
1422
|
-
kind: "throw",
|
|
1423
|
-
value: {
|
|
1424
|
-
kind: "libCall",
|
|
1425
|
-
fn: "error.new",
|
|
1426
|
-
args: [
|
|
1427
|
-
{
|
|
1428
|
-
kind: "strLit",
|
|
1429
|
-
value: "Reduce of empty array with no initial value",
|
|
1430
|
-
type: STRING,
|
|
1431
|
-
loc,
|
|
1432
|
-
},
|
|
1433
|
-
],
|
|
1434
|
-
type: { kind: "object", className: "%TypeError" },
|
|
1435
|
-
loc,
|
|
1436
|
-
},
|
|
1437
|
-
loc,
|
|
1438
|
-
},
|
|
1439
|
-
],
|
|
1440
|
-
else_: null,
|
|
1441
|
-
loc,
|
|
1442
|
-
},
|
|
1443
|
-
{
|
|
1444
|
-
kind: "varDecl",
|
|
1445
|
-
localId: "acc.0",
|
|
1446
|
-
init: at(right ? { kind: "bin", op: "-", left: n, right: numLit(1, loc), type: F64, loc } : numLit(0, loc)),
|
|
1447
|
-
loc,
|
|
1448
|
-
},
|
|
1449
|
-
];
|
|
1450
|
-
// Loop bounds: with init the walk covers every index; the seeded form
|
|
1451
|
-
// starts one past the seed. reduce ascends, reduceRight descends.
|
|
1827
|
+
{ id: "i.0", name: "i", type: F64, mutable: true },
|
|
1828
|
+
];
|
|
1829
|
+
const params = [
|
|
1830
|
+
{ localId: "a.0", name: "a", type: arrT },
|
|
1831
|
+
{ localId: "f.0", name: "f", type: fnT },
|
|
1832
|
+
...(hasInit ? [{ localId: "z.0", name: "z", type: accT }] : []),
|
|
1833
|
+
];
|
|
1452
1834
|
const start = right
|
|
1453
|
-
? { kind: "bin", op: "-", left: n, right: numLit(
|
|
1454
|
-
: numLit(
|
|
1835
|
+
? { kind: "bin", op: "-", left: n, right: numLit(1, loc), type: F64, loc }
|
|
1836
|
+
: numLit(0, loc);
|
|
1837
|
+
const inRange = right
|
|
1838
|
+
? { kind: "bin", op: ">=", left: i, right: numLit(0, loc), type: BOOL, loc }
|
|
1839
|
+
: { kind: "bin", op: "<", left: i, right: n, type: BOOL, loc };
|
|
1840
|
+
const advance = { kind: "assign", localId: "i.0", value: { kind: "bin", op: right ? "-" : "+", left: i, right: numLit(1, loc), type: F64, loc }, loc };
|
|
1841
|
+
const seedStmts = [
|
|
1842
|
+
{ kind: "varDecl", localId: "i.0", init: start, loc },
|
|
1843
|
+
...(hasInit
|
|
1844
|
+
? [{ kind: "varDecl", localId: "acc.0", init: varRef("z.0", accT, loc), loc }]
|
|
1845
|
+
: [
|
|
1846
|
+
{ kind: "while", cond: { kind: "ternary", cond: inRange, then: { kind: "unary", op: "!", operand: getHasExpr(arrT, loc), type: BOOL, loc }, else_: boolLit(false, loc), type: BOOL, loc }, body: [advance], loc },
|
|
1847
|
+
{ kind: "if", cond: { kind: "unary", op: "!", operand: inRange, type: BOOL, loc }, then: [{ kind: "throw", value: { kind: "libCall", fn: "error.new", args: [{ kind: "strLit", value: "Reduce of empty array with no initial value", type: STRING, loc }], type: { kind: "object", className: "%TypeError" }, loc }, loc }], else_: null, loc },
|
|
1848
|
+
{ kind: "varDecl", localId: "acc.0", init: lowerer.coerceToExpected(at(i), accT), loc },
|
|
1849
|
+
advance,
|
|
1850
|
+
]),
|
|
1851
|
+
];
|
|
1455
1852
|
const loop = {
|
|
1456
1853
|
kind: "for",
|
|
1457
|
-
init:
|
|
1854
|
+
init: null,
|
|
1458
1855
|
cond: right
|
|
1459
1856
|
? { kind: "bin", op: ">=", left: i, right: numLit(0, loc), type: BOOL, loc }
|
|
1460
1857
|
: { kind: "bin", op: "<", left: i, right: n, type: BOOL, loc },
|
|
@@ -1464,20 +1861,20 @@ function reduceHelper(lowerer, method, elem, accT, arity, hasInit, loc) {
|
|
|
1464
1861
|
value: { kind: "bin", op: right ? "-" : "+", left: i, right: numLit(1, loc), type: F64, loc },
|
|
1465
1862
|
loc,
|
|
1466
1863
|
},
|
|
1467
|
-
body: [
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1864
|
+
body: [{ kind: "if", cond: getHasExpr(arrT, loc), then: [
|
|
1865
|
+
{
|
|
1866
|
+
kind: "assign",
|
|
1867
|
+
localId: "acc.0",
|
|
1868
|
+
value: lowerer.coerceToExpected({
|
|
1869
|
+
kind: "callValue",
|
|
1870
|
+
callee: varRef("f.0", fnT, loc),
|
|
1871
|
+
args: [varRef("acc.0", accT, loc), at(i), i, varRef("a.0", arrT, loc)].slice(0, arity),
|
|
1872
|
+
type: fnRet,
|
|
1873
|
+
loc,
|
|
1874
|
+
}, accT),
|
|
1875
|
+
loc,
|
|
1876
|
+
},
|
|
1877
|
+
], else_: null, loc }],
|
|
1481
1878
|
loc,
|
|
1482
1879
|
};
|
|
1483
1880
|
const body = [
|
|
@@ -1493,19 +1890,20 @@ function reduceHelper(lowerer, method, elem, accT, arity, hasInit, loc) {
|
|
|
1493
1890
|
* function like the other array HOFs. sort mutates and returns the receiver;
|
|
1494
1891
|
* toSorted takes its shallow snapshot INSIDE the helper, after the receiver
|
|
1495
1892
|
* and comparator expressions have both been evaluated, then sorts and
|
|
1496
|
-
* returns that copy without touching the receiver. The
|
|
1497
|
-
*
|
|
1498
|
-
*
|
|
1499
|
-
*
|
|
1500
|
-
*
|
|
1501
|
-
*
|
|
1502
|
-
* comparator
|
|
1503
|
-
*
|
|
1504
|
-
*
|
|
1505
|
-
*
|
|
1506
|
-
*
|
|
1507
|
-
*
|
|
1508
|
-
*
|
|
1893
|
+
* returns that copy without touching the receiver. The helper uses a stable
|
|
1894
|
+
* bottom-up merge sort with an ordered-boundary check: ordered inputs use a
|
|
1895
|
+
* linear number of comparator calls, but buffer movement remains O(n log n)
|
|
1896
|
+
* even when every boundary is already ordered. During a merge, an element
|
|
1897
|
+
* moves right only while cmp(left, right) > 0, so a NaN or 0 result keeps
|
|
1898
|
+
* the left element first. Undefined values sink without reaching the user
|
|
1899
|
+
* comparator. The callback sequence differs from V8's TimSort, so exact
|
|
1900
|
+
* order and count parity are not claimed; results agree for consistent
|
|
1901
|
+
* comparators. The comparator-less form lowers for STRING elements only —
|
|
1902
|
+
* JS's default converts every element to string and compares UTF-16 units,
|
|
1903
|
+
* so the interned synthesized comparator selects the runtime's code-unit
|
|
1904
|
+
* ordering rather than scriptc's documented code-point relational
|
|
1905
|
+
* operators. For numbers that default is the notorious string sort
|
|
1906
|
+
* ([10, 9, 1] → [1, 10, 9]), deliberately fenced toward an explicit
|
|
1509
1907
|
* comparator. */
|
|
1510
1908
|
function lowerArraySortCall(lowerer, call, access, elem, arrT) {
|
|
1511
1909
|
const loc = locOf(call);
|
|
@@ -1599,169 +1997,10 @@ function defaultStringCmpHelper(lowerer, loc) {
|
|
|
1599
1997
|
});
|
|
1600
1998
|
return name;
|
|
1601
1999
|
}
|
|
1602
|
-
/** The insertion-sort loop, from existing IR nodes:
|
|
1603
|
-
*
|
|
1604
|
-
* n = a.length;
|
|
1605
|
-
* for (i = 1; i < n; i++) {
|
|
1606
|
-
* v = a[i]; j = i - 1;
|
|
1607
|
-
* while (j >= 0) {
|
|
1608
|
-
* if (CompareArrayElements(a[j], v, f) > 0) {
|
|
1609
|
-
* a[j + 1] = a[j]; j = j - 1;
|
|
1610
|
-
* } else break;
|
|
1611
|
-
* }
|
|
1612
|
-
* a[j + 1] = v;
|
|
1613
|
-
* }
|
|
1614
|
-
* return a;
|
|
1615
|
-
*/
|
|
1616
|
-
function buildArraySortFn(name, elem, arity, copyFirst, undefinedTag, loc) {
|
|
1617
|
-
const arrT = arrayOf(elem);
|
|
1618
|
-
const fnT = funcOf([elem, elem].slice(0, arity), F64);
|
|
1619
|
-
const j = varRef("j.0", F64, loc);
|
|
1620
|
-
const at = (index) => ({ kind: "arrayGet", arr: varRef("a.0", arrT, loc), index, type: elem, loc });
|
|
1621
|
-
const jPlus1 = { kind: "bin", op: "+", left: j, right: numLit(1, loc), type: F64, loc };
|
|
1622
|
-
const isUndefined = (value) => {
|
|
1623
|
-
if (elem.kind === "union" && undefinedTag !== null) {
|
|
1624
|
-
return {
|
|
1625
|
-
kind: "unionIsTag",
|
|
1626
|
-
unionId: elem.unionId,
|
|
1627
|
-
tag: undefinedTag,
|
|
1628
|
-
negated: false,
|
|
1629
|
-
value,
|
|
1630
|
-
type: BOOL,
|
|
1631
|
-
loc,
|
|
1632
|
-
};
|
|
1633
|
-
}
|
|
1634
|
-
if (elem.kind === "jsval") {
|
|
1635
|
-
return {
|
|
1636
|
-
kind: "jsOp",
|
|
1637
|
-
op: "eq",
|
|
1638
|
-
args: [
|
|
1639
|
-
value,
|
|
1640
|
-
{ kind: "jsOp", op: "undefLit", args: [], type: JSVAL, loc },
|
|
1641
|
-
],
|
|
1642
|
-
type: BOOL,
|
|
1643
|
-
loc,
|
|
1644
|
-
};
|
|
1645
|
-
}
|
|
1646
|
-
return null;
|
|
1647
|
-
};
|
|
1648
|
-
const compareGreater = {
|
|
1649
|
-
kind: "bin",
|
|
1650
|
-
op: ">",
|
|
1651
|
-
left: {
|
|
1652
|
-
kind: "callValue",
|
|
1653
|
-
callee: varRef("f.0", fnT, loc),
|
|
1654
|
-
args: [at(j), varRef("v.0", elem, loc)].slice(0, arity),
|
|
1655
|
-
type: F64,
|
|
1656
|
-
loc,
|
|
1657
|
-
},
|
|
1658
|
-
right: numLit(0, loc),
|
|
1659
|
-
type: BOOL,
|
|
1660
|
-
loc,
|
|
1661
|
-
};
|
|
1662
|
-
const leftUndefined = isUndefined(at(j));
|
|
1663
|
-
const valueUndefined = isUndefined(varRef("v.0", elem, loc));
|
|
1664
|
-
// CompareArrayElements: undefined always sinks and never reaches the
|
|
1665
|
-
// user comparator. Ternaries preserve that callback suppression.
|
|
1666
|
-
const shouldShift = leftUndefined !== null && valueUndefined !== null
|
|
1667
|
-
? {
|
|
1668
|
-
kind: "ternary",
|
|
1669
|
-
cond: leftUndefined,
|
|
1670
|
-
then: {
|
|
1671
|
-
kind: "unary",
|
|
1672
|
-
op: "!",
|
|
1673
|
-
operand: valueUndefined,
|
|
1674
|
-
type: BOOL,
|
|
1675
|
-
loc,
|
|
1676
|
-
},
|
|
1677
|
-
else_: {
|
|
1678
|
-
kind: "ternary",
|
|
1679
|
-
cond: valueUndefined,
|
|
1680
|
-
then: { kind: "boolLit", value: false, type: BOOL, loc },
|
|
1681
|
-
else_: compareGreater,
|
|
1682
|
-
type: BOOL,
|
|
1683
|
-
loc,
|
|
1684
|
-
},
|
|
1685
|
-
type: BOOL,
|
|
1686
|
-
loc,
|
|
1687
|
-
}
|
|
1688
|
-
: compareGreater;
|
|
1689
|
-
const shiftLoop = {
|
|
1690
|
-
kind: "while",
|
|
1691
|
-
cond: { kind: "bin", op: ">=", left: j, right: numLit(0, loc), type: BOOL, loc },
|
|
1692
|
-
body: [
|
|
1693
|
-
{
|
|
1694
|
-
kind: "if",
|
|
1695
|
-
cond: shouldShift,
|
|
1696
|
-
then: [
|
|
1697
|
-
{ kind: "arraySet", arr: varRef("a.0", arrT, loc), index: jPlus1, value: at(j), loc },
|
|
1698
|
-
{ kind: "assign", localId: "j.0", value: { kind: "bin", op: "-", left: j, right: numLit(1, loc), type: F64, loc }, loc },
|
|
1699
|
-
],
|
|
1700
|
-
else_: [{ kind: "break", loc }],
|
|
1701
|
-
loc,
|
|
1702
|
-
},
|
|
1703
|
-
],
|
|
1704
|
-
loc,
|
|
1705
|
-
};
|
|
1706
|
-
const body = [
|
|
1707
|
-
...(copyFirst
|
|
1708
|
-
? [{
|
|
1709
|
-
kind: "assign",
|
|
1710
|
-
localId: "a.0",
|
|
1711
|
-
value: {
|
|
1712
|
-
kind: "arrIntrinsic",
|
|
1713
|
-
method: "slice",
|
|
1714
|
-
receiver: varRef("a.0", arrT, loc),
|
|
1715
|
-
args: [],
|
|
1716
|
-
type: arrT,
|
|
1717
|
-
loc,
|
|
1718
|
-
},
|
|
1719
|
-
loc,
|
|
1720
|
-
}]
|
|
1721
|
-
: []),
|
|
1722
|
-
readLenStmt(arrT, loc),
|
|
1723
|
-
countedFor(loc, { kind: "bin", op: "-", left: varRef("n.0", F64, loc), right: numLit(1, loc), type: F64, loc }, (index) => [
|
|
1724
|
-
{
|
|
1725
|
-
kind: "varDecl",
|
|
1726
|
-
localId: "v.0",
|
|
1727
|
-
init: {
|
|
1728
|
-
kind: "arrayGet",
|
|
1729
|
-
arr: varRef("a.0", arrT, loc),
|
|
1730
|
-
index: { kind: "bin", op: "+", left: index, right: numLit(1, loc), type: F64, loc },
|
|
1731
|
-
type: elem,
|
|
1732
|
-
loc,
|
|
1733
|
-
},
|
|
1734
|
-
loc,
|
|
1735
|
-
},
|
|
1736
|
-
{ kind: "varDecl", localId: "j.0", init: index, loc },
|
|
1737
|
-
shiftLoop,
|
|
1738
|
-
{ kind: "arraySet", arr: varRef("a.0", arrT, loc), index: jPlus1, value: varRef("v.0", elem, loc), loc },
|
|
1739
|
-
]),
|
|
1740
|
-
{ kind: "return", value: varRef("a.0", arrT, loc), loc },
|
|
1741
|
-
];
|
|
1742
|
-
return {
|
|
1743
|
-
name,
|
|
1744
|
-
params: [
|
|
1745
|
-
{ localId: "a.0", name: "a", type: arrT },
|
|
1746
|
-
{ localId: "f.0", name: "f", type: fnT },
|
|
1747
|
-
],
|
|
1748
|
-
returnType: arrT,
|
|
1749
|
-
locals: [
|
|
1750
|
-
{ id: "a.0", name: "a", type: arrT, mutable: true },
|
|
1751
|
-
{ id: "f.0", name: "f", type: fnT, mutable: true },
|
|
1752
|
-
{ id: "n.0", name: "n", type: F64, mutable: false },
|
|
1753
|
-
{ id: "i.0", name: "i", type: F64, mutable: true },
|
|
1754
|
-
{ id: "v.0", name: "v", type: elem, mutable: false },
|
|
1755
|
-
{ id: "j.0", name: "j", type: F64, mutable: true },
|
|
1756
|
-
],
|
|
1757
|
-
body,
|
|
1758
|
-
loc,
|
|
1759
|
-
};
|
|
1760
|
-
}
|
|
1761
2000
|
/** Uint8Array.prototype.toSorted. The receiver/comparator expressions are
|
|
1762
2001
|
* evaluated before entering the helper; the helper snapshots with
|
|
1763
2002
|
* TypedArray.prototype.slice before its first comparison, then performs
|
|
1764
|
-
* the same stable
|
|
2003
|
+
* the same stable merge walk as Array.toSorted. Uint8Array's default
|
|
1765
2004
|
* comparator is numeric ascending, so its comparator-less form needs no
|
|
1766
2005
|
* string-conversion machinery. */
|
|
1767
2006
|
function lowerBytesToSortedCall(lowerer, call, access, bytesT) {
|
|
@@ -1835,205 +2074,6 @@ function lowerBytesToSortedCall(lowerer, call, access, bytesT) {
|
|
|
1835
2074
|
loc,
|
|
1836
2075
|
};
|
|
1837
2076
|
}
|
|
1838
|
-
function buildBytesSortFn(name, arity, hasComparator, loc) {
|
|
1839
|
-
const bytesT = BYTES_U8;
|
|
1840
|
-
const fnT = funcOf([F64, F64].slice(0, arity), F64);
|
|
1841
|
-
const j = varRef("j.0", F64, loc);
|
|
1842
|
-
const at = (index) => ({
|
|
1843
|
-
kind: "bytesIntrinsic",
|
|
1844
|
-
method: "get",
|
|
1845
|
-
receiver: varRef("a.0", bytesT, loc),
|
|
1846
|
-
args: [index],
|
|
1847
|
-
type: F64,
|
|
1848
|
-
loc,
|
|
1849
|
-
});
|
|
1850
|
-
const jPlus1 = {
|
|
1851
|
-
kind: "bin",
|
|
1852
|
-
op: "+",
|
|
1853
|
-
left: j,
|
|
1854
|
-
right: numLit(1, loc),
|
|
1855
|
-
type: F64,
|
|
1856
|
-
loc,
|
|
1857
|
-
};
|
|
1858
|
-
const compare = hasComparator
|
|
1859
|
-
? {
|
|
1860
|
-
kind: "callValue",
|
|
1861
|
-
callee: varRef("f.0", fnT, loc),
|
|
1862
|
-
args: [at(j), varRef("v.0", F64, loc)].slice(0, arity),
|
|
1863
|
-
type: F64,
|
|
1864
|
-
loc,
|
|
1865
|
-
}
|
|
1866
|
-
: {
|
|
1867
|
-
kind: "bin",
|
|
1868
|
-
op: "-",
|
|
1869
|
-
left: at(j),
|
|
1870
|
-
right: varRef("v.0", F64, loc),
|
|
1871
|
-
type: F64,
|
|
1872
|
-
loc,
|
|
1873
|
-
};
|
|
1874
|
-
const shiftLoop = {
|
|
1875
|
-
kind: "while",
|
|
1876
|
-
cond: {
|
|
1877
|
-
kind: "bin",
|
|
1878
|
-
op: ">=",
|
|
1879
|
-
left: j,
|
|
1880
|
-
right: numLit(0, loc),
|
|
1881
|
-
type: BOOL,
|
|
1882
|
-
loc,
|
|
1883
|
-
},
|
|
1884
|
-
body: [
|
|
1885
|
-
{
|
|
1886
|
-
kind: "if",
|
|
1887
|
-
cond: {
|
|
1888
|
-
kind: "bin",
|
|
1889
|
-
op: ">",
|
|
1890
|
-
left: compare,
|
|
1891
|
-
right: numLit(0, loc),
|
|
1892
|
-
type: BOOL,
|
|
1893
|
-
loc,
|
|
1894
|
-
},
|
|
1895
|
-
then: [
|
|
1896
|
-
{
|
|
1897
|
-
kind: "bytesSet",
|
|
1898
|
-
arr: varRef("a.0", bytesT, loc),
|
|
1899
|
-
index: jPlus1,
|
|
1900
|
-
value: at(j),
|
|
1901
|
-
loc,
|
|
1902
|
-
},
|
|
1903
|
-
{
|
|
1904
|
-
kind: "assign",
|
|
1905
|
-
localId: "j.0",
|
|
1906
|
-
value: {
|
|
1907
|
-
kind: "bin",
|
|
1908
|
-
op: "-",
|
|
1909
|
-
left: j,
|
|
1910
|
-
right: numLit(1, loc),
|
|
1911
|
-
type: F64,
|
|
1912
|
-
loc,
|
|
1913
|
-
},
|
|
1914
|
-
loc,
|
|
1915
|
-
},
|
|
1916
|
-
],
|
|
1917
|
-
else_: [{ kind: "break", loc }],
|
|
1918
|
-
loc,
|
|
1919
|
-
},
|
|
1920
|
-
],
|
|
1921
|
-
loc,
|
|
1922
|
-
};
|
|
1923
|
-
const params = [
|
|
1924
|
-
{ localId: "a.0", name: "a", type: bytesT },
|
|
1925
|
-
...(hasComparator
|
|
1926
|
-
? [{ localId: "f.0", name: "f", type: fnT }]
|
|
1927
|
-
: []),
|
|
1928
|
-
];
|
|
1929
|
-
const locals = [
|
|
1930
|
-
{ id: "a.0", name: "a", type: bytesT, mutable: true },
|
|
1931
|
-
...(hasComparator
|
|
1932
|
-
? [{ id: "f.0", name: "f", type: fnT, mutable: true }]
|
|
1933
|
-
: []),
|
|
1934
|
-
{ id: "n.0", name: "n", type: F64, mutable: false },
|
|
1935
|
-
{ id: "i.0", name: "i", type: F64, mutable: true },
|
|
1936
|
-
{ id: "v.0", name: "v", type: F64, mutable: false },
|
|
1937
|
-
{ id: "j.0", name: "j", type: F64, mutable: true },
|
|
1938
|
-
];
|
|
1939
|
-
const body = [
|
|
1940
|
-
{
|
|
1941
|
-
kind: "assign",
|
|
1942
|
-
localId: "a.0",
|
|
1943
|
-
value: {
|
|
1944
|
-
kind: "bytesIntrinsic",
|
|
1945
|
-
method: "slice",
|
|
1946
|
-
receiver: varRef("a.0", bytesT, loc),
|
|
1947
|
-
args: [],
|
|
1948
|
-
type: bytesT,
|
|
1949
|
-
loc,
|
|
1950
|
-
},
|
|
1951
|
-
loc,
|
|
1952
|
-
},
|
|
1953
|
-
{
|
|
1954
|
-
kind: "varDecl",
|
|
1955
|
-
localId: "n.0",
|
|
1956
|
-
init: {
|
|
1957
|
-
kind: "bytesIntrinsic",
|
|
1958
|
-
method: "length",
|
|
1959
|
-
receiver: varRef("a.0", bytesT, loc),
|
|
1960
|
-
args: [],
|
|
1961
|
-
type: F64,
|
|
1962
|
-
loc,
|
|
1963
|
-
},
|
|
1964
|
-
loc,
|
|
1965
|
-
},
|
|
1966
|
-
{
|
|
1967
|
-
kind: "for",
|
|
1968
|
-
init: {
|
|
1969
|
-
kind: "varDecl",
|
|
1970
|
-
localId: "i.0",
|
|
1971
|
-
init: numLit(1, loc),
|
|
1972
|
-
loc,
|
|
1973
|
-
},
|
|
1974
|
-
cond: {
|
|
1975
|
-
kind: "bin",
|
|
1976
|
-
op: "<",
|
|
1977
|
-
left: varRef("i.0", F64, loc),
|
|
1978
|
-
right: varRef("n.0", F64, loc),
|
|
1979
|
-
type: BOOL,
|
|
1980
|
-
loc,
|
|
1981
|
-
},
|
|
1982
|
-
update: {
|
|
1983
|
-
kind: "assign",
|
|
1984
|
-
localId: "i.0",
|
|
1985
|
-
value: {
|
|
1986
|
-
kind: "bin",
|
|
1987
|
-
op: "+",
|
|
1988
|
-
left: varRef("i.0", F64, loc),
|
|
1989
|
-
right: numLit(1, loc),
|
|
1990
|
-
type: F64,
|
|
1991
|
-
loc,
|
|
1992
|
-
},
|
|
1993
|
-
loc,
|
|
1994
|
-
},
|
|
1995
|
-
body: [
|
|
1996
|
-
{
|
|
1997
|
-
kind: "varDecl",
|
|
1998
|
-
localId: "v.0",
|
|
1999
|
-
init: at(varRef("i.0", F64, loc)),
|
|
2000
|
-
loc,
|
|
2001
|
-
},
|
|
2002
|
-
{
|
|
2003
|
-
kind: "varDecl",
|
|
2004
|
-
localId: "j.0",
|
|
2005
|
-
init: {
|
|
2006
|
-
kind: "bin",
|
|
2007
|
-
op: "-",
|
|
2008
|
-
left: varRef("i.0", F64, loc),
|
|
2009
|
-
right: numLit(1, loc),
|
|
2010
|
-
type: F64,
|
|
2011
|
-
loc,
|
|
2012
|
-
},
|
|
2013
|
-
loc,
|
|
2014
|
-
},
|
|
2015
|
-
shiftLoop,
|
|
2016
|
-
{
|
|
2017
|
-
kind: "bytesSet",
|
|
2018
|
-
arr: varRef("a.0", bytesT, loc),
|
|
2019
|
-
index: jPlus1,
|
|
2020
|
-
value: varRef("v.0", F64, loc),
|
|
2021
|
-
loc,
|
|
2022
|
-
},
|
|
2023
|
-
],
|
|
2024
|
-
loc,
|
|
2025
|
-
},
|
|
2026
|
-
{ kind: "return", value: varRef("a.0", bytesT, loc), loc },
|
|
2027
|
-
];
|
|
2028
|
-
return {
|
|
2029
|
-
name,
|
|
2030
|
-
params,
|
|
2031
|
-
returnType: bytesT,
|
|
2032
|
-
locals,
|
|
2033
|
-
body,
|
|
2034
|
-
loc,
|
|
2035
|
-
};
|
|
2036
|
-
}
|
|
2037
2077
|
/** `n = a.length` — the once-up-front length read every array HOF loop
|
|
2038
2078
|
* starts with (locals a.0/n.0 by convention). */
|
|
2039
2079
|
function readLenStmt(arrT, loc) {
|
|
@@ -2111,14 +2151,6 @@ function lowerArrayAtCall(lowerer, call, access, elem) {
|
|
|
2111
2151
|
const undefTag = lowerer.armTag(resultT.unionId, UNDEFINED_T);
|
|
2112
2152
|
if (undefTag < 0)
|
|
2113
2153
|
lowerer.badType(call, lowerer.typeOf(call));
|
|
2114
|
-
let foundTag = null;
|
|
2115
|
-
if (!typeEquals(resultT, elem)) {
|
|
2116
|
-
foundTag = lowerer.armTag(resultT.unionId, elem);
|
|
2117
|
-
if (foundTag < 0) {
|
|
2118
|
-
lowerer.unsupported("SC1090", call, `'.at' on '${lowerer.fmt(elem)}'-element arrays (the element would need a union ` +
|
|
2119
|
-
"re-tag that is not supported yet — index and test instead)");
|
|
2120
|
-
}
|
|
2121
|
-
}
|
|
2122
2154
|
const key = `at:${typeKey(elem)}:${typeKey(resultT)}`;
|
|
2123
2155
|
let name = lowerer.arrHofHelpers.get(key);
|
|
2124
2156
|
if (!name) {
|
|
@@ -2128,10 +2160,7 @@ function lowerArrayAtCall(lowerer, call, access, elem) {
|
|
|
2128
2160
|
const t = varRef("t.0", F64, loc);
|
|
2129
2161
|
const i = varRef("i.0", F64, loc);
|
|
2130
2162
|
const n = varRef("n.0", F64, loc);
|
|
2131
|
-
const
|
|
2132
|
-
const found = foundTag === null
|
|
2133
|
-
? v
|
|
2134
|
-
: { kind: "unionWrap", unionId: resultT.unionId, tag: foundTag, value: v, type: resultT, loc };
|
|
2163
|
+
const found = lowerer.coerceToExpected(arrayValueRead(lowerer, varRef("a.0", arrT, loc), t, elem, loc), resultT);
|
|
2135
2164
|
const miss = {
|
|
2136
2165
|
kind: "unionWrap",
|
|
2137
2166
|
unionId: resultT.unionId,
|
|
@@ -2183,12 +2212,6 @@ function lowerArrayAtCall(lowerer, call, access, elem) {
|
|
|
2183
2212
|
else_: null,
|
|
2184
2213
|
loc,
|
|
2185
2214
|
},
|
|
2186
|
-
{
|
|
2187
|
-
kind: "varDecl",
|
|
2188
|
-
localId: "v.0",
|
|
2189
|
-
init: { kind: "arrayGet", arr: varRef("a.0", arrT, loc), index: t, type: elem, loc },
|
|
2190
|
-
loc,
|
|
2191
|
-
},
|
|
2192
2215
|
{ kind: "return", value: found, loc },
|
|
2193
2216
|
];
|
|
2194
2217
|
lowerer.liftedFns.push({
|
|
@@ -2203,7 +2226,6 @@ function lowerArrayAtCall(lowerer, call, access, elem) {
|
|
|
2203
2226
|
{ id: "i.0", name: "i", type: F64, mutable: true },
|
|
2204
2227
|
{ id: "n.0", name: "n", type: F64, mutable: false },
|
|
2205
2228
|
{ id: "t.0", name: "t", type: F64, mutable: true },
|
|
2206
|
-
{ id: "v.0", name: "v", type: elem, mutable: false },
|
|
2207
2229
|
],
|
|
2208
2230
|
body,
|
|
2209
2231
|
loc,
|
|
@@ -3906,7 +3928,7 @@ export function lowerForOfSearchParams(lowerer, stmt, iterable, proj) {
|
|
|
3906
3928
|
export function lowerForOfArrayIter(lowerer, stmt, iterable, proj) {
|
|
3907
3929
|
const loc = locOf(stmt);
|
|
3908
3930
|
const arrT = iterable.type;
|
|
3909
|
-
const elemT = arrT.kind === "array" ? arrT.elem : F64;
|
|
3931
|
+
const elemT = arrT.kind === "array" ? arrayValueType(lowerer, arrT.elem) : F64;
|
|
3910
3932
|
const yieldsPair = proj === "entries";
|
|
3911
3933
|
if (!ts.isVariableDeclarationList(stmt.initializer)) {
|
|
3912
3934
|
lowerer.unsupported("SC1090", stmt.initializer, "for-of over a pre-declared variable (declare the loop variable in the loop: for (const x of ...))");
|
|
@@ -3925,13 +3947,7 @@ export function lowerForOfArrayIter(lowerer, stmt, iterable, proj) {
|
|
|
3925
3947
|
i.mutable = true;
|
|
3926
3948
|
const keyRead = () => varRef(i.id, F64, loc);
|
|
3927
3949
|
const valRead = () => arrT.kind === "array"
|
|
3928
|
-
?
|
|
3929
|
-
kind: "arrayGet",
|
|
3930
|
-
arr: varRef(arr.id, arrT, loc),
|
|
3931
|
-
index: varRef(i.id, F64, loc),
|
|
3932
|
-
type: elemT,
|
|
3933
|
-
loc,
|
|
3934
|
-
}
|
|
3950
|
+
? arrayValueRead(lowerer, varRef(arr.id, arrT, loc), varRef(i.id, F64, loc), arrT.elem, loc)
|
|
3935
3951
|
: {
|
|
3936
3952
|
kind: "bytesIntrinsic",
|
|
3937
3953
|
method: "get",
|
|
@@ -3954,6 +3970,11 @@ export function lowerForOfArrayIter(lowerer, stmt, iterable, proj) {
|
|
|
3954
3970
|
binds.push({ kind: "varDecl", localId: kLocal.id, init: keyRead(), loc });
|
|
3955
3971
|
if (els[1]) {
|
|
3956
3972
|
const vLocal = lowerer.declareLocal(els[1].name, els[1].name.text, elemT, isLet);
|
|
3973
|
+
if (arrT.kind === "array" && !typeEquals(elemT, arrT.elem)) {
|
|
3974
|
+
const root = lowerer.runtimeOptionalRootOf(vLocal);
|
|
3975
|
+
lowerer.runtimeOptionalLocals.add(root);
|
|
3976
|
+
lowerer.runtimeOptionalStorageLocals.add(root);
|
|
3977
|
+
}
|
|
3957
3978
|
binds.push({ kind: "varDecl", localId: vLocal.id, init: valRead(), loc });
|
|
3958
3979
|
}
|
|
3959
3980
|
}
|
|
@@ -3964,6 +3985,13 @@ export function lowerForOfArrayIter(lowerer, stmt, iterable, proj) {
|
|
|
3964
3985
|
const declT = lowerer.mapTypeOf(lowerer.checker.getTypeAtLocation(decl.name));
|
|
3965
3986
|
if (yieldsPair) {
|
|
3966
3987
|
const shape = declT?.kind === "record" ? lowerer.shapes.get(declT.shapeId) : null;
|
|
3988
|
+
if (declT?.kind === "record" && shape?.tuple && arrT.kind === "array") {
|
|
3989
|
+
const valueField = shape.fields.find((field) => field.name === "1");
|
|
3990
|
+
if (valueField && typeEquals(valueField.type, arrT.elem) && !typeEquals(elemT, arrT.elem)) {
|
|
3991
|
+
valueField.type = elemT;
|
|
3992
|
+
lowerer.runtimeOptionalFields.add(lowerer.runtimeOptionalFieldKey(declT.shapeId, "1"));
|
|
3993
|
+
}
|
|
3994
|
+
}
|
|
3967
3995
|
if (declT?.kind !== "record" || !shape?.tuple || shape.fields.length !== 2 ||
|
|
3968
3996
|
shape.fields.find((f) => f.name === "0")?.type.kind !== "f64" ||
|
|
3969
3997
|
!typeEquals(shape.fields.find((f) => f.name === "1")?.type ?? F64, elemT)) {
|
|
@@ -4391,7 +4419,15 @@ export function lowerRegexMethodCall(lowerer, call, access, dynReceiver) {
|
|
|
4391
4419
|
return null;
|
|
4392
4420
|
const name = access.name.text;
|
|
4393
4421
|
const receiverKind = dynReceiver ? "string" : lowerer.mapTypeOf(lowerer.typeOf(access.expression))?.kind;
|
|
4394
|
-
const lowerReceiver = () =>
|
|
4422
|
+
const lowerReceiver = () => {
|
|
4423
|
+
if (dynReceiver)
|
|
4424
|
+
return dynReceiver();
|
|
4425
|
+
if (receiverKind === "string")
|
|
4426
|
+
return lowerMethodReceiver(lowerer, access.expression, STRING, access.name.text);
|
|
4427
|
+
if (receiverKind === "regex")
|
|
4428
|
+
return lowerMethodReceiver(lowerer, access.expression, { kind: "regex" }, access.name.text);
|
|
4429
|
+
return lowerer.lowerExpr(access.expression);
|
|
4430
|
+
};
|
|
4395
4431
|
const loc = locOf(call);
|
|
4396
4432
|
if (receiverKind === "regex" && name === "test") {
|
|
4397
4433
|
// The statefulness fence, at compile time where the flags are
|
|
@@ -4406,7 +4442,7 @@ export function lowerRegexMethodCall(lowerer, call, access, dynReceiver) {
|
|
|
4406
4442
|
lowerer.unsupported("SC1121", call);
|
|
4407
4443
|
}
|
|
4408
4444
|
}
|
|
4409
|
-
const receiver =
|
|
4445
|
+
const receiver = lowerReceiver();
|
|
4410
4446
|
const args = call.arguments.map((a) => lowerer.lowerExpr(a));
|
|
4411
4447
|
return { kind: "regexIntrinsic", method: "test", receiver, args, type: BOOL, loc };
|
|
4412
4448
|
}
|
|
@@ -4429,7 +4465,7 @@ export function lowerRegexMethodCall(lowerer, call, access, dynReceiver) {
|
|
|
4429
4465
|
lowerer.unsupported("SC1121", call);
|
|
4430
4466
|
}
|
|
4431
4467
|
}
|
|
4432
|
-
const re =
|
|
4468
|
+
const re = lowerReceiver();
|
|
4433
4469
|
const subject = lowerer.lowerExprExpecting(call.arguments[0], STRING);
|
|
4434
4470
|
const resultT = { kind: "union", unionId: lowerer.unions.intern([arrayOf(STRING), { kind: "nullT" }]) };
|
|
4435
4471
|
return { kind: "regexIntrinsic", method: "match", receiver: subject, args: [re], type: resultT, loc };
|
|
@@ -4473,7 +4509,9 @@ export function lowerRegexMethodCall(lowerer, call, access, dynReceiver) {
|
|
|
4473
4509
|
lowerer.unsupported("SC1120", call, "'.match()' with the 'g' or 'y' flag (an every-match array is a different shape — use replaceAll/split, or test() per position)");
|
|
4474
4510
|
}
|
|
4475
4511
|
}
|
|
4476
|
-
const receiver = nullableStringRecv
|
|
4512
|
+
const receiver = nullableStringRecv
|
|
4513
|
+
? lowerMethodReceiver(lowerer, access.expression, STRING, access.name.text)
|
|
4514
|
+
: lowerReceiver();
|
|
4477
4515
|
const re = lowerer.lowerExpr(arg0);
|
|
4478
4516
|
// RegExpMatchArray | null maps to the string[] | null union by
|
|
4479
4517
|
// itself; intern it directly when the checker's spelling doesn't —
|
|
@@ -4587,7 +4625,9 @@ export function lowerStringMethodCall(lowerer, call, access, dynReceiver) {
|
|
|
4587
4625
|
if (call.arguments.length < entry.minArgs || call.arguments.length > entry.maxArgs) {
|
|
4588
4626
|
lowerer.noLowering(`.${access.name.text} with ${call.arguments.length} argument${call.arguments.length === 1 ? "" : "s"} on strings`, call);
|
|
4589
4627
|
}
|
|
4590
|
-
const receiver = dynReceiver
|
|
4628
|
+
const receiver = dynReceiver
|
|
4629
|
+
? dynReceiver()
|
|
4630
|
+
: lowerMethodReceiver(lowerer, access.expression, STRING, access.name.text);
|
|
4591
4631
|
const args = entry.method === "split"
|
|
4592
4632
|
? [lowerer.lowerExpr(call.arguments[0]), lowerSplitLimitArg(lowerer, call.arguments[1], locOf(call))]
|
|
4593
4633
|
: call.arguments.map((a) => lowerer.lowerExpr(a));
|
|
@@ -4630,7 +4670,7 @@ function lowerLocaleCompareCall(lowerer, call, access) {
|
|
|
4630
4670
|
lowerer.noLowering(`.localeCompare with ${call.arguments.length} arguments`, call, "the locales/options parameters select ICU collations that have no lowering — " +
|
|
4631
4671
|
"pass exactly the comparison string");
|
|
4632
4672
|
}
|
|
4633
|
-
const receiver = lowerer.
|
|
4673
|
+
const receiver = lowerMethodReceiver(lowerer, access.expression, STRING, access.name.text);
|
|
4634
4674
|
const arg = lowerer.lowerExpr(call.arguments[0]);
|
|
4635
4675
|
if (arg.type.kind !== "string")
|
|
4636
4676
|
lowerer.badType(call.arguments[0], lowerer.typeOf(call.arguments[0]));
|
|
@@ -4752,6 +4792,25 @@ export function lowerBytesNew(lowerer, expr, symbol) {
|
|
|
4752
4792
|
return { kind: "bytesNew", source: count, type, loc };
|
|
4753
4793
|
}
|
|
4754
4794
|
const src = lowerer.lowerExpr(argNode);
|
|
4795
|
+
if (src.type.kind === "union" && lowerer.armTag(src.type.unionId, UNDEFINED_T) >= 0) {
|
|
4796
|
+
const present = lowerer.stripUndefinedArm(src.type);
|
|
4797
|
+
if (present.kind === "array" && present.elem.kind === "f64") {
|
|
4798
|
+
const undefTag = lowerer.armTag(src.type.unionId, UNDEFINED_T);
|
|
4799
|
+
const presentTag = lowerer.armTag(src.type.unionId, present);
|
|
4800
|
+
if (undefTag >= 0 && presentTag >= 0) {
|
|
4801
|
+
const empty = { kind: "arrayLit", elems: [], type: present, loc };
|
|
4802
|
+
const source = {
|
|
4803
|
+
kind: "ternary",
|
|
4804
|
+
cond: { kind: "unionIsTag", unionId: src.type.unionId, tag: undefTag, negated: false, value: src, type: BOOL, loc },
|
|
4805
|
+
then: empty,
|
|
4806
|
+
else_: { kind: "unionNarrow", unionId: src.type.unionId, tag: presentTag, value: src, type: present, loc },
|
|
4807
|
+
type: present,
|
|
4808
|
+
loc,
|
|
4809
|
+
};
|
|
4810
|
+
return { kind: "bytesNew", source, type, loc };
|
|
4811
|
+
}
|
|
4812
|
+
}
|
|
4813
|
+
}
|
|
4755
4814
|
if (src.type.kind === "f64" ||
|
|
4756
4815
|
typeEquals(src.type, type) ||
|
|
4757
4816
|
(src.type.kind === "array" && src.type.elem.kind === "f64")) {
|
|
@@ -5132,6 +5191,20 @@ const BUF_ENCODINGS = {
|
|
|
5132
5191
|
export function knownBufEncoding(name) {
|
|
5133
5192
|
return own(BUF_ENCODINGS, name);
|
|
5134
5193
|
}
|
|
5194
|
+
/** Presence guard for a synthetic native-array loop. HOFs skip holes, while
|
|
5195
|
+
* the ordinary `arrayGet` remains a proven-presence operation. */
|
|
5196
|
+
function arrayHasExpr(arr, index, loc) {
|
|
5197
|
+
return {
|
|
5198
|
+
kind: "arrayHas",
|
|
5199
|
+
arr,
|
|
5200
|
+
index,
|
|
5201
|
+
type: BOOL,
|
|
5202
|
+
loc,
|
|
5203
|
+
};
|
|
5204
|
+
}
|
|
5205
|
+
function getHasExpr(arrT, loc) {
|
|
5206
|
+
return arrayHasExpr({ kind: "varRef", localId: "a.0", type: arrT, loc }, { kind: "varRef", localId: "i.0", type: F64, loc }, loc);
|
|
5207
|
+
}
|
|
5135
5208
|
/** The literal encoding argument of a Buffer surface, normalized — or a
|
|
5136
5209
|
* fence when it isn't a literal alias Node knows. */
|
|
5137
5210
|
export function bufEncoding(lowerer, what, encNode) {
|