@scriptc/compiler 0.0.36 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ambient/scriptc-node-fallback.d.ts +52 -22
- package/ambient/scriptc-overrides.d.ts +0 -7
- package/ambient/scriptc.d.ts +3 -2
- package/dist/backend/build-cache.js.map +1 -1
- package/dist/backend/c/async.js +24 -2
- package/dist/backend/c/async.js.map +1 -1
- package/dist/backend/c/c-emitter.d.ts +22 -10
- package/dist/backend/c/c-emitter.js +60 -15
- package/dist/backend/c/c-emitter.js.map +1 -1
- package/dist/backend/c/exprs.js +289 -55
- package/dist/backend/c/exprs.js.map +1 -1
- package/dist/backend/c/island.js.map +1 -1
- package/dist/backend/c/may-throw.js +3 -0
- package/dist/backend/c/may-throw.js.map +1 -1
- package/dist/backend/c/stmts.d.ts +3 -5
- package/dist/backend/c/stmts.js +76 -17
- package/dist/backend/c/stmts.js.map +1 -1
- package/dist/backend/c/types.js.map +1 -1
- package/dist/backend/c/walkers.js +36 -12
- package/dist/backend/c/walkers.js.map +1 -1
- package/dist/backend/external-c.d.ts +33 -0
- package/dist/backend/external-c.js +51 -0
- package/dist/backend/external-c.js.map +1 -0
- package/dist/backend/link-plan.d.ts +37 -0
- package/dist/backend/link-plan.js +26 -0
- package/dist/backend/link-plan.js.map +1 -0
- package/dist/backend/linker.d.ts +22 -0
- package/dist/backend/linker.js +236 -0
- package/dist/backend/linker.js.map +1 -0
- package/dist/backend/llvm/blocks.js.map +1 -1
- package/dist/backend/llvm/dyn.js +63 -3
- package/dist/backend/llvm/dyn.js.map +1 -1
- package/dist/backend/llvm/emitter.js +132 -26
- package/dist/backend/llvm/emitter.js.map +1 -1
- package/dist/backend/llvm/expr-async.js +85 -3
- package/dist/backend/llvm/expr-async.js.map +1 -1
- package/dist/backend/llvm/expr-bytes.js.map +1 -1
- package/dist/backend/llvm/expr-calls.js.map +1 -1
- package/dist/backend/llvm/expr-containers.js +68 -54
- package/dist/backend/llvm/expr-containers.js.map +1 -1
- package/dist/backend/llvm/expr-context.d.ts +2 -1
- package/dist/backend/llvm/expr-control.js.map +1 -1
- package/dist/backend/llvm/expr-dispatch.js +2 -0
- package/dist/backend/llvm/expr-dispatch.js.map +1 -1
- package/dist/backend/llvm/expr-dynamic.js +6 -3
- package/dist/backend/llvm/expr-dynamic.js.map +1 -1
- package/dist/backend/llvm/expr-island.js.map +1 -1
- package/dist/backend/llvm/expr-primitives.d.ts +1 -1
- package/dist/backend/llvm/expr-primitives.js +20 -0
- package/dist/backend/llvm/expr-primitives.js.map +1 -1
- package/dist/backend/llvm/expr-records.js.map +1 -1
- package/dist/backend/llvm/expr-stream-bridges.js +245 -10
- package/dist/backend/llvm/expr-stream-bridges.js.map +1 -1
- package/dist/backend/llvm/lib-dispatch.js +2 -0
- package/dist/backend/llvm/lib-dispatch.js.map +1 -1
- package/dist/backend/llvm/lib-shared.js +18 -0
- package/dist/backend/llvm/lib-shared.js.map +1 -1
- package/dist/backend/llvm/lib-stream.js +6 -3
- package/dist/backend/llvm/lib-stream.js.map +1 -1
- package/dist/backend/llvm/split.js.map +1 -1
- package/dist/backend/llvm/unsupported.js.map +1 -1
- package/dist/backend/llvm/walkers.js +31 -4
- package/dist/backend/llvm/walkers.js.map +1 -1
- package/dist/backend/native-codegen.d.ts +8 -1
- package/dist/backend/native-codegen.js +39 -25
- package/dist/backend/native-codegen.js.map +1 -1
- package/dist/backend/native-codegen.test.js +44 -4
- package/dist/backend/native-codegen.test.js.map +1 -1
- package/dist/backend/native-link-info.d.ts +1 -1
- package/dist/backend/native-link-info.js +42 -18
- package/dist/backend/native-link-info.js.map +1 -1
- package/dist/backend/native-toolchain.js.map +1 -1
- package/dist/backend/native-toolchain.test.js +5 -1
- package/dist/backend/native-toolchain.test.js.map +1 -1
- package/dist/backend/runtime-pack-targets.test.d.ts +1 -0
- package/dist/backend/runtime-pack-targets.test.js +54 -0
- package/dist/backend/runtime-pack-targets.test.js.map +1 -0
- package/dist/backend/runtime-pack.d.ts +5 -30
- package/dist/backend/runtime-pack.js +46 -141
- package/dist/backend/runtime-pack.js.map +1 -1
- package/dist/backend/runtime-pack.test.js +49 -14
- package/dist/backend/runtime-pack.test.js.map +1 -1
- package/dist/backend/targets.d.ts +63 -17
- package/dist/backend/targets.js +251 -35
- package/dist/backend/targets.js.map +1 -1
- package/dist/backend/targets.test.js +27 -5
- package/dist/backend/targets.test.js.map +1 -1
- package/dist/backend/vendor-archives.js.map +1 -1
- package/dist/compat/fetch-profile.js +4 -2
- package/dist/compat/fetch-profile.js.map +1 -1
- package/dist/coverage/report.js.map +1 -1
- package/dist/coverage/surface-manifest.js +22 -1
- package/dist/coverage/surface-manifest.js.map +1 -1
- package/dist/diagnostics/diagnostic.d.ts +6 -5
- package/dist/diagnostics/diagnostic.js +7 -6
- package/dist/diagnostics/diagnostic.js.map +1 -1
- package/dist/diagnostics/render.js.map +1 -1
- package/dist/ffi/ffi-manifest.js.map +1 -1
- package/dist/frontend/cjs-lexer.js.map +1 -1
- package/dist/frontend/dts-paths.js.map +1 -1
- package/dist/frontend/input-tracker.d.ts +10 -0
- package/dist/frontend/input-tracker.js +26 -3
- package/dist/frontend/input-tracker.js.map +1 -1
- package/dist/frontend/input-tracker.test.js +8 -1
- package/dist/frontend/input-tracker.test.js.map +1 -1
- package/dist/frontend/lib-contract.js.map +1 -1
- package/dist/frontend/lowering/array-values.d.ts +12 -0
- package/dist/frontend/lowering/array-values.js +76 -0
- package/dist/frontend/lowering/array-values.js.map +1 -0
- package/dist/frontend/lowering/callback-arg.js.map +1 -1
- package/dist/frontend/lowering/containers/bytes.d.ts +35 -0
- package/dist/frontend/lowering/containers/bytes.js +1021 -0
- package/dist/frontend/lowering/containers/bytes.js.map +1 -0
- package/dist/frontend/lowering/containers/string-and-regexp.d.ts +17 -0
- package/dist/frontend/lowering/containers/string-and-regexp.js +345 -0
- package/dist/frontend/lowering/containers/string-and-regexp.js.map +1 -0
- package/dist/frontend/lowering/expressions/evaluation-safety.d.ts +13 -0
- package/dist/frontend/lowering/expressions/evaluation-safety.js +76 -0
- package/dist/frontend/lowering/expressions/evaluation-safety.js.map +1 -0
- package/dist/frontend/lowering/expressions/object-literals.d.ts +44 -0
- package/dist/frontend/lowering/expressions/object-literals.js +1815 -0
- package/dist/frontend/lowering/expressions/object-literals.js.map +1 -0
- package/dist/frontend/lowering/expressions/optional-chains.d.ts +31 -0
- package/dist/frontend/lowering/expressions/optional-chains.js +382 -0
- package/dist/frontend/lowering/expressions/optional-chains.js.map +1 -0
- package/dist/frontend/lowering/expressions/try-lower-expression.d.ts +7 -0
- package/dist/frontend/lowering/expressions/try-lower-expression.js +26 -0
- package/dist/frontend/lowering/expressions/try-lower-expression.js.map +1 -0
- package/dist/frontend/lowering/lib-boundary.js +7 -2
- package/dist/frontend/lowering/lib-boundary.js.map +1 -1
- package/dist/frontend/lowering/lower-array-sort.d.ts +22 -0
- package/dist/frontend/lowering/lower-array-sort.js +619 -0
- package/dist/frontend/lowering/lower-array-sort.js.map +1 -0
- package/dist/frontend/lowering/lower-assert.js.map +1 -1
- package/dist/frontend/lowering/lower-async-iteration.d.ts +7 -0
- package/dist/frontend/lowering/lower-async-iteration.js +657 -0
- package/dist/frontend/lowering/lower-async-iteration.js.map +1 -0
- package/dist/frontend/lowering/lower-builtins.d.ts +18 -0
- package/dist/frontend/lowering/lower-builtins.js +379 -48
- package/dist/frontend/lowering/lower-builtins.js.map +1 -1
- package/dist/frontend/lowering/lower-calls.d.ts +16 -8
- package/dist/frontend/lowering/lower-calls.js +667 -91
- package/dist/frontend/lowering/lower-calls.js.map +1 -1
- package/dist/frontend/lowering/lower-classes.d.ts +7 -13
- package/dist/frontend/lowering/lower-classes.js +128 -35
- package/dist/frontend/lowering/lower-classes.js.map +1 -1
- package/dist/frontend/lowering/lower-comptime.js.map +1 -1
- package/dist/frontend/lowering/lower-containers.d.ts +1 -47
- package/dist/frontend/lowering/lower-containers.js +779 -2038
- package/dist/frontend/lowering/lower-containers.js.map +1 -1
- package/dist/frontend/lowering/lower-event-emitter.js +51 -5
- package/dist/frontend/lowering/lower-event-emitter.js.map +1 -1
- package/dist/frontend/lowering/lower-exprs.d.ts +4 -78
- package/dist/frontend/lowering/lower-exprs.js +1128 -2409
- package/dist/frontend/lowering/lower-exprs.js.map +1 -1
- package/dist/frontend/lowering/lower-generators.d.ts +18 -3
- package/dist/frontend/lowering/lower-generators.js +145 -9
- package/dist/frontend/lowering/lower-generators.js.map +1 -1
- package/dist/frontend/lowering/lower-inspect.js +50 -36
- package/dist/frontend/lowering/lower-inspect.js.map +1 -1
- package/dist/frontend/lowering/lower-island.d.ts +11 -4
- package/dist/frontend/lowering/lower-island.js +47 -10
- package/dist/frontend/lowering/lower-island.js.map +1 -1
- package/dist/frontend/lowering/lower-modules.js +52 -15
- package/dist/frontend/lowering/lower-modules.js.map +1 -1
- package/dist/frontend/lowering/lower-node-module.d.ts +18 -0
- package/dist/frontend/lowering/lower-node-module.js +302 -0
- package/dist/frontend/lowering/lower-node-module.js.map +1 -0
- package/dist/frontend/lowering/lower-server.js +11 -1
- package/dist/frontend/lowering/lower-server.js.map +1 -1
- package/dist/frontend/lowering/lower-stmts.d.ts +5 -24
- package/dist/frontend/lowering/lower-stmts.js +811 -314
- package/dist/frontend/lowering/lower-stmts.js.map +1 -1
- package/dist/frontend/lowering/lower-stream.js +4 -4
- package/dist/frontend/lowering/lower-stream.js.map +1 -1
- package/dist/frontend/lowering/lowerer.d.ts +109 -34
- package/dist/frontend/lowering/lowerer.js +1225 -62
- package/dist/frontend/lowering/lowerer.js.map +1 -1
- package/dist/frontend/lowering/optional-arguments.d.ts +9 -0
- package/dist/frontend/lowering/optional-arguments.js +57 -0
- package/dist/frontend/lowering/optional-arguments.js.map +1 -0
- package/dist/frontend/lowering/surfaces.d.ts +10 -2
- package/dist/frontend/lowering/surfaces.js +15 -3
- package/dist/frontend/lowering/surfaces.js.map +1 -1
- package/dist/frontend/npm-static-declarations.d.ts +35 -0
- package/dist/frontend/npm-static-declarations.js +506 -0
- package/dist/frontend/npm-static-declarations.js.map +1 -0
- package/dist/frontend/npm-static-declarations.test.d.ts +1 -0
- package/dist/frontend/npm-static-declarations.test.js +122 -0
- package/dist/frontend/npm-static-declarations.test.js.map +1 -0
- package/dist/frontend/npm-static.d.ts +2 -0
- package/dist/frontend/npm-static.js +26 -10
- package/dist/frontend/npm-static.js.map +1 -1
- package/dist/frontend/npm.d.ts +60 -0
- package/dist/frontend/npm.js +38 -4
- package/dist/frontend/npm.js.map +1 -1
- package/dist/frontend/npm.test.d.ts +1 -0
- package/dist/frontend/npm.test.js +50 -0
- package/dist/frontend/npm.test.js.map +1 -0
- package/dist/frontend/program.d.ts +3 -2
- package/dist/frontend/program.js +495 -68
- package/dist/frontend/program.js.map +1 -1
- package/dist/frontend/resolve.d.ts +10 -2
- package/dist/frontend/resolve.js +90 -14
- package/dist/frontend/resolve.js.map +1 -1
- package/dist/frontend/resolve.test.js +4 -1
- package/dist/frontend/resolve.test.js.map +1 -1
- package/dist/frontend/runtime-resolve.d.ts +22 -0
- package/dist/frontend/runtime-resolve.js +101 -0
- package/dist/frontend/runtime-resolve.js.map +1 -0
- package/dist/frontend/runtime-resolve.test.d.ts +1 -0
- package/dist/frontend/runtime-resolve.test.js +18 -0
- package/dist/frontend/runtime-resolve.test.js.map +1 -0
- package/dist/frontend/ts7/adapter.js +12 -13
- package/dist/frontend/ts7/adapter.js.map +1 -1
- package/dist/frontend/ts7/ast.js.map +1 -1
- package/dist/frontend/ts7/census-check.js +3 -3
- package/dist/frontend/ts7/census-check.js.map +1 -1
- package/dist/frontend/ts7/checker.d.ts +3 -0
- package/dist/frontend/ts7/checker.js +12 -3
- package/dist/frontend/ts7/checker.js.map +1 -1
- package/dist/frontend/ts7/enums.js.map +1 -1
- package/dist/frontend/ts7/program-adapter.js +42 -3
- package/dist/frontend/ts7/program-adapter.js.map +1 -1
- package/dist/frontend/type-mapper.js +120 -7
- package/dist/frontend/type-mapper.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +50 -28
- package/dist/index.js.map +1 -1
- package/dist/ir/analysis.js.map +1 -1
- package/dist/ir/ir.d.ts +125 -41
- package/dist/ir/ir.js +82 -31
- package/dist/ir/ir.js.map +1 -1
- package/dist/ir/serialize.d.ts +1 -1
- package/dist/ir/serialize.js +1 -1
- package/dist/ir/validate.js +127 -95
- package/dist/ir/validate.js.map +1 -1
- package/dist/library/cache-primitives.js.map +1 -1
- package/dist/library/cache-reset.test.js.map +1 -1
- package/dist/library/compiler-self-identity.js.map +1 -1
- package/dist/library/fence-eval.js +3 -1
- package/dist/library/fence-eval.js.map +1 -1
- package/dist/library/int-infer.js +12 -1
- package/dist/library/int-infer.js.map +1 -1
- package/dist/library/int-infer.test.js +4 -4
- package/dist/library/int-infer.test.js.map +1 -1
- package/dist/library/library-cache.test.js +5 -5
- package/dist/library/library-profile.js.map +1 -1
- package/dist/library/sidecar-validate.js.map +1 -1
- package/dist/library/sidecar.js.map +1 -1
- package/dist/startup-cache.d.ts +9 -1
- package/dist/startup-cache.js +16 -1
- package/dist/startup-cache.js.map +1 -1
- package/dist/wasi-paths.js.map +1 -1
- package/package.json +18 -5
- package/surface-manifest.json +64 -8
|
@@ -1,97 +1,46 @@
|
|
|
1
1
|
import { InternalCompilerError } from "../../errors.js";
|
|
2
2
|
/* Container-surface call lowering: array methods (including the HOF family
|
|
3
3
|
* map/filter/forEach with their synthesized helper functions), Map/Set
|
|
4
|
-
* methods with Node-exact forEach desugaring,
|
|
5
|
-
*
|
|
4
|
+
* methods with Node-exact forEach desugaring, iterator helpers, and indexed
|
|
5
|
+
* record operations. */
|
|
6
6
|
import * as ts from "../ts7/adapter.js";
|
|
7
|
-
import { BOOL,
|
|
8
|
-
import { ARRAY_METHODS, MAP_METHODS, SET_COMBINE_METHODS, SET_METHODS
|
|
9
|
-
import {
|
|
7
|
+
import { BOOL, CAUGHT, DYN, F64, JSVAL, STRING, UNDEFINED_T, VOID, arrayOf, funcOf, isRefCounted, isSupportedArrayElem, isSupportedIndexValue, isUnitType, typeEquals } from "../../ir/ir.js";
|
|
8
|
+
import { ARRAY_METHODS, MAP_METHODS, SET_COMBINE_METHODS, SET_METHODS } from "./surfaces.js";
|
|
9
|
+
import { tryLowerExpression } from "./expressions/try-lower-expression.js";
|
|
10
|
+
import { isSafeToRepeat } from "./expressions/evaluation-safety.js";
|
|
10
11
|
import { forOfVarTarget, lowerDestructuringAssign } from "./lower-stmts.js";
|
|
11
12
|
import { isJsSourceFile, locOf } from "../program.js";
|
|
12
13
|
import { islandPrimitiveExit, lowerDynDispatchMethodCall } from "./lower-calls.js";
|
|
14
|
+
import { buildArraySortFn } from "./lower-array-sort.js";
|
|
15
|
+
import { arrayIndexPresent, arrayValueRead, arrayValueStore, arrayValueType, currentArrayIndexPresent } from "./array-values.js";
|
|
13
16
|
import { typeKey } from "../type-mapper.js";
|
|
14
|
-
import { dynUndefinedExpr, own } from "./lowerer.js";
|
|
15
17
|
import { boolLit, countedFor, numLit, varRef } from "../../ir/build.js";
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
}
|
|
41
|
-
// TypeScript erases `as`, angle-bracket assertions, and `satisfies`.
|
|
42
|
-
// Peel them only for the void recognition: a non-void assertion still
|
|
43
|
-
// lowers through the ordinary path below, preserving the checked-dynamic
|
|
44
|
-
// cast discipline. Nested voids add no effects of their own, so the one
|
|
45
|
-
// innermost operand carries the complete runtime evaluation.
|
|
46
|
-
expr = peelErasableWrappers(expr);
|
|
47
|
-
let sawVoid = false;
|
|
48
|
-
while (ts.isVoidExpression(expr)) {
|
|
49
|
-
sawVoid = true;
|
|
50
|
-
expr = peelErasableWrappers(expr.expression);
|
|
51
|
-
}
|
|
52
|
-
if (sawVoid) {
|
|
53
|
-
// The caller discards this token before producing the parameter
|
|
54
|
-
// default, so the operand itself carries exactly the required effects;
|
|
55
|
-
// no bare undefined value needs to enter the IR.
|
|
56
|
-
return lowerer.lowerExpr(expr);
|
|
57
|
-
}
|
|
58
|
-
return lowerer.lowerExpr(node);
|
|
59
|
-
}
|
|
60
|
-
/** Preserve a statically-undefined argument's effects, then answer the
|
|
61
|
-
* optional parameter's already-lowered default value. */
|
|
62
|
-
function defaultAfterUndefined(value, defaultValue) {
|
|
63
|
-
if (droppableStatic(value))
|
|
64
|
-
return defaultValue;
|
|
65
|
-
return {
|
|
66
|
-
kind: "seqExpr",
|
|
67
|
-
stmts: [{ kind: "exprStmt", expr: value, loc: value.loc }],
|
|
68
|
-
result: defaultValue,
|
|
69
|
-
type: defaultValue.type,
|
|
70
|
-
loc: value.loc,
|
|
71
|
-
};
|
|
72
|
-
}
|
|
73
|
-
function lowerOptionalDefaultArg(lowerer, node, expected, defaultValue) {
|
|
74
|
-
const undefinedArg = lowerStaticallyUndefinedArg(lowerer, node);
|
|
75
|
-
if (undefinedArg)
|
|
76
|
-
return defaultAfterUndefined(undefinedArg, defaultValue);
|
|
77
|
-
const value = lowerer.lowerExpr(node);
|
|
78
|
-
if (value.type.kind === "union") {
|
|
79
|
-
const def = lowerer.unions.get(value.type.unionId);
|
|
80
|
-
if (def?.arms.length === 2 &&
|
|
81
|
-
def.arms.some((arm) => arm.kind === "undefinedT") &&
|
|
82
|
-
def.arms.some((arm) => typeEquals(arm, expected))) {
|
|
83
|
-
// A runtime optional argument uses its API default only on the
|
|
84
|
-
// undefined arm. The exact two-arm gate deliberately excludes null:
|
|
85
|
-
// optional parameters do not admit it, and an asserted null must keep
|
|
86
|
-
// the ordinary checked-narrow failure instead of defaulting.
|
|
87
|
-
return { kind: "nullish", left: value, right: defaultValue, type: expected, loc: value.loc };
|
|
88
|
-
}
|
|
18
|
+
import { lowerOptionalArgument } from "./optional-arguments.js";
|
|
19
|
+
/** Build the argument array for a mutating/copying operation while keeping
|
|
20
|
+
* the receiver's scalar payload ABI. A missing read is represented as the
|
|
21
|
+
* present UNDEFINED state in this temporary, so push/unshift and toSpliced
|
|
22
|
+
* materialize it through their ordinary indexed/iterator copies. */
|
|
23
|
+
function lowerArrayValueItems(lowerer, values, elem, arrType, loc) {
|
|
24
|
+
const out = lowerer.declareHiddenLocal("%arrayItems", arrType);
|
|
25
|
+
const outRef = varRef(out.id, arrType, loc);
|
|
26
|
+
const length = () => ({
|
|
27
|
+
kind: "arrIntrinsic",
|
|
28
|
+
method: "length",
|
|
29
|
+
receiver: outRef,
|
|
30
|
+
args: [],
|
|
31
|
+
type: F64,
|
|
32
|
+
loc,
|
|
33
|
+
});
|
|
34
|
+
const body = [
|
|
35
|
+
{ kind: "varDecl", localId: out.id, init: { kind: "arrayLit", elems: [], type: arrType, loc }, loc },
|
|
36
|
+
];
|
|
37
|
+
for (const value of values) {
|
|
38
|
+
const stored = lowerer.runtimeOptionalWidening(value.type, elem) !== null
|
|
39
|
+
? value
|
|
40
|
+
: lowerer.coerceToExpected(value, elem);
|
|
41
|
+
body.push(arrayValueStore(lowerer, outRef, length(), stored, elem, loc));
|
|
89
42
|
}
|
|
90
|
-
return
|
|
91
|
-
}
|
|
92
|
-
function lowerSplitLimitArg(lowerer, node, loc) {
|
|
93
|
-
const defaultValue = { kind: "numLit", value: 4294967295, type: F64, loc };
|
|
94
|
-
return node ? lowerOptionalDefaultArg(lowerer, node, F64, defaultValue) : defaultValue;
|
|
43
|
+
return { kind: "seqExpr", stmts: body, result: outRef, type: arrType, loc };
|
|
95
44
|
}
|
|
96
45
|
/** Callback-driven array producers bypass mapType's ordinary T[] mapping:
|
|
97
46
|
* the callback is lowered first, then the helper's result array is built
|
|
@@ -119,6 +68,9 @@ export function lowerArrayMethodCall(lowerer, call, access) {
|
|
|
119
68
|
if (!ARRAY_METHODS.has(name) && name !== "sort" && name !== "shift" && name !== "splice")
|
|
120
69
|
return null;
|
|
121
70
|
let receiverIr = lowerer.mapTypeOf(lowerer.typeOf(access.expression));
|
|
71
|
+
const checkerReceiver = lowerer.checker.getTypeAtLocation(access.expression);
|
|
72
|
+
const implicitArrayReceiver = receiverIr?.kind === "array" &&
|
|
73
|
+
(checkerReceiver.flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown) || lowerer.checkerAnyArrayType(checkerReceiver));
|
|
122
74
|
// A checker-`any[]` receiver (the readonly-array Array.isArray quirk)
|
|
123
75
|
// whose VALUE lowers to a real static array (maybeNarrow's isArray
|
|
124
76
|
// bridge): ride the ordinary tables on the lowered element type — the
|
|
@@ -127,7 +79,10 @@ export function lowerArrayMethodCall(lowerer, call, access) {
|
|
|
127
79
|
// the isStdlibMember gate is skipped for probed receivers (nothing
|
|
128
80
|
// user-declared can shadow a method on a value the checker calls any).
|
|
129
81
|
let probedUntyped = false;
|
|
130
|
-
if (
|
|
82
|
+
if (implicitArrayReceiver)
|
|
83
|
+
probedUntyped = true;
|
|
84
|
+
if ((receiverIr === null || receiverIr.kind === "dyn" ||
|
|
85
|
+
(receiverIr.kind === "array" && receiverIr.elem.kind === "dyn")) &&
|
|
131
86
|
(lowerer.checkerAnyArray(access.expression) ||
|
|
132
87
|
// A checker-`any` CHAIN whose value lowers to a real static array
|
|
133
88
|
// (`context.stack.split('\n').slice(2)` — the dyn-receiver string
|
|
@@ -227,12 +182,62 @@ export function lowerArrayMethodCall(lowerer, call, access) {
|
|
|
227
182
|
}
|
|
228
183
|
const receiver = lowerer.lowerExpr(access.expression);
|
|
229
184
|
const index = lowerer.lowerExprExpecting(call.arguments[0], F64);
|
|
230
|
-
const value = lowerer.
|
|
185
|
+
const value = lowerer.lowerExpr(call.arguments[1]);
|
|
186
|
+
if (value.type.kind === "union" && lowerer.runtimeOptionalWidening(value.type, elem) !== null) {
|
|
187
|
+
// `.with()` validates its index and returns a fresh copy. Stabilize
|
|
188
|
+
// all three operands, then select the scalar replacement or the
|
|
189
|
+
// present-undefined copy without evaluating a read twice.
|
|
190
|
+
const receiverLocal = lowerer.declareHiddenLocal("%withRecv", receiverIr);
|
|
191
|
+
const indexLocal = lowerer.declareHiddenLocal("%withIndex", F64);
|
|
192
|
+
const valueLocal = lowerer.declareHiddenLocal("%withValue", value.type);
|
|
193
|
+
const unionId = value.type.unionId;
|
|
194
|
+
const receiverRef = varRef(receiverLocal.id, receiverIr, loc);
|
|
195
|
+
const indexRef = varRef(indexLocal.id, F64, loc);
|
|
196
|
+
const valueRef = varRef(valueLocal.id, value.type, loc);
|
|
197
|
+
const undefTag = lowerer.armTag(unionId, UNDEFINED_T);
|
|
198
|
+
const condition = {
|
|
199
|
+
kind: "unionIsTag",
|
|
200
|
+
unionId,
|
|
201
|
+
tag: undefTag,
|
|
202
|
+
negated: false,
|
|
203
|
+
value: valueRef,
|
|
204
|
+
type: BOOL,
|
|
205
|
+
loc,
|
|
206
|
+
};
|
|
207
|
+
const missing = {
|
|
208
|
+
kind: "arrIntrinsic",
|
|
209
|
+
method: "withUndefined",
|
|
210
|
+
receiver: receiverRef,
|
|
211
|
+
args: [indexRef],
|
|
212
|
+
type: receiverIr,
|
|
213
|
+
loc,
|
|
214
|
+
};
|
|
215
|
+
const present = {
|
|
216
|
+
kind: "arrIntrinsic",
|
|
217
|
+
method: "with",
|
|
218
|
+
receiver: receiverRef,
|
|
219
|
+
args: [indexRef, lowerer.coerceToExpected(valueRef, elem)],
|
|
220
|
+
type: receiverIr,
|
|
221
|
+
loc,
|
|
222
|
+
};
|
|
223
|
+
return {
|
|
224
|
+
kind: "seqExpr",
|
|
225
|
+
stmts: [
|
|
226
|
+
{ kind: "varDecl", localId: receiverLocal.id, init: receiver, loc },
|
|
227
|
+
{ kind: "varDecl", localId: indexLocal.id, init: index, loc },
|
|
228
|
+
{ kind: "varDecl", localId: valueLocal.id, init: value, loc },
|
|
229
|
+
],
|
|
230
|
+
result: { kind: "ternary", cond: condition, then: missing, else_: present, type: receiverIr, loc },
|
|
231
|
+
type: receiverIr,
|
|
232
|
+
loc,
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
const coercedValue = lowerer.coerceInto(call.arguments[1], value, elem);
|
|
231
236
|
return {
|
|
232
237
|
kind: "arrIntrinsic",
|
|
233
238
|
method: "with",
|
|
234
239
|
receiver,
|
|
235
|
-
args: [index,
|
|
240
|
+
args: [index, coercedValue],
|
|
236
241
|
type: receiverIr,
|
|
237
242
|
loc,
|
|
238
243
|
};
|
|
@@ -254,14 +259,19 @@ export function lowerArrayMethodCall(lowerer, call, access) {
|
|
|
254
259
|
const deleteCount = call.arguments.length === 0
|
|
255
260
|
? { kind: "numLit", value: 0, type: F64, loc }
|
|
256
261
|
: call.arguments[1]
|
|
257
|
-
?
|
|
262
|
+
? lowerOptionalArgument(lowerer, call.arguments[1], F64, deleteCountDefault)
|
|
258
263
|
: { kind: "numLit", value: Infinity, type: F64, loc };
|
|
259
|
-
const
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
loc
|
|
264
|
-
|
|
264
|
+
const itemNodes = call.arguments.slice(2);
|
|
265
|
+
const itemProbes = itemNodes.map((arg) => tryLowerExpression(lowerer, arg));
|
|
266
|
+
const statefulItems = itemProbes.some((probe) => probe !== null && lowerer.runtimeOptionalWidening(probe.type, elem) !== null);
|
|
267
|
+
const items = statefulItems
|
|
268
|
+
? lowerArrayValueItems(lowerer, itemNodes.map((arg) => lowerer.lowerExpr(arg)), elem, receiverIr, loc)
|
|
269
|
+
: {
|
|
270
|
+
kind: "arrayLit",
|
|
271
|
+
elems: itemNodes.map((arg) => lowerer.coerceInto(arg, lowerer.lowerExpr(arg), elem)),
|
|
272
|
+
type: receiverIr,
|
|
273
|
+
loc,
|
|
274
|
+
};
|
|
265
275
|
return {
|
|
266
276
|
kind: "arrIntrinsic",
|
|
267
277
|
method: "toSpliced",
|
|
@@ -299,8 +309,17 @@ export function lowerArrayMethodCall(lowerer, call, access) {
|
|
|
299
309
|
: undefined;
|
|
300
310
|
lowerer.noLowering(`.${name} with ${call.arguments.length} argument${call.arguments.length === 1 ? "" : "s"}`, call, hint);
|
|
301
311
|
}
|
|
302
|
-
if (name === "push" || name === "unshift"
|
|
303
|
-
|
|
312
|
+
if (name === "push" || name === "unshift") {
|
|
313
|
+
let receiver = lowerer.lowerExpr(access.expression);
|
|
314
|
+
if (receiver.type.kind === "union" && lowerer.armTag(receiver.type.unionId, UNDEFINED_T) >= 0) {
|
|
315
|
+
const present = lowerer.stripUndefinedArm(receiver.type);
|
|
316
|
+
const helper = present.kind === "array"
|
|
317
|
+
? lowerer.narrowedArmHelper(receiver.type.unionId, present, loc)
|
|
318
|
+
: null;
|
|
319
|
+
receiver = helper
|
|
320
|
+
? { kind: "call", callee: helper, args: [receiver], type: present, loc }
|
|
321
|
+
: lowerer.maybeNarrow(receiver, access.expression);
|
|
322
|
+
}
|
|
304
323
|
// `a.push(...src)` / `a.unshift(...src)`: copy src's elements in
|
|
305
324
|
// order (count snapshotted first, so the self-spread forms duplicate
|
|
306
325
|
// like JS) and return the new length.
|
|
@@ -326,6 +345,18 @@ export function lowerArrayMethodCall(lowerer, call, access) {
|
|
|
326
345
|
loc,
|
|
327
346
|
};
|
|
328
347
|
}
|
|
348
|
+
const valueProbes = call.arguments.map((arg) => tryLowerExpression(lowerer, arg));
|
|
349
|
+
if (valueProbes.some((probe) => probe !== null && lowerer.runtimeOptionalWidening(probe.type, elem) !== null)) {
|
|
350
|
+
const items = lowerArrayValueItems(lowerer, call.arguments.map((arg) => lowerer.lowerExpr(arg)), elem, receiverIr, loc);
|
|
351
|
+
return {
|
|
352
|
+
kind: "arrIntrinsic",
|
|
353
|
+
method: name === "push" ? "pushSpread" : "unshiftSpread",
|
|
354
|
+
receiver,
|
|
355
|
+
args: [items],
|
|
356
|
+
type: F64,
|
|
357
|
+
loc,
|
|
358
|
+
};
|
|
359
|
+
}
|
|
329
360
|
// Inserted values flow into the element slot like an assignment would:
|
|
330
361
|
// union-element arrays wrap plain arm values (coerceInto is inert
|
|
331
362
|
// when the types already agree).
|
|
@@ -335,39 +366,31 @@ export function lowerArrayMethodCall(lowerer, call, access) {
|
|
|
335
366
|
method: name,
|
|
336
367
|
receiver,
|
|
337
368
|
args,
|
|
338
|
-
type:
|
|
369
|
+
type: F64,
|
|
339
370
|
loc,
|
|
340
371
|
};
|
|
341
372
|
}
|
|
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)");
|
|
373
|
+
if (name === "pop") {
|
|
374
|
+
// Like shift(), pop() returns undefined for an empty array. Union
|
|
375
|
+
// element arrays remain fenced here because their existing boxed
|
|
376
|
+
// element already carries the result union and a second undefined
|
|
377
|
+
// wrapper would lose the element identity contract.
|
|
378
|
+
if (elem.kind === "union" && lowerer.armTag(elem.unionId, UNDEFINED_T) < 0) {
|
|
379
|
+
lowerer.unsupported("SC1090", call, "'.pop()' on union-element arrays without an undefined arm (read the last element and splice it after narrowing instead)");
|
|
350
380
|
}
|
|
351
381
|
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
382
|
return {
|
|
363
383
|
kind: "arrIntrinsic",
|
|
364
|
-
method:
|
|
384
|
+
method: "pop",
|
|
365
385
|
receiver,
|
|
366
|
-
args: [
|
|
367
|
-
type:
|
|
386
|
+
args: [],
|
|
387
|
+
type: arrayValueType(lowerer, elem),
|
|
368
388
|
loc,
|
|
369
389
|
};
|
|
370
390
|
}
|
|
391
|
+
if (name === "indexOf" || name === "includes") {
|
|
392
|
+
return lowerArraySearchCall(lowerer, call, access, name, elem);
|
|
393
|
+
}
|
|
371
394
|
if (name === "concat") {
|
|
372
395
|
// `a.concat(x, ys, ...)` — a fresh array of a's elements followed by
|
|
373
396
|
// each argument in order: JS spreads array arguments one level
|
|
@@ -385,8 +408,12 @@ export function lowerArrayMethodCall(lowerer, call, access) {
|
|
|
385
408
|
lowerer.unsupported("SC1090", argNode, "spread arguments to concat (pass the array itself — concat already spreads array arguments)");
|
|
386
409
|
}
|
|
387
410
|
const argIr = lowerer.mapTypeOf(lowerer.typeOf(argNode));
|
|
388
|
-
|
|
389
|
-
|
|
411
|
+
const probedArg = probedUntyped ? tryLowerExpression(lowerer, argNode) : null;
|
|
412
|
+
const argArrayType = probedArg?.type.kind === "array"
|
|
413
|
+
? probedArg.type
|
|
414
|
+
: argIr?.kind === "array" ? argIr : null;
|
|
415
|
+
if (argArrayType !== null && typeEquals(argArrayType.elem, elem)) {
|
|
416
|
+
if (elem.kind === "array" && typeEquals(argArrayType, elem)) {
|
|
390
417
|
// number[][].concat(inner: number[]) — TS says element, JS's
|
|
391
418
|
// IsArray says spread; no honest static answer exists.
|
|
392
419
|
lowerer.unsupported("SC1090", argNode, "concat of a bare inner array onto an array-of-arrays (JS would SPREAD it one level — wrap it: a.concat([inner]))");
|
|
@@ -455,11 +482,11 @@ export function lowerArrayMethodCall(lowerer, call, access) {
|
|
|
455
482
|
// arrays are fenced: their shift result would collapse arms
|
|
456
483
|
// (`(string | undefined)[]`'s shift is `string | undefined` too, and
|
|
457
484
|
// 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)");
|
|
485
|
+
if (elem.kind === "union" && lowerer.armTag(elem.unionId, UNDEFINED_T) < 0) {
|
|
486
|
+
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
487
|
}
|
|
461
488
|
const receiver = lowerer.lowerExpr(access.expression);
|
|
462
|
-
return { kind: "arrIntrinsic", method: "shift", receiver, args: [], type: lowerer
|
|
489
|
+
return { kind: "arrIntrinsic", method: "shift", receiver, args: [], type: arrayValueType(lowerer, elem), loc };
|
|
463
490
|
}
|
|
464
491
|
if (name === "join") {
|
|
465
492
|
// The ambient declares join on every Array<T> (a per-element-type
|
|
@@ -487,7 +514,7 @@ export function lowerArrayMethodCall(lowerer, call, access) {
|
|
|
487
514
|
}
|
|
488
515
|
if (name === "find" || name === "findIndex" || name === "findLast" ||
|
|
489
516
|
name === "findLastIndex" || name === "some" || name === "every") {
|
|
490
|
-
return lowerArrayFindLikeCall(lowerer, call, access, name, elem);
|
|
517
|
+
return lowerArrayFindLikeCall(lowerer, call, access, name, elem, probedUntyped);
|
|
491
518
|
}
|
|
492
519
|
if (name === "at")
|
|
493
520
|
return lowerArrayAtCall(lowerer, call, access, elem);
|
|
@@ -496,6 +523,217 @@ export function lowerArrayMethodCall(lowerer, call, access) {
|
|
|
496
523
|
// reduce / reduceRight
|
|
497
524
|
return lowerArrayReduceCall(lowerer, call, access, name, elem);
|
|
498
525
|
}
|
|
526
|
+
/** `indexOf` and `includes` normally use the runtime's typed search helpers,
|
|
527
|
+
* but an indexed array read is a `T | undefined` value. Keep that union
|
|
528
|
+
* through a small lowered loop so the needle's missing state is observable:
|
|
529
|
+
* indexOf skips holes, while includes treats holes as undefined. The same
|
|
530
|
+
* loop also gives union-element arrays their value equality rather than
|
|
531
|
+
* comparing the compiler's union boxes by pointer. */
|
|
532
|
+
function lowerArraySearchCall(lowerer, call, access, method, elem) {
|
|
533
|
+
const loc = locOf(call);
|
|
534
|
+
const receiver = lowerer.lowerExpr(access.expression);
|
|
535
|
+
const argNode = call.arguments[0];
|
|
536
|
+
let needle = lowerer.lowerExpr(argNode);
|
|
537
|
+
// A derived CLASS VALUE against a base-classval element widens (the same
|
|
538
|
+
// pointer — identity search is exact); the coercion path owns the ABI gate.
|
|
539
|
+
if (needle.type.kind === "classval" && elem.kind === "classval" && !typeEquals(needle.type, elem)) {
|
|
540
|
+
needle = lowerer.coerceInto(argNode, needle, elem);
|
|
541
|
+
}
|
|
542
|
+
// Preserve the existing fast path for an ordinary, non-union needle. Its
|
|
543
|
+
// runtime implementation already has exact primitive/reference equality.
|
|
544
|
+
if (elem.kind !== "union" && typeEquals(needle.type, elem)) {
|
|
545
|
+
return {
|
|
546
|
+
kind: "arrIntrinsic",
|
|
547
|
+
method,
|
|
548
|
+
receiver,
|
|
549
|
+
args: [needle],
|
|
550
|
+
type: method === "indexOf" ? F64 : BOOL,
|
|
551
|
+
loc,
|
|
552
|
+
};
|
|
553
|
+
}
|
|
554
|
+
const valueT = arrayValueType(lowerer, elem);
|
|
555
|
+
// jsval/dyn arrays have a single engine value representation rather than a
|
|
556
|
+
// static undefined arm. Keep their existing runtime path and its explicit
|
|
557
|
+
// type fence; typed static arrays take the state-aware helper below.
|
|
558
|
+
if (valueT.kind !== "union") {
|
|
559
|
+
if (!typeEquals(needle.type, elem))
|
|
560
|
+
lowerer.badType(argNode, lowerer.typeOf(argNode));
|
|
561
|
+
return {
|
|
562
|
+
kind: "arrIntrinsic",
|
|
563
|
+
method,
|
|
564
|
+
receiver,
|
|
565
|
+
args: [needle],
|
|
566
|
+
type: method === "indexOf" ? F64 : BOOL,
|
|
567
|
+
loc,
|
|
568
|
+
};
|
|
569
|
+
}
|
|
570
|
+
const searchNeedle = lowerer.coerceInto(argNode, needle, valueT);
|
|
571
|
+
if (!typeEquals(searchNeedle.type, valueT))
|
|
572
|
+
lowerer.badType(argNode, lowerer.typeOf(argNode));
|
|
573
|
+
const helper = arraySearchHelper(lowerer, method, elem, valueT, loc);
|
|
574
|
+
return { kind: "call", callee: helper, args: [receiver, searchNeedle], type: method === "indexOf" ? F64 : BOOL, loc };
|
|
575
|
+
}
|
|
576
|
+
/** SameValueZero over a static value union. `unionEq` supplies strict
|
|
577
|
+
* equality for every arm; the extra f64-arm test is the only difference for
|
|
578
|
+
* includes, where NaN equals NaN while +0 and -0 remain equal. */
|
|
579
|
+
function arraySearchEquality(lowerer, left, right, sameValueZero, loc) {
|
|
580
|
+
if (left.type.kind !== "union" || right.type.kind !== "union" || left.type.unionId !== right.type.unionId) {
|
|
581
|
+
return { kind: "bin", op: "===", left, right, type: BOOL, loc };
|
|
582
|
+
}
|
|
583
|
+
const unionId = left.type.unionId;
|
|
584
|
+
const strict = { kind: "unionEq", unionId, negated: false, sameValue: false, left, right, type: BOOL, loc };
|
|
585
|
+
if (!sameValueZero)
|
|
586
|
+
return strict;
|
|
587
|
+
const numberTag = lowerer.unions.get(unionId)?.arms.findIndex((arm) => arm.kind === "f64") ?? -1;
|
|
588
|
+
if (numberTag < 0)
|
|
589
|
+
return strict;
|
|
590
|
+
const leftIsNumber = { kind: "unionIsTag", unionId, tag: numberTag, negated: false, value: left, type: BOOL, loc };
|
|
591
|
+
const rightIsNumber = { kind: "unionIsTag", unionId, tag: numberTag, negated: false, value: right, type: BOOL, loc };
|
|
592
|
+
const leftNumber = { kind: "unionNarrow", unionId, tag: numberTag, value: left, type: F64, loc };
|
|
593
|
+
const rightNumber = { kind: "unionNarrow", unionId, tag: numberTag, value: right, type: F64, loc };
|
|
594
|
+
const bothNaN = {
|
|
595
|
+
kind: "logical",
|
|
596
|
+
op: "&&",
|
|
597
|
+
left: {
|
|
598
|
+
kind: "logical",
|
|
599
|
+
op: "&&",
|
|
600
|
+
left: leftIsNumber,
|
|
601
|
+
right: rightIsNumber,
|
|
602
|
+
type: BOOL,
|
|
603
|
+
loc,
|
|
604
|
+
},
|
|
605
|
+
right: {
|
|
606
|
+
kind: "logical",
|
|
607
|
+
op: "&&",
|
|
608
|
+
left: { kind: "libCall", fn: "num.isNaN", args: [leftNumber], type: BOOL, loc },
|
|
609
|
+
right: { kind: "libCall", fn: "num.isNaN", args: [rightNumber], type: BOOL, loc },
|
|
610
|
+
type: BOOL,
|
|
611
|
+
loc,
|
|
612
|
+
},
|
|
613
|
+
type: BOOL,
|
|
614
|
+
loc,
|
|
615
|
+
};
|
|
616
|
+
return { kind: "logical", op: "||", left: strict, right: bothNaN, type: BOOL, loc };
|
|
617
|
+
}
|
|
618
|
+
function arraySearchHelper(lowerer, method, elem, valueT, loc) {
|
|
619
|
+
const key = `${method}:${typeKey(elem)}:${typeKey(valueT)}`;
|
|
620
|
+
const existing = lowerer.arrHofHelpers.get(key);
|
|
621
|
+
if (existing)
|
|
622
|
+
return existing;
|
|
623
|
+
const name = `%arr.${method}.${lowerer.arrHofHelpers.size}`;
|
|
624
|
+
lowerer.arrHofHelpers.set(key, name);
|
|
625
|
+
const arrT = arrayOf(elem);
|
|
626
|
+
const resultT = method === "indexOf" ? F64 : BOOL;
|
|
627
|
+
const arrRef = varRef("a.0", arrT, loc);
|
|
628
|
+
const indexRef = varRef("i.0", F64, loc);
|
|
629
|
+
const needleRef = varRef("needle.0", valueT, loc);
|
|
630
|
+
const stateRef = varRef("state.0", F64, loc);
|
|
631
|
+
const nextRef = varRef("next.0", F64, loc);
|
|
632
|
+
const rawRead = { kind: "arrayGet", arr: arrRef, index: indexRef, type: elem, loc };
|
|
633
|
+
const missing = lowerer.wrappedUndefined(valueT, loc);
|
|
634
|
+
if (!missing)
|
|
635
|
+
throw new InternalCompilerError("array search helper needs an undefined arm");
|
|
636
|
+
const value = {
|
|
637
|
+
kind: "ternary",
|
|
638
|
+
cond: { kind: "bin", op: "===", left: stateRef, right: numLit(1, loc), type: BOOL, loc },
|
|
639
|
+
then: typeEquals(elem, valueT) ? rawRead : lowerer.coerceToExpected(rawRead, valueT),
|
|
640
|
+
else_: missing,
|
|
641
|
+
type: valueT,
|
|
642
|
+
loc,
|
|
643
|
+
};
|
|
644
|
+
const equal = arraySearchEquality(lowerer, varRef("value.0", valueT, loc), needleRef, method === "includes", loc);
|
|
645
|
+
const match = method === "indexOf"
|
|
646
|
+
? {
|
|
647
|
+
kind: "logical",
|
|
648
|
+
op: "&&",
|
|
649
|
+
left: { kind: "bin", op: "!==", left: stateRef, right: numLit(0, loc), type: BOOL, loc },
|
|
650
|
+
right: equal,
|
|
651
|
+
type: BOOL,
|
|
652
|
+
loc,
|
|
653
|
+
}
|
|
654
|
+
: equal;
|
|
655
|
+
const undefinedTag = lowerer.armTag(valueT.unionId, UNDEFINED_T);
|
|
656
|
+
if (undefinedTag < 0)
|
|
657
|
+
throw new InternalCompilerError("array search helper needs an undefined tag");
|
|
658
|
+
const needleIsUndefined = {
|
|
659
|
+
kind: "unionIsTag",
|
|
660
|
+
unionId: valueT.unionId,
|
|
661
|
+
tag: undefinedTag,
|
|
662
|
+
negated: false,
|
|
663
|
+
value: needleRef,
|
|
664
|
+
type: BOOL,
|
|
665
|
+
loc,
|
|
666
|
+
};
|
|
667
|
+
const advance = {
|
|
668
|
+
kind: "assign",
|
|
669
|
+
localId: "i.0",
|
|
670
|
+
value: { kind: "bin", op: "+", left: indexRef, right: numLit(1, loc), type: F64, loc },
|
|
671
|
+
loc,
|
|
672
|
+
};
|
|
673
|
+
const visitPresent = [
|
|
674
|
+
{ kind: "varDecl", localId: "value.0", init: value, loc },
|
|
675
|
+
{
|
|
676
|
+
kind: "if",
|
|
677
|
+
cond: match,
|
|
678
|
+
then: [{ kind: "return", value: method === "indexOf" ? indexRef : boolLit(true, loc), loc }],
|
|
679
|
+
else_: null,
|
|
680
|
+
loc,
|
|
681
|
+
},
|
|
682
|
+
advance,
|
|
683
|
+
];
|
|
684
|
+
const skipHole = [
|
|
685
|
+
{
|
|
686
|
+
kind: "varDecl",
|
|
687
|
+
localId: "next.0",
|
|
688
|
+
init: { kind: "arrIntrinsic", method: "nextPresent", receiver: arrRef, args: [indexRef], type: F64, loc },
|
|
689
|
+
loc,
|
|
690
|
+
},
|
|
691
|
+
...(method === "includes"
|
|
692
|
+
? [{ kind: "if", cond: needleIsUndefined, then: [{ kind: "return", value: boolLit(true, loc), loc }], else_: null, loc }]
|
|
693
|
+
: []),
|
|
694
|
+
{ kind: "assign", localId: "i.0", value: nextRef, loc },
|
|
695
|
+
];
|
|
696
|
+
const loopBody = [
|
|
697
|
+
{ kind: "varDecl", localId: "state.0", init: { kind: "arrayState", arr: arrRef, index: indexRef, type: F64, loc }, loc },
|
|
698
|
+
{
|
|
699
|
+
kind: "if",
|
|
700
|
+
cond: { kind: "bin", op: "===", left: stateRef, right: numLit(0, loc), type: BOOL, loc },
|
|
701
|
+
then: skipHole,
|
|
702
|
+
else_: visitPresent,
|
|
703
|
+
loc,
|
|
704
|
+
},
|
|
705
|
+
];
|
|
706
|
+
lowerer.liftedFns.push({
|
|
707
|
+
name,
|
|
708
|
+
params: [
|
|
709
|
+
{ localId: "a.0", name: "a", type: arrT },
|
|
710
|
+
{ localId: "needle.0", name: "needle", type: valueT },
|
|
711
|
+
],
|
|
712
|
+
returnType: resultT,
|
|
713
|
+
locals: [
|
|
714
|
+
{ id: "a.0", name: "a", type: arrT, mutable: true },
|
|
715
|
+
{ id: "needle.0", name: "needle", type: valueT, mutable: true },
|
|
716
|
+
{ id: "n.0", name: "n", type: F64, mutable: false },
|
|
717
|
+
{ id: "i.0", name: "i", type: F64, mutable: true },
|
|
718
|
+
{ id: "state.0", name: "state", type: F64, mutable: false },
|
|
719
|
+
{ id: "next.0", name: "next", type: F64, mutable: false },
|
|
720
|
+
{ id: "value.0", name: "value", type: valueT, mutable: false },
|
|
721
|
+
],
|
|
722
|
+
body: [
|
|
723
|
+
readLenStmt(arrT, loc),
|
|
724
|
+
{ kind: "varDecl", localId: "i.0", init: numLit(0, loc), loc },
|
|
725
|
+
{
|
|
726
|
+
kind: "while",
|
|
727
|
+
cond: { kind: "bin", op: "<", left: indexRef, right: varRef("n.0", F64, loc), type: BOOL, loc },
|
|
728
|
+
body: loopBody,
|
|
729
|
+
loc,
|
|
730
|
+
},
|
|
731
|
+
{ kind: "return", value: method === "indexOf" ? numLit(-1, loc) : boolLit(false, loc), loc },
|
|
732
|
+
],
|
|
733
|
+
loc,
|
|
734
|
+
});
|
|
735
|
+
return name;
|
|
736
|
+
}
|
|
499
737
|
/** Lowers and validates a HOF callback argument. The lib declares optional
|
|
500
738
|
* trailing (index, array) parameters after `lead` (the element for the
|
|
501
739
|
* map family, accumulator + element for reduce); a callback may declare
|
|
@@ -503,7 +741,8 @@ export function lowerArrayMethodCall(lowerer, call, access) {
|
|
|
503
741
|
* loop passes exactly what it declares (JS passes everything; a callback
|
|
504
742
|
* only sees the parameters it names). Returns the lowered callback and
|
|
505
743
|
* its declared arity. */
|
|
506
|
-
function hofCallbackArg(lowerer, argNode, lead, arrT) {
|
|
744
|
+
function hofCallbackArg(lowerer, argNode, lead, arrT, bindUntyped = false) {
|
|
745
|
+
const full = [...lead, F64, arrT];
|
|
507
746
|
// A DYN-receiver HOF's callback (`parsed.flatMap((value) => ...)`):
|
|
508
747
|
// the contextual signature types the unannotated param `any` (the
|
|
509
748
|
// receiver is checker-`any[]`), while the VALUE each call receives is
|
|
@@ -537,6 +776,43 @@ function hofCallbackArg(lowerer, argNode, lead, arrT) {
|
|
|
537
776
|
lowerer.jsvalParamOverrides.add(p);
|
|
538
777
|
});
|
|
539
778
|
}
|
|
779
|
+
// A checker-untyped array expression whose lowered value supplies a
|
|
780
|
+
// concrete static element type still contextually types an inline HOF
|
|
781
|
+
// callback with any/unknown. Bind only unannotated parameters to the
|
|
782
|
+
// exact ABI the synthesized loop passes. The override is temporary:
|
|
783
|
+
// one callback AST may be lowered under several generic instances.
|
|
784
|
+
const contextual = [];
|
|
785
|
+
const previousImplicit = lowerer.implicitParamTypes;
|
|
786
|
+
let contextualTs = null;
|
|
787
|
+
if (bindUntyped && (ts.isArrowFunction(argNode) || ts.isFunctionExpression(argNode))) {
|
|
788
|
+
argNode.parameters.forEach((param, i) => {
|
|
789
|
+
const expected = full[i];
|
|
790
|
+
if (expected === undefined || expected.kind === "dyn" || expected.kind === "jsval" ||
|
|
791
|
+
!ts.isIdentifier(param.name) || param.type || param.initializer || param.dotDotDotToken) {
|
|
792
|
+
return;
|
|
793
|
+
}
|
|
794
|
+
const checkerType = lowerer.checker.getTypeAtLocation(param.name);
|
|
795
|
+
if ((checkerType.flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown)) === 0)
|
|
796
|
+
return;
|
|
797
|
+
const symbol = lowerer.checker.getSymbolAtLocation(param.name);
|
|
798
|
+
if (!symbol)
|
|
799
|
+
return;
|
|
800
|
+
contextual.push({ symbol, previous: lowerer.runtimeOptionalBindingTypes.get(symbol) });
|
|
801
|
+
lowerer.runtimeOptionalBindingTypes.set(symbol, expected);
|
|
802
|
+
const bodyExpected = expected.kind === "union" && lowerer.armTag(expected.unionId, UNDEFINED_T) >= 0
|
|
803
|
+
? lowerer.stripUndefinedArm(expected)
|
|
804
|
+
: expected;
|
|
805
|
+
const expectedTs = bodyExpected.kind === "string" ? lowerer.checker.getStringType() :
|
|
806
|
+
bodyExpected.kind === "f64" ? lowerer.checker.getNumberType() :
|
|
807
|
+
bodyExpected.kind === "bool" ? lowerer.checker.getBooleanType() : null;
|
|
808
|
+
if (expectedTs !== null) {
|
|
809
|
+
contextualTs ??= new Map(previousImplicit ?? []);
|
|
810
|
+
contextualTs.set(symbol, expectedTs);
|
|
811
|
+
}
|
|
812
|
+
});
|
|
813
|
+
}
|
|
814
|
+
if (contextualTs !== null)
|
|
815
|
+
lowerer.implicitParamTypes = contextualTs;
|
|
540
816
|
let fnArg;
|
|
541
817
|
try {
|
|
542
818
|
fnArg = lowerer.lowerExpr(argNode);
|
|
@@ -544,8 +820,14 @@ function hofCallbackArg(lowerer, argNode, lead, arrT) {
|
|
|
544
820
|
finally {
|
|
545
821
|
for (const n of overridden)
|
|
546
822
|
lowerer.chainNarrowedType.delete(n);
|
|
823
|
+
for (const { symbol, previous } of contextual) {
|
|
824
|
+
if (previous === undefined)
|
|
825
|
+
lowerer.runtimeOptionalBindingTypes.delete(symbol);
|
|
826
|
+
else
|
|
827
|
+
lowerer.runtimeOptionalBindingTypes.set(symbol, previous);
|
|
828
|
+
}
|
|
829
|
+
lowerer.implicitParamTypes = previousImplicit;
|
|
547
830
|
}
|
|
548
|
-
const full = [...lead, F64, arrT];
|
|
549
831
|
if (fnArg.type.kind !== "func" ||
|
|
550
832
|
fnArg.type.params.length > full.length ||
|
|
551
833
|
!fnArg.type.params.every((p, i) => typeEquals(p, full[i]))) {
|
|
@@ -572,7 +854,22 @@ function lowerArrayHofCall(lowerer, call, access, method, elem) {
|
|
|
572
854
|
const argNode = call.arguments[0];
|
|
573
855
|
if (!argNode)
|
|
574
856
|
lowerer.unsupported("SC1090", call, "this call form"); // tsc-guarded
|
|
575
|
-
const
|
|
857
|
+
const booleanFilter = method === "filter" &&
|
|
858
|
+
ts.isIdentifier(argNode) &&
|
|
859
|
+
argNode.text === "Boolean" &&
|
|
860
|
+
lowerer.isStdlibSymbol(lowerer.resolveValueSymbol(argNode) ?? undefined);
|
|
861
|
+
const valueT = arrayValueType(lowerer, elem);
|
|
862
|
+
if (booleanFilter) {
|
|
863
|
+
if (valueT.kind === "union")
|
|
864
|
+
lowerer.requireTruthyUnion(valueT.unionId, argNode);
|
|
865
|
+
if (valueT.kind === "dyn" || valueT.kind === "jsval" || valueT.kind === "void" || isUnitType(valueT)) {
|
|
866
|
+
lowerer.badType(argNode, lowerer.typeOf(argNode));
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
const callback = booleanFilter
|
|
870
|
+
? { fnArg: truthyFilterCallback(lowerer, valueT, loc), arity: 1 }
|
|
871
|
+
: hofCallbackArg(lowerer, argNode, [valueT], arrayOf(elem));
|
|
872
|
+
const { fnArg, arity } = callback;
|
|
576
873
|
const fnRet = fnArg.type.ret;
|
|
577
874
|
if (method === "map" && (fnRet.kind === "void" || fnRet.kind === "func")) {
|
|
578
875
|
// The result array U[] is unrepresentable here (no void elements;
|
|
@@ -595,10 +892,98 @@ function lowerArrayHofCall(lowerer, call, access, method, elem) {
|
|
|
595
892
|
}
|
|
596
893
|
lowerer.badType(argNode, lowerer.typeOf(argNode));
|
|
597
894
|
}
|
|
598
|
-
const
|
|
599
|
-
|
|
895
|
+
const outElem = method === "filter"
|
|
896
|
+
? filterResultElem(lowerer, call, argNode, elem, valueT, booleanFilter)
|
|
897
|
+
: callbackArrayElem(lowerer, call, fnRet);
|
|
898
|
+
const helper = arrayHofHelper(lowerer, method, elem, fnRet, arity, loc, outElem);
|
|
899
|
+
const resultType = method === "map" || method === "filter" ? arrayOf(outElem) : VOID;
|
|
600
900
|
return { kind: "call", callee: helper, args: [receiver, fnArg], type: resultType, loc };
|
|
601
901
|
}
|
|
902
|
+
/** Preserve the checker-selected payload ABI when a callback's runtime
|
|
903
|
+
* return gained only undefined. The array state byte carries that value. */
|
|
904
|
+
function callbackArrayElem(lowerer, call, fnRet) {
|
|
905
|
+
const result = lowerer.mapTypeOf(lowerer.typeOf(call));
|
|
906
|
+
return result?.kind === "array" && lowerer.runtimeOptionalWidening(fnRet, result.elem)
|
|
907
|
+
? result.elem : fnRet;
|
|
908
|
+
}
|
|
909
|
+
/** The checker-selected element of a filter result, with the same trust
|
|
910
|
+
* boundary as the historical narrowing filter: an inferred inline type
|
|
911
|
+
* predicate may prove one source arm; a written predicate annotation may
|
|
912
|
+
* not. Boolean is safe when every discarded arm is a nullish unit. */
|
|
913
|
+
function filterResultElem(lowerer, call, argNode, elem, valueT, booleanFilter) {
|
|
914
|
+
const callT = lowerer.typeOf(call);
|
|
915
|
+
const result = lowerer.mapTypeOf(callT);
|
|
916
|
+
let outElem = result?.kind === "array" ? result.elem : elem;
|
|
917
|
+
const access = ts.isPropertyAccessExpression(call.expression) ? call.expression : null;
|
|
918
|
+
const checkerReceiver = access
|
|
919
|
+
? lowerer.mapTypeOf(lowerer.typeOf(access.expression))
|
|
920
|
+
: null;
|
|
921
|
+
// Evolving-any arrays of island handles retain their jsval element ABI,
|
|
922
|
+
// while the checker later describes both the receiver and unchanged
|
|
923
|
+
// filter result by the evolved callable type. Equal checker elements mean
|
|
924
|
+
// no predicate narrowing occurred; preserve the runtime element home.
|
|
925
|
+
if (elem.kind === "jsval" && valueT.kind === "jsval" &&
|
|
926
|
+
result?.kind === "array" && checkerReceiver?.kind === "array" &&
|
|
927
|
+
typeEquals(result.elem, checkerReceiver.elem)) {
|
|
928
|
+
return elem;
|
|
929
|
+
}
|
|
930
|
+
if (!typeEquals(outElem, elem)) {
|
|
931
|
+
const contextual = lowerer.mapTypeOf(lowerer.checker.getContextualType(call) ?? callT);
|
|
932
|
+
if (contextual?.kind === "array" && typeEquals(contextual.elem, elem))
|
|
933
|
+
outElem = elem;
|
|
934
|
+
}
|
|
935
|
+
if (typeEquals(outElem, elem))
|
|
936
|
+
return elem;
|
|
937
|
+
const annotateEscape = "keep the receiver's element type instead — annotate the callback's return ': boolean' " +
|
|
938
|
+
"(the checker then skips the predicate) or annotate the result with the receiver's own " +
|
|
939
|
+
"element type — and narrow the elements after";
|
|
940
|
+
if (!booleanFilter) {
|
|
941
|
+
if (!ts.isArrowFunction(argNode) && !ts.isFunctionExpression(argNode)) {
|
|
942
|
+
lowerer.unsupported("SC1090", argNode, `narrowing '.filter' through a callback VALUE (only an inline callback whose predicate ` +
|
|
943
|
+
`the checker inferred can re-tag — ${annotateEscape})`);
|
|
944
|
+
}
|
|
945
|
+
if (argNode.type) {
|
|
946
|
+
lowerer.unsupported("SC1090", argNode, `narrowing '.filter' with a hand-written type predicate (a written 'x is T' is an ` +
|
|
947
|
+
`unchecked assertion nothing validates at runtime — ${annotateEscape})`);
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
if (valueT.kind !== "union" || lowerer.armTag(valueT.unionId, outElem) < 0) {
|
|
951
|
+
const multiArm = outElem.kind === "union";
|
|
952
|
+
lowerer.unsupported("SC1090", call, `'.filter' narrowing '${lowerer.fmt(elem)}' elements to ` +
|
|
953
|
+
`${multiArm ? `the multi-arm '${lowerer.fmt(outElem)}'` : `'${lowerer.fmt(outElem)}'`} ` +
|
|
954
|
+
`(${multiArm ? "only a SINGLE arm re-tags" : "only a single runtime arm re-tags"} — ${annotateEscape})`);
|
|
955
|
+
}
|
|
956
|
+
if (booleanFilter) {
|
|
957
|
+
const arms = lowerer.unions.get(valueT.unionId)?.arms ?? [];
|
|
958
|
+
if (arms.some((arm) => !typeEquals(arm, outElem) && !isUnitType(arm))) {
|
|
959
|
+
lowerer.unsupported("SC1090", call, `'.filter(Boolean)' narrowing '${lowerer.fmt(elem)}' to '${lowerer.fmt(outElem)}' ` +
|
|
960
|
+
"when another truthy arm could survive");
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
return outElem;
|
|
964
|
+
}
|
|
965
|
+
/** A native closure for the ambient Boolean callback. Reifying the
|
|
966
|
+
* JavaScript constructor itself is unnecessary; Array#filter only observes
|
|
967
|
+
* its one-argument ToBoolean result. */
|
|
968
|
+
function truthyFilterCallback(lowerer, valueT, loc) {
|
|
969
|
+
const fnT = { kind: "func", params: [valueT], ret: BOOL };
|
|
970
|
+
const key = `filterBoolean:${typeKey(valueT)}`;
|
|
971
|
+
let name = lowerer.arrHofHelpers.get(key);
|
|
972
|
+
if (!name) {
|
|
973
|
+
name = `%arr.filterBoolean.${lowerer.arrHofHelpers.size}`;
|
|
974
|
+
lowerer.arrHofHelpers.set(key, name);
|
|
975
|
+
const value = varRef("v.0", valueT, loc);
|
|
976
|
+
lowerer.liftedFns.push({
|
|
977
|
+
name,
|
|
978
|
+
params: [{ localId: "v.0", name: "v", type: valueT }],
|
|
979
|
+
returnType: BOOL,
|
|
980
|
+
locals: [{ id: "v.0", name: "v", type: valueT, mutable: false }],
|
|
981
|
+
body: [{ kind: "return", value: { kind: "toBool", operand: value, type: BOOL, loc }, loc }],
|
|
982
|
+
loc,
|
|
983
|
+
});
|
|
984
|
+
}
|
|
985
|
+
return { kind: "closure", fnName: name, captures: [], type: fnT, loc };
|
|
986
|
+
}
|
|
602
987
|
/** The predicate result kinds `.filter()` accepts. JS applies ToBoolean to
|
|
603
988
|
* whatever the callback answers, so a bool is not required: the scalars and
|
|
604
989
|
* the reference kinds have constant or by-value answers, and a union is fine
|
|
@@ -644,14 +1029,14 @@ function filterCond(call, fnRet, loc) {
|
|
|
644
1029
|
* combo. Named `%arr.<method>.<n>` ('%' keeps it out of the user
|
|
645
1030
|
* namespace); rides `liftedFns` into the module like a lifted lambda (it
|
|
646
1031
|
* is a plain function — no captures). */
|
|
647
|
-
function arrayHofHelper(lowerer, method, elem, fnRet, arity, loc) {
|
|
648
|
-
const key = `${method}:${typeKey(elem)}:${typeKey(fnRet)}:${arity}`;
|
|
1032
|
+
function arrayHofHelper(lowerer, method, elem, fnRet, arity, loc, outElem = fnRet) {
|
|
1033
|
+
const key = `${method}:${typeKey(elem)}:${typeKey(fnRet)}:${arity}:${typeKey(outElem)}`;
|
|
649
1034
|
const existing = lowerer.arrHofHelpers.get(key);
|
|
650
1035
|
if (existing)
|
|
651
1036
|
return existing;
|
|
652
1037
|
const name = `%arr.${method}.${lowerer.arrHofHelpers.size}`;
|
|
653
1038
|
lowerer.arrHofHelpers.set(key, name);
|
|
654
|
-
lowerer.liftedFns.push(buildArrayHofFn(lowerer, name, method, elem, fnRet, arity, loc));
|
|
1039
|
+
lowerer.liftedFns.push(buildArrayHofFn(lowerer, name, method, elem, fnRet, arity, loc, outElem));
|
|
655
1040
|
return name;
|
|
656
1041
|
}
|
|
657
1042
|
/** READ-ONLY array methods on TUPLE receivers — `t.slice(...)` and
|
|
@@ -690,7 +1075,7 @@ export function lowerTupleReadMethodCall(lowerer, call, access) {
|
|
|
690
1075
|
receiver ??= lowerer.lowerExpr(access.expression);
|
|
691
1076
|
if (receiver.type.kind !== "record")
|
|
692
1077
|
return null;
|
|
693
|
-
if (!
|
|
1078
|
+
if (!isSafeToRepeat(receiver)) {
|
|
694
1079
|
lowerer.noLowering(`${lowerer.checker.typeToString(lowerer.typeOf(access.expression))}.${name}`, call, "tuple receivers snapshot per position, so only effect-free receivers lower — bind the tuple to a const first");
|
|
695
1080
|
}
|
|
696
1081
|
const loc = locOf(call);
|
|
@@ -743,13 +1128,14 @@ export function lowerTupleReadMethodCall(lowerer, call, access) {
|
|
|
743
1128
|
lowerer.noLowering(`.map with ${call.arguments.length} arguments`, call, "the thisArg parameter has no lowering — use an arrow function");
|
|
744
1129
|
}
|
|
745
1130
|
const argNode = call.arguments[0];
|
|
746
|
-
const { fnArg, arity } = hofCallbackArg(lowerer, argNode, [snapshotElem], arrayOf(snapshotElem));
|
|
1131
|
+
const { fnArg, arity } = hofCallbackArg(lowerer, argNode, [arrayValueType(lowerer, snapshotElem)], arrayOf(snapshotElem));
|
|
747
1132
|
const fnRet = fnArg.type.ret;
|
|
748
1133
|
if (fnRet.kind === "void" || fnRet.kind === "func")
|
|
749
1134
|
lowerer.badType(call, lowerer.typeOf(call));
|
|
750
1135
|
fenceProducedArrayElem(lowerer, call, "'.map()'", fnRet);
|
|
751
|
-
const
|
|
752
|
-
|
|
1136
|
+
const outElem = callbackArrayElem(lowerer, call, fnRet);
|
|
1137
|
+
const helper = arrayHofHelper(lowerer, "map", snapshotElem, fnRet, arity, loc, outElem);
|
|
1138
|
+
return { kind: "call", callee: helper, args: [snapshot, fnArg], type: arrayOf(outElem), loc };
|
|
753
1139
|
}
|
|
754
1140
|
/** An OOB-SAFE indexed read: `xs[i]`
|
|
755
1141
|
* answers the interned `elem | undefined` union — the element when `i`
|
|
@@ -764,54 +1150,13 @@ export function lowerSafeIndexRead(lowerer, arr, index, loc) {
|
|
|
764
1150
|
const elem = arr.type.elem;
|
|
765
1151
|
if (elem.kind === "void" || elem.kind === "dyn")
|
|
766
1152
|
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;
|
|
1153
|
+
const resultT = arrayValueType(lowerer, elem);
|
|
775
1154
|
const key = `idxOr:${typeKey(elem)}`;
|
|
776
1155
|
let name = lowerer.arrHofHelpers.get(key);
|
|
777
1156
|
if (!name) {
|
|
778
1157
|
name = `%arr.idxOr.${lowerer.arrHofHelpers.size}`;
|
|
779
1158
|
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
|
-
];
|
|
1159
|
+
const arrT = arr.type;
|
|
815
1160
|
lowerer.liftedFns.push({
|
|
816
1161
|
name,
|
|
817
1162
|
params: [
|
|
@@ -820,12 +1165,10 @@ export function lowerSafeIndexRead(lowerer, arr, index, loc) {
|
|
|
820
1165
|
],
|
|
821
1166
|
returnType: resultT,
|
|
822
1167
|
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 },
|
|
1168
|
+
{ id: "a.0", name: "a", type: arrT, mutable: false },
|
|
1169
|
+
{ id: "i.0", name: "i", type: F64, mutable: false },
|
|
827
1170
|
],
|
|
828
|
-
body,
|
|
1171
|
+
body: [{ kind: "return", value: arrayValueRead(lowerer, varRef("a.0", arrT, loc), varRef("i.0", F64, loc), elem, loc), loc }],
|
|
829
1172
|
loc,
|
|
830
1173
|
});
|
|
831
1174
|
}
|
|
@@ -859,7 +1202,7 @@ function arrayConcatHelper(lowerer, elem, shape, loc) {
|
|
|
859
1202
|
kind: "exprStmt",
|
|
860
1203
|
expr: {
|
|
861
1204
|
kind: "arrIntrinsic",
|
|
862
|
-
method: s === "a" ? "
|
|
1205
|
+
method: s === "a" ? "concatSpread" : "push",
|
|
863
1206
|
receiver: varRef("out.0", arrT, loc),
|
|
864
1207
|
args: [src],
|
|
865
1208
|
type: F64,
|
|
@@ -887,9 +1230,10 @@ function arrayConcatHelper(lowerer, elem, shape, loc) {
|
|
|
887
1230
|
* `arity` is the callback's declared parameter count (1–3): the loop
|
|
888
1231
|
* passes the index and the receiver itself after the element when the
|
|
889
1232
|
* callback names them, exactly the arguments JS supplies. */
|
|
890
|
-
function buildArrayHofFn(lowerer, name, method, elem, fnRet, arity, loc) {
|
|
1233
|
+
function buildArrayHofFn(lowerer, name, method, elem, fnRet, arity, loc, outElem) {
|
|
891
1234
|
const arrT = arrayOf(elem);
|
|
892
|
-
const
|
|
1235
|
+
const valueT = arrayValueType(lowerer, elem);
|
|
1236
|
+
const fnT = funcOf([arrayValueType(lowerer, elem), F64, arrT].slice(0, arity), fnRet);
|
|
893
1237
|
const locals = [
|
|
894
1238
|
{ id: "a.0", name: "a", type: arrT, mutable: true },
|
|
895
1239
|
{ id: "f.0", name: "f", type: fnT, mutable: true },
|
|
@@ -907,25 +1251,14 @@ function buildArrayHofFn(lowerer, name, method, elem, fnRet, arity, loc) {
|
|
|
907
1251
|
type: fnRet,
|
|
908
1252
|
loc,
|
|
909
1253
|
});
|
|
910
|
-
const getElem =
|
|
911
|
-
|
|
1254
|
+
const getElem = arrayValueRead(lowerer, varRef("a.0", arrT, loc), varRef("i.0", F64, loc), elem, loc);
|
|
1255
|
+
const hasElem = {
|
|
1256
|
+
kind: "arrayHas",
|
|
912
1257
|
arr: varRef("a.0", arrT, loc),
|
|
913
1258
|
index: varRef("i.0", F64, loc),
|
|
914
|
-
type:
|
|
1259
|
+
type: BOOL,
|
|
915
1260
|
loc,
|
|
916
1261
|
};
|
|
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
1262
|
const readLen = {
|
|
930
1263
|
kind: "varDecl",
|
|
931
1264
|
localId: "n.0",
|
|
@@ -935,41 +1268,85 @@ function buildArrayHofFn(lowerer, name, method, elem, fnRet, arity, loc) {
|
|
|
935
1268
|
const forLoop = (body) => countedFor(loc, varRef("n.0", F64, loc), () => body);
|
|
936
1269
|
let returnType;
|
|
937
1270
|
let body;
|
|
938
|
-
if (method === "map") {
|
|
939
|
-
const outT = arrayOf(
|
|
940
|
-
locals.push({ id: "out.0", name: "out", type: outT, mutable: false });
|
|
1271
|
+
if (method === "map" || method === "flatMap") {
|
|
1272
|
+
const outT = arrayOf(outElem);
|
|
1273
|
+
locals.push({ id: "out.0", name: "out", type: outT, mutable: false }, { id: "mapped.0", name: "mapped", type: fnRet, mutable: false });
|
|
941
1274
|
returnType = outT;
|
|
942
1275
|
body = [
|
|
943
1276
|
{ kind: "varDecl", localId: "out.0", init: { kind: "arrayLit", elems: [], type: outT, loc }, loc },
|
|
944
1277
|
readLen,
|
|
945
|
-
|
|
1278
|
+
...(method === "map" ? [{ kind: "arraySetLength", arr: varRef("out.0", outT, loc), length: varRef("n.0", F64, loc), loc }] : []),
|
|
1279
|
+
forLoop([
|
|
1280
|
+
{
|
|
1281
|
+
kind: "if",
|
|
1282
|
+
cond: hasElem,
|
|
1283
|
+
then: [
|
|
1284
|
+
{ kind: "varDecl", localId: "mapped.0", init: callF(getElem), loc },
|
|
1285
|
+
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),
|
|
1286
|
+
],
|
|
1287
|
+
else_: null,
|
|
1288
|
+
loc,
|
|
1289
|
+
},
|
|
1290
|
+
]),
|
|
946
1291
|
{ kind: "return", value: varRef("out.0", outT, loc), loc },
|
|
947
1292
|
];
|
|
948
1293
|
}
|
|
949
1294
|
else if (method === "filter") {
|
|
950
|
-
|
|
951
|
-
|
|
1295
|
+
const outT = arrayOf(outElem);
|
|
1296
|
+
locals.push({ id: "out.0", name: "out", type: outT, mutable: false }, { id: "v.0", name: "v", type: valueT, mutable: false });
|
|
1297
|
+
returnType = outT;
|
|
1298
|
+
const retained = typeEquals(outElem, elem)
|
|
1299
|
+
? varRef("v.0", valueT, loc)
|
|
1300
|
+
: valueT.kind === "union"
|
|
1301
|
+
? {
|
|
1302
|
+
kind: "unionNarrow",
|
|
1303
|
+
unionId: valueT.unionId,
|
|
1304
|
+
tag: lowerer.armTag(valueT.unionId, outElem),
|
|
1305
|
+
value: varRef("v.0", valueT, loc),
|
|
1306
|
+
type: outElem,
|
|
1307
|
+
loc,
|
|
1308
|
+
}
|
|
1309
|
+
: varRef("v.0", valueT, loc);
|
|
952
1310
|
body = [
|
|
953
|
-
{ kind: "varDecl", localId: "out.0", init: { kind: "arrayLit", elems: [], type:
|
|
1311
|
+
{ kind: "varDecl", localId: "out.0", init: { kind: "arrayLit", elems: [], type: outT, loc }, loc },
|
|
954
1312
|
readLen,
|
|
955
1313
|
forLoop([
|
|
956
|
-
{ kind: "varDecl", localId: "v.0", init: getElem, loc },
|
|
957
1314
|
{
|
|
958
1315
|
kind: "if",
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
1316
|
+
cond: hasElem,
|
|
1317
|
+
then: [
|
|
1318
|
+
{ kind: "varDecl", localId: "v.0", init: getElem, loc },
|
|
1319
|
+
{
|
|
1320
|
+
kind: "if",
|
|
1321
|
+
// ToBoolean over the predicate's answer — inert when it already
|
|
1322
|
+
// returned bool, the per-union helper when it returned a union.
|
|
1323
|
+
cond: filterCond(callF(varRef("v.0", valueT, loc)), fnRet, loc),
|
|
1324
|
+
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)],
|
|
1325
|
+
else_: null,
|
|
1326
|
+
loc,
|
|
1327
|
+
},
|
|
1328
|
+
],
|
|
963
1329
|
else_: null,
|
|
964
1330
|
loc,
|
|
965
1331
|
},
|
|
966
1332
|
]),
|
|
967
|
-
{ kind: "return", value: varRef("out.0",
|
|
1333
|
+
{ kind: "return", value: varRef("out.0", outT, loc), loc },
|
|
968
1334
|
];
|
|
969
1335
|
}
|
|
970
1336
|
else {
|
|
971
1337
|
returnType = VOID;
|
|
972
|
-
body = [
|
|
1338
|
+
body = [
|
|
1339
|
+
readLen,
|
|
1340
|
+
forLoop([
|
|
1341
|
+
{
|
|
1342
|
+
kind: "if",
|
|
1343
|
+
cond: hasElem,
|
|
1344
|
+
then: [{ kind: "exprStmt", expr: callF(getElem), loc }],
|
|
1345
|
+
else_: null,
|
|
1346
|
+
loc,
|
|
1347
|
+
},
|
|
1348
|
+
]),
|
|
1349
|
+
];
|
|
973
1350
|
}
|
|
974
1351
|
return { name, params, returnType, locals, body, loc };
|
|
975
1352
|
}
|
|
@@ -985,14 +1362,14 @@ function buildArrayHofFn(lowerer, name, method, elem, fnRet, arity, loc) {
|
|
|
985
1362
|
* backwards (`i = n - 1; i >= 0; i--`), exactly the es2023 spec's
|
|
986
1363
|
* descending index walk. All require a bool-returning callback
|
|
987
1364
|
* (JS's ToBoolean of arbitrary predicate results has no lowering). */
|
|
988
|
-
function lowerArrayFindLikeCall(lowerer, call, access, method, elem) {
|
|
1365
|
+
function lowerArrayFindLikeCall(lowerer, call, access, method, elem, bindUntyped = false) {
|
|
989
1366
|
const loc = locOf(call);
|
|
990
1367
|
const receiver = lowerer.lowerExpr(access.expression);
|
|
991
1368
|
const arrT = arrayOf(elem);
|
|
992
1369
|
const argNode = call.arguments[0];
|
|
993
1370
|
if (!argNode)
|
|
994
1371
|
lowerer.unsupported("SC1090", call, "this call form"); // tsc-guarded
|
|
995
|
-
const { fnArg, arity } = hofCallbackArg(lowerer, argNode, [elem], arrT);
|
|
1372
|
+
const { fnArg, arity } = hofCallbackArg(lowerer, argNode, [arrayValueType(lowerer, elem)], arrT, bindUntyped);
|
|
996
1373
|
// JS takes the ToBoolean of the predicate's result — allowed wherever
|
|
997
1374
|
// that ToBoolean has a static answer (bool passes through; f64/string
|
|
998
1375
|
// by value; a truthy-answerable union by its arm — the
|
|
@@ -1022,21 +1399,13 @@ function lowerArrayFindLikeCall(lowerer, call, access, method, elem) {
|
|
|
1022
1399
|
// found element passes through untouched; otherwise it wraps into its
|
|
1023
1400
|
// arm. A union element whose result union differs would need the
|
|
1024
1401
|
// union-into-union re-tag that doesn't exist — fenced.
|
|
1025
|
-
const resultT = lowerer.irTypeOf(call);
|
|
1402
|
+
const resultT = bindUntyped ? arrayValueType(lowerer, elem) : lowerer.irTypeOf(call);
|
|
1026
1403
|
if (resultT.kind !== "union")
|
|
1027
1404
|
lowerer.badType(call, lowerer.typeOf(call)); // defensive: T | undefined always maps to a union
|
|
1028
1405
|
const undefTag = lowerer.armTag(resultT.unionId, UNDEFINED_T);
|
|
1029
1406
|
if (undefTag < 0)
|
|
1030
1407
|
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);
|
|
1408
|
+
const helper = findHelper(lowerer, elem, resultT, undefTag, fnRet, arity, last, loc);
|
|
1040
1409
|
return { kind: "call", callee: helper, args: [receiver, fnArg], type: resultT, loc };
|
|
1041
1410
|
}
|
|
1042
1411
|
/** ToBoolean of a predicate result inside a synthesized HOF helper — the
|
|
@@ -1053,7 +1422,7 @@ function predToBool(e) {
|
|
|
1053
1422
|
* for (i = 0; i < n; i++) { v = a[i]; if (f(v)) return <v as result arm>; }
|
|
1054
1423
|
* return <undefined arm>;
|
|
1055
1424
|
*/
|
|
1056
|
-
function findHelper(lowerer, elem, resultT,
|
|
1425
|
+
function findHelper(lowerer, elem, resultT, undefTag, fnRet, arity, last, loc) {
|
|
1057
1426
|
const method = last ? "findLast" : "find";
|
|
1058
1427
|
const key = `${method}:${typeKey(elem)}:${typeKey(resultT)}:${typeKey(fnRet)}:${arity}`;
|
|
1059
1428
|
const existing = lowerer.arrHofHelpers.get(key);
|
|
@@ -1062,11 +1431,10 @@ function findHelper(lowerer, elem, resultT, foundTag, undefTag, fnRet, arity, la
|
|
|
1062
1431
|
const name = `%arr.${method}.${lowerer.arrHofHelpers.size}`;
|
|
1063
1432
|
lowerer.arrHofHelpers.set(key, name);
|
|
1064
1433
|
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 };
|
|
1434
|
+
const fnT = funcOf([arrayValueType(lowerer, elem), F64, arrT].slice(0, arity), fnRet);
|
|
1435
|
+
const valueT = arrayValueType(lowerer, elem);
|
|
1436
|
+
const v = varRef("v.0", valueT, loc);
|
|
1437
|
+
const found = lowerer.coerceToExpected(v, resultT);
|
|
1070
1438
|
const miss = {
|
|
1071
1439
|
kind: "unionWrap",
|
|
1072
1440
|
unionId: resultT.unionId,
|
|
@@ -1075,8 +1443,8 @@ function findHelper(lowerer, elem, resultT, foundTag, undefTag, fnRet, arity, la
|
|
|
1075
1443
|
type: resultT,
|
|
1076
1444
|
loc,
|
|
1077
1445
|
};
|
|
1078
|
-
const
|
|
1079
|
-
{ kind: "varDecl", localId: "v.0", init:
|
|
1446
|
+
const visitBody = [
|
|
1447
|
+
{ kind: "varDecl", localId: "v.0", init: arrayValueRead(lowerer, varRef("a.0", arrT, loc), varRef("i.0", F64, loc), elem, loc), loc },
|
|
1080
1448
|
{
|
|
1081
1449
|
kind: "if",
|
|
1082
1450
|
cond: predToBool({
|
|
@@ -1091,6 +1459,7 @@ function findHelper(lowerer, elem, resultT, foundTag, undefTag, fnRet, arity, la
|
|
|
1091
1459
|
loc,
|
|
1092
1460
|
},
|
|
1093
1461
|
];
|
|
1462
|
+
const visit = visitBody;
|
|
1094
1463
|
const loop = last
|
|
1095
1464
|
? reverseCountedForLoop(loc, visit)
|
|
1096
1465
|
: countedFor(loc, varRef("n.0", F64, loc), () => visit);
|
|
@@ -1111,7 +1480,7 @@ function findHelper(lowerer, elem, resultT, foundTag, undefTag, fnRet, arity, la
|
|
|
1111
1480
|
{ id: "f.0", name: "f", type: fnT, mutable: true },
|
|
1112
1481
|
{ id: "n.0", name: "n", type: F64, mutable: false },
|
|
1113
1482
|
{ id: "i.0", name: "i", type: F64, mutable: true },
|
|
1114
|
-
{ id: "v.0", name: "v", type:
|
|
1483
|
+
{ id: "v.0", name: "v", type: valueT, mutable: false },
|
|
1115
1484
|
],
|
|
1116
1485
|
body,
|
|
1117
1486
|
loc,
|
|
@@ -1132,13 +1501,13 @@ function findIndexHelper(lowerer, elem, fnRet, arity, last, loc) {
|
|
|
1132
1501
|
const name = `%arr.${method}.${lowerer.arrHofHelpers.size}`;
|
|
1133
1502
|
lowerer.arrHofHelpers.set(key, name);
|
|
1134
1503
|
const arrT = arrayOf(elem);
|
|
1135
|
-
const fnT = funcOf([elem, F64, arrT].slice(0, arity), fnRet);
|
|
1504
|
+
const fnT = funcOf([arrayValueType(lowerer, elem), F64, arrT].slice(0, arity), fnRet);
|
|
1136
1505
|
const visit = [{
|
|
1137
1506
|
kind: "if",
|
|
1138
1507
|
cond: predToBool({
|
|
1139
1508
|
kind: "callValue",
|
|
1140
1509
|
callee: varRef("f.0", fnT, loc),
|
|
1141
|
-
args: [
|
|
1510
|
+
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
1511
|
type: fnRet,
|
|
1143
1512
|
loc,
|
|
1144
1513
|
}),
|
|
@@ -1185,11 +1554,11 @@ function someEveryHelper(lowerer, method, elem, fnRet, arity, loc) {
|
|
|
1185
1554
|
const name = `%arr.${method}.${lowerer.arrHofHelpers.size}`;
|
|
1186
1555
|
lowerer.arrHofHelpers.set(key, name);
|
|
1187
1556
|
const arrT = arrayOf(elem);
|
|
1188
|
-
const fnT = funcOf([elem, F64, arrT].slice(0, arity), fnRet);
|
|
1557
|
+
const fnT = funcOf([arrayValueType(lowerer, elem), F64, arrT].slice(0, arity), fnRet);
|
|
1189
1558
|
const callF = predToBool({
|
|
1190
1559
|
kind: "callValue",
|
|
1191
1560
|
callee: varRef("f.0", fnT, loc),
|
|
1192
|
-
args: [
|
|
1561
|
+
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
1562
|
type: fnRet,
|
|
1194
1563
|
loc,
|
|
1195
1564
|
});
|
|
@@ -1198,8 +1567,14 @@ function someEveryHelper(lowerer, method, elem, fnRet, arity, loc) {
|
|
|
1198
1567
|
countedFor(loc, varRef("n.0", F64, loc), () => [
|
|
1199
1568
|
{
|
|
1200
1569
|
kind: "if",
|
|
1201
|
-
cond:
|
|
1202
|
-
then: [{
|
|
1570
|
+
cond: currentArrayIndexPresent(arrT, loc),
|
|
1571
|
+
then: [{
|
|
1572
|
+
kind: "if",
|
|
1573
|
+
cond: method === "some" ? callF : { kind: "unary", op: "!", operand: callF, type: BOOL, loc },
|
|
1574
|
+
then: [{ kind: "return", value: boolLit(method === "some", loc), loc }],
|
|
1575
|
+
else_: null,
|
|
1576
|
+
loc,
|
|
1577
|
+
}],
|
|
1203
1578
|
else_: null,
|
|
1204
1579
|
loc,
|
|
1205
1580
|
},
|
|
@@ -1237,7 +1612,7 @@ function lowerArrayFlatMapCall(lowerer, call, access, elem) {
|
|
|
1237
1612
|
const argNode = call.arguments[0];
|
|
1238
1613
|
if (!argNode)
|
|
1239
1614
|
lowerer.unsupported("SC1090", call, "this call form"); // tsc-guarded
|
|
1240
|
-
const { fnArg, arity } = hofCallbackArg(lowerer, argNode, [elem], arrT);
|
|
1615
|
+
const { fnArg, arity } = hofCallbackArg(lowerer, argNode, [arrayValueType(lowerer, elem)], arrT);
|
|
1241
1616
|
const fnRet = fnArg.type.ret;
|
|
1242
1617
|
if (fnRet.kind === "union") {
|
|
1243
1618
|
const def = lowerer.unions.get(fnRet.unionId);
|
|
@@ -1247,12 +1622,13 @@ function lowerArrayFlatMapCall(lowerer, call, access, elem) {
|
|
|
1247
1622
|
}
|
|
1248
1623
|
}
|
|
1249
1624
|
if (fnRet.kind !== "array") {
|
|
1250
|
-
//
|
|
1625
|
+
// Scalar results append densely; source holes do not produce output slots.
|
|
1251
1626
|
if (fnRet.kind === "void" || fnRet.kind === "func")
|
|
1252
1627
|
lowerer.badType(call, lowerer.typeOf(call));
|
|
1253
1628
|
fenceProducedArrayElem(lowerer, call, "'.flatMap()'", fnRet);
|
|
1254
|
-
const
|
|
1255
|
-
|
|
1629
|
+
const outElem = callbackArrayElem(lowerer, call, fnRet);
|
|
1630
|
+
const helper = arrayHofHelper(lowerer, "flatMap", elem, fnRet, arity, loc, outElem);
|
|
1631
|
+
return { kind: "call", callee: helper, args: [receiver, fnArg], type: arrayOf(outElem), loc };
|
|
1256
1632
|
}
|
|
1257
1633
|
const helper = flatMapHelper(lowerer, elem, fnRet, arity, loc);
|
|
1258
1634
|
return { kind: "call", callee: helper, args: [receiver, fnArg], type: fnRet, loc };
|
|
@@ -1275,7 +1651,7 @@ function flatMapHelper(lowerer, elem, fnRet, arity, loc) {
|
|
|
1275
1651
|
lowerer.arrHofHelpers.set(key, name);
|
|
1276
1652
|
const arrT = arrayOf(elem);
|
|
1277
1653
|
const inner = fnRet.elem;
|
|
1278
|
-
const fnT = funcOf([elem, F64, arrT].slice(0, arity), fnRet);
|
|
1654
|
+
const fnT = funcOf([arrayValueType(lowerer, elem), F64, arrT].slice(0, arity), fnRet);
|
|
1279
1655
|
const innerLoop = {
|
|
1280
1656
|
kind: "for",
|
|
1281
1657
|
init: { kind: "varDecl", localId: "j.0", init: numLit(0, loc), loc },
|
|
@@ -1288,15 +1664,13 @@ function flatMapHelper(lowerer, elem, fnRet, arity, loc) {
|
|
|
1288
1664
|
},
|
|
1289
1665
|
body: [
|
|
1290
1666
|
{
|
|
1291
|
-
kind: "
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
receiver: varRef("out.0", fnRet, loc),
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
loc,
|
|
1299
|
-
},
|
|
1667
|
+
kind: "if",
|
|
1668
|
+
cond: arrayIndexPresent(varRef("r.0", fnRet, loc), varRef("j.0", F64, loc), loc),
|
|
1669
|
+
then: [
|
|
1670
|
+
{ kind: "varDecl", localId: "inner.0", init: arrayValueRead(lowerer, varRef("r.0", fnRet, loc), varRef("j.0", F64, loc), inner, loc), loc },
|
|
1671
|
+
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),
|
|
1672
|
+
],
|
|
1673
|
+
else_: null,
|
|
1300
1674
|
loc,
|
|
1301
1675
|
},
|
|
1302
1676
|
],
|
|
@@ -1307,24 +1681,32 @@ function flatMapHelper(lowerer, elem, fnRet, arity, loc) {
|
|
|
1307
1681
|
readLenStmt(arrT, loc),
|
|
1308
1682
|
countedFor(loc, varRef("n.0", F64, loc), () => [
|
|
1309
1683
|
{
|
|
1310
|
-
kind: "
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1684
|
+
kind: "if",
|
|
1685
|
+
cond: currentArrayIndexPresent(arrT, loc),
|
|
1686
|
+
then: [
|
|
1687
|
+
{
|
|
1688
|
+
kind: "varDecl",
|
|
1689
|
+
localId: "r.0",
|
|
1690
|
+
init: {
|
|
1691
|
+
kind: "callValue",
|
|
1692
|
+
callee: varRef("f.0", fnT, loc),
|
|
1693
|
+
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),
|
|
1694
|
+
type: fnRet,
|
|
1695
|
+
loc,
|
|
1696
|
+
},
|
|
1697
|
+
loc,
|
|
1698
|
+
},
|
|
1699
|
+
{
|
|
1700
|
+
kind: "varDecl",
|
|
1701
|
+
localId: "m.0",
|
|
1702
|
+
init: { kind: "arrIntrinsic", method: "length", receiver: varRef("r.0", fnRet, loc), args: [], type: F64, loc },
|
|
1703
|
+
loc,
|
|
1704
|
+
},
|
|
1705
|
+
innerLoop,
|
|
1706
|
+
],
|
|
1707
|
+
else_: null,
|
|
1325
1708
|
loc,
|
|
1326
1709
|
},
|
|
1327
|
-
innerLoop,
|
|
1328
1710
|
]),
|
|
1329
1711
|
{ kind: "return", value: varRef("out.0", fnRet, loc), loc },
|
|
1330
1712
|
];
|
|
@@ -1344,6 +1726,7 @@ function flatMapHelper(lowerer, elem, fnRet, arity, loc) {
|
|
|
1344
1726
|
{ id: "r.0", name: "r", type: fnRet, mutable: false },
|
|
1345
1727
|
{ id: "m.0", name: "m", type: F64, mutable: false },
|
|
1346
1728
|
{ id: "j.0", name: "j", type: F64, mutable: true },
|
|
1729
|
+
{ id: "inner.0", name: "inner", type: arrayValueType(lowerer, inner), mutable: false },
|
|
1347
1730
|
],
|
|
1348
1731
|
body,
|
|
1349
1732
|
loc,
|
|
@@ -1363,13 +1746,12 @@ function lowerArrayReduceCall(lowerer, call, access, method, elem) {
|
|
|
1363
1746
|
if (!argNode)
|
|
1364
1747
|
lowerer.unsupported("SC1090", call, "this call form"); // tsc-guarded
|
|
1365
1748
|
const hasInit = call.arguments.length === 2;
|
|
1366
|
-
const accT =
|
|
1749
|
+
const accT = lowerer.runtimeOptionalReduceTypes.get(call) ??
|
|
1750
|
+
(hasInit ? lowerer.irTypeOf(call) : arrayValueType(lowerer, elem));
|
|
1367
1751
|
if (accT.kind === "void" || accT.kind === "func")
|
|
1368
1752
|
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);
|
|
1753
|
+
const { fnArg, arity } = hofCallbackArg(lowerer, argNode, [accT, arrayValueType(lowerer, elem)], arrT);
|
|
1754
|
+
const helper = reduceHelper(lowerer, method, elem, accT, fnArg.type.ret, arity, hasInit, loc);
|
|
1373
1755
|
const args = [receiver, fnArg];
|
|
1374
1756
|
if (hasInit)
|
|
1375
1757
|
args.push(lowerer.lowerExprExpecting(call.arguments[1], accT));
|
|
@@ -1385,19 +1767,19 @@ function lowerArrayReduceCall(lowerer, call, access, method, elem) {
|
|
|
1385
1767
|
* reduce walks 0→n-1, reduceRight n-1→0; the seed without an initial
|
|
1386
1768
|
* value is a[0] / a[n-1] and the loop starts one past it. The empty-array
|
|
1387
1769
|
* 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"}`;
|
|
1770
|
+
function reduceHelper(lowerer, method, elem, accT, fnRet, arity, hasInit, loc) {
|
|
1771
|
+
const key = `${method}:${typeKey(elem)}:${typeKey(accT)}:${typeKey(fnRet)}:${arity}:${hasInit ? "init" : "seed"}`;
|
|
1390
1772
|
const existing = lowerer.arrHofHelpers.get(key);
|
|
1391
1773
|
if (existing)
|
|
1392
1774
|
return existing;
|
|
1393
1775
|
const name = `%arr.${method}.${lowerer.arrHofHelpers.size}`;
|
|
1394
1776
|
lowerer.arrHofHelpers.set(key, name);
|
|
1395
1777
|
const arrT = arrayOf(elem);
|
|
1396
|
-
const fnT = funcOf([accT, elem, F64, arrT].slice(0, arity),
|
|
1778
|
+
const fnT = funcOf([accT, arrayValueType(lowerer, elem), F64, arrT].slice(0, arity), fnRet);
|
|
1397
1779
|
const n = varRef("n.0", F64, loc);
|
|
1398
1780
|
const i = varRef("i.0", F64, loc);
|
|
1399
1781
|
const right = method === "reduceRight";
|
|
1400
|
-
const at = (index) => (
|
|
1782
|
+
const at = (index) => arrayValueRead(lowerer, varRef("a.0", arrT, loc), index, elem, loc);
|
|
1401
1783
|
const locals = [
|
|
1402
1784
|
{ id: "a.0", name: "a", type: arrT, mutable: true },
|
|
1403
1785
|
{ id: "f.0", name: "f", type: fnT, mutable: true },
|
|
@@ -1411,50 +1793,27 @@ function reduceHelper(lowerer, method, elem, accT, arity, hasInit, loc) {
|
|
|
1411
1793
|
{ localId: "f.0", name: "f", type: fnT },
|
|
1412
1794
|
...(hasInit ? [{ localId: "z.0", name: "z", type: accT }] : []),
|
|
1413
1795
|
];
|
|
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.
|
|
1452
1796
|
const start = right
|
|
1453
|
-
? { kind: "bin", op: "-", left: n, right: numLit(
|
|
1454
|
-
: numLit(
|
|
1797
|
+
? { kind: "bin", op: "-", left: n, right: numLit(1, loc), type: F64, loc }
|
|
1798
|
+
: numLit(0, loc);
|
|
1799
|
+
const inRange = right
|
|
1800
|
+
? { kind: "bin", op: ">=", left: i, right: numLit(0, loc), type: BOOL, loc }
|
|
1801
|
+
: { kind: "bin", op: "<", left: i, right: n, type: BOOL, loc };
|
|
1802
|
+
const advance = { kind: "assign", localId: "i.0", value: { kind: "bin", op: right ? "-" : "+", left: i, right: numLit(1, loc), type: F64, loc }, loc };
|
|
1803
|
+
const seedStmts = [
|
|
1804
|
+
{ kind: "varDecl", localId: "i.0", init: start, loc },
|
|
1805
|
+
...(hasInit
|
|
1806
|
+
? [{ kind: "varDecl", localId: "acc.0", init: varRef("z.0", accT, loc), loc }]
|
|
1807
|
+
: [
|
|
1808
|
+
{ kind: "while", cond: { kind: "ternary", cond: inRange, then: { kind: "unary", op: "!", operand: currentArrayIndexPresent(arrT, loc), type: BOOL, loc }, else_: boolLit(false, loc), type: BOOL, loc }, body: [advance], loc },
|
|
1809
|
+
{ 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 },
|
|
1810
|
+
{ kind: "varDecl", localId: "acc.0", init: lowerer.coerceToExpected(at(i), accT), loc },
|
|
1811
|
+
advance,
|
|
1812
|
+
]),
|
|
1813
|
+
];
|
|
1455
1814
|
const loop = {
|
|
1456
1815
|
kind: "for",
|
|
1457
|
-
init:
|
|
1816
|
+
init: null,
|
|
1458
1817
|
cond: right
|
|
1459
1818
|
? { kind: "bin", op: ">=", left: i, right: numLit(0, loc), type: BOOL, loc }
|
|
1460
1819
|
: { kind: "bin", op: "<", left: i, right: n, type: BOOL, loc },
|
|
@@ -1464,20 +1823,20 @@ function reduceHelper(lowerer, method, elem, accT, arity, hasInit, loc) {
|
|
|
1464
1823
|
value: { kind: "bin", op: right ? "-" : "+", left: i, right: numLit(1, loc), type: F64, loc },
|
|
1465
1824
|
loc,
|
|
1466
1825
|
},
|
|
1467
|
-
body: [
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1826
|
+
body: [{ kind: "if", cond: currentArrayIndexPresent(arrT, loc), then: [
|
|
1827
|
+
{
|
|
1828
|
+
kind: "assign",
|
|
1829
|
+
localId: "acc.0",
|
|
1830
|
+
value: lowerer.coerceToExpected({
|
|
1831
|
+
kind: "callValue",
|
|
1832
|
+
callee: varRef("f.0", fnT, loc),
|
|
1833
|
+
args: [varRef("acc.0", accT, loc), at(i), i, varRef("a.0", arrT, loc)].slice(0, arity),
|
|
1834
|
+
type: fnRet,
|
|
1835
|
+
loc,
|
|
1836
|
+
}, accT),
|
|
1837
|
+
loc,
|
|
1838
|
+
},
|
|
1839
|
+
], else_: null, loc }],
|
|
1481
1840
|
loc,
|
|
1482
1841
|
};
|
|
1483
1842
|
const body = [
|
|
@@ -1493,19 +1852,20 @@ function reduceHelper(lowerer, method, elem, accT, arity, hasInit, loc) {
|
|
|
1493
1852
|
* function like the other array HOFs. sort mutates and returns the receiver;
|
|
1494
1853
|
* toSorted takes its shallow snapshot INSIDE the helper, after the receiver
|
|
1495
1854
|
* 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
|
-
*
|
|
1855
|
+
* returns that copy without touching the receiver. The helper uses a stable
|
|
1856
|
+
* bottom-up merge sort with an ordered-boundary check: ordered inputs use a
|
|
1857
|
+
* linear number of comparator calls, but buffer movement remains O(n log n)
|
|
1858
|
+
* even when every boundary is already ordered. During a merge, an element
|
|
1859
|
+
* moves right only while cmp(left, right) > 0, so a NaN or 0 result keeps
|
|
1860
|
+
* the left element first. Undefined values sink without reaching the user
|
|
1861
|
+
* comparator. The callback sequence differs from V8's TimSort, so exact
|
|
1862
|
+
* order and count parity are not claimed; results agree for consistent
|
|
1863
|
+
* comparators. The comparator-less form lowers for STRING elements only —
|
|
1864
|
+
* JS's default converts every element to string and compares UTF-16 units,
|
|
1865
|
+
* so the interned synthesized comparator selects the runtime's code-unit
|
|
1866
|
+
* ordering rather than scriptc's documented code-point relational
|
|
1867
|
+
* operators. For numbers that default is the notorious string sort
|
|
1868
|
+
* ([10, 9, 1] → [1, 10, 9]), deliberately fenced toward an explicit
|
|
1509
1869
|
* comparator. */
|
|
1510
1870
|
function lowerArraySortCall(lowerer, call, access, elem, arrT) {
|
|
1511
1871
|
const loc = locOf(call);
|
|
@@ -1599,441 +1959,6 @@ function defaultStringCmpHelper(lowerer, loc) {
|
|
|
1599
1959
|
});
|
|
1600
1960
|
return name;
|
|
1601
1961
|
}
|
|
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
|
-
/** Uint8Array.prototype.toSorted. The receiver/comparator expressions are
|
|
1762
|
-
* evaluated before entering the helper; the helper snapshots with
|
|
1763
|
-
* TypedArray.prototype.slice before its first comparison, then performs
|
|
1764
|
-
* the same stable insertion walk as Array.toSorted. Uint8Array's default
|
|
1765
|
-
* comparator is numeric ascending, so its comparator-less form needs no
|
|
1766
|
-
* string-conversion machinery. */
|
|
1767
|
-
function lowerBytesToSortedCall(lowerer, call, access, bytesT) {
|
|
1768
|
-
const loc = locOf(call);
|
|
1769
|
-
if (call.arguments.length > 1 || call.arguments.some(ts.isSpreadElement)) {
|
|
1770
|
-
lowerer.noLowering(`.toSorted with ${call.arguments.length} arguments on Uint8Array`, call);
|
|
1771
|
-
}
|
|
1772
|
-
const receiver = lowerer.lowerExpr(access.expression);
|
|
1773
|
-
const undefinedArg = call.arguments[0]
|
|
1774
|
-
? lowerStaticallyUndefinedArg(lowerer, call.arguments[0])
|
|
1775
|
-
: null;
|
|
1776
|
-
if (call.arguments.length === 0 || undefinedArg) {
|
|
1777
|
-
const key = "bytes.toSorted:u8:default";
|
|
1778
|
-
let helper = lowerer.arrHofHelpers.get(key);
|
|
1779
|
-
if (!helper) {
|
|
1780
|
-
helper = `%bytes.toSorted.${lowerer.arrHofHelpers.size}`;
|
|
1781
|
-
lowerer.arrHofHelpers.set(key, helper);
|
|
1782
|
-
lowerer.liftedFns.push(buildBytesSortFn(helper, 0, false, loc));
|
|
1783
|
-
}
|
|
1784
|
-
if (!undefinedArg || droppableStatic(undefinedArg)) {
|
|
1785
|
-
return { kind: "call", callee: helper, args: [receiver], type: bytesT, loc };
|
|
1786
|
-
}
|
|
1787
|
-
// The default helper takes no comparator argument. Snapshot the
|
|
1788
|
-
// receiver into a hidden local so the discarded undefined argument
|
|
1789
|
-
// still evaluates after the receiver and before the helper call.
|
|
1790
|
-
const saved = lowerer.declareHiddenLocal("%bytesSortRecv", bytesT);
|
|
1791
|
-
const savedRef = {
|
|
1792
|
-
kind: "varRef",
|
|
1793
|
-
localId: saved.id,
|
|
1794
|
-
type: bytesT,
|
|
1795
|
-
loc,
|
|
1796
|
-
};
|
|
1797
|
-
return {
|
|
1798
|
-
kind: "seqExpr",
|
|
1799
|
-
stmts: [
|
|
1800
|
-
{ kind: "varDecl", localId: saved.id, init: receiver, loc },
|
|
1801
|
-
{ kind: "exprStmt", expr: undefinedArg, loc: undefinedArg.loc },
|
|
1802
|
-
],
|
|
1803
|
-
result: {
|
|
1804
|
-
kind: "call",
|
|
1805
|
-
callee: helper,
|
|
1806
|
-
args: [savedRef],
|
|
1807
|
-
type: bytesT,
|
|
1808
|
-
loc,
|
|
1809
|
-
},
|
|
1810
|
-
type: bytesT,
|
|
1811
|
-
loc,
|
|
1812
|
-
};
|
|
1813
|
-
}
|
|
1814
|
-
const argNode = call.arguments[0];
|
|
1815
|
-
const fnArg = lowerer.lowerExpr(argNode);
|
|
1816
|
-
if (fnArg.type.kind !== "func" ||
|
|
1817
|
-
fnArg.type.params.length > 2 ||
|
|
1818
|
-
!fnArg.type.params.every((p) => p.kind === "f64") ||
|
|
1819
|
-
fnArg.type.ret.kind !== "f64") {
|
|
1820
|
-
lowerer.badType(argNode, lowerer.typeOf(argNode));
|
|
1821
|
-
}
|
|
1822
|
-
const arity = fnArg.type.params.length;
|
|
1823
|
-
const key = `bytes.toSorted:u8:${arity}`;
|
|
1824
|
-
let helper = lowerer.arrHofHelpers.get(key);
|
|
1825
|
-
if (!helper) {
|
|
1826
|
-
helper = `%bytes.toSorted.${lowerer.arrHofHelpers.size}`;
|
|
1827
|
-
lowerer.arrHofHelpers.set(key, helper);
|
|
1828
|
-
lowerer.liftedFns.push(buildBytesSortFn(helper, arity, true, loc));
|
|
1829
|
-
}
|
|
1830
|
-
return {
|
|
1831
|
-
kind: "call",
|
|
1832
|
-
callee: helper,
|
|
1833
|
-
args: [receiver, fnArg],
|
|
1834
|
-
type: bytesT,
|
|
1835
|
-
loc,
|
|
1836
|
-
};
|
|
1837
|
-
}
|
|
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
1962
|
/** `n = a.length` — the once-up-front length read every array HOF loop
|
|
2038
1963
|
* starts with (locals a.0/n.0 by convention). */
|
|
2039
1964
|
function readLenStmt(arrT, loc) {
|
|
@@ -2111,14 +2036,6 @@ function lowerArrayAtCall(lowerer, call, access, elem) {
|
|
|
2111
2036
|
const undefTag = lowerer.armTag(resultT.unionId, UNDEFINED_T);
|
|
2112
2037
|
if (undefTag < 0)
|
|
2113
2038
|
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
2039
|
const key = `at:${typeKey(elem)}:${typeKey(resultT)}`;
|
|
2123
2040
|
let name = lowerer.arrHofHelpers.get(key);
|
|
2124
2041
|
if (!name) {
|
|
@@ -2128,10 +2045,7 @@ function lowerArrayAtCall(lowerer, call, access, elem) {
|
|
|
2128
2045
|
const t = varRef("t.0", F64, loc);
|
|
2129
2046
|
const i = varRef("i.0", F64, loc);
|
|
2130
2047
|
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 };
|
|
2048
|
+
const found = lowerer.coerceToExpected(arrayValueRead(lowerer, varRef("a.0", arrT, loc), t, elem, loc), resultT);
|
|
2135
2049
|
const miss = {
|
|
2136
2050
|
kind: "unionWrap",
|
|
2137
2051
|
unionId: resultT.unionId,
|
|
@@ -2183,12 +2097,6 @@ function lowerArrayAtCall(lowerer, call, access, elem) {
|
|
|
2183
2097
|
else_: null,
|
|
2184
2098
|
loc,
|
|
2185
2099
|
},
|
|
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
2100
|
{ kind: "return", value: found, loc },
|
|
2193
2101
|
];
|
|
2194
2102
|
lowerer.liftedFns.push({
|
|
@@ -2203,7 +2111,6 @@ function lowerArrayAtCall(lowerer, call, access, elem) {
|
|
|
2203
2111
|
{ id: "i.0", name: "i", type: F64, mutable: true },
|
|
2204
2112
|
{ id: "n.0", name: "n", type: F64, mutable: false },
|
|
2205
2113
|
{ id: "t.0", name: "t", type: F64, mutable: true },
|
|
2206
|
-
{ id: "v.0", name: "v", type: elem, mutable: false },
|
|
2207
2114
|
],
|
|
2208
2115
|
body,
|
|
2209
2116
|
loc,
|
|
@@ -2435,7 +2342,15 @@ export function lowerMapMethodCall(lowerer, call, access) {
|
|
|
2435
2342
|
const name = access.name.text;
|
|
2436
2343
|
if (!MAP_METHODS.has(name) && !MAP_ITER_METHODS.has(name))
|
|
2437
2344
|
return null;
|
|
2438
|
-
|
|
2345
|
+
let receiverIr = lowerer.mapTypeOf(lowerer.typeOf(access.expression));
|
|
2346
|
+
let probedUntyped = false;
|
|
2347
|
+
if (receiverIr?.kind !== "map" && isJsSourceFile(access.getSourceFile())) {
|
|
2348
|
+
const probed = tryLowerExpression(lowerer, access.expression);
|
|
2349
|
+
if (probed?.type.kind === "map") {
|
|
2350
|
+
receiverIr = probed.type;
|
|
2351
|
+
probedUntyped = true;
|
|
2352
|
+
}
|
|
2353
|
+
}
|
|
2439
2354
|
if (receiverIr?.kind !== "map")
|
|
2440
2355
|
return null;
|
|
2441
2356
|
if (!lowerer.isStdlibMember(access))
|
|
@@ -2479,7 +2394,7 @@ export function lowerMapMethodCall(lowerer, call, access) {
|
|
|
2479
2394
|
return lowerMapIterDrainCall(lowerer, call, receiver, receiverIr, name);
|
|
2480
2395
|
}
|
|
2481
2396
|
// forEach
|
|
2482
|
-
return
|
|
2397
|
+
return lowerMapForEachCall(lowerer, call, receiver, receiverIr, probedUntyped);
|
|
2483
2398
|
}
|
|
2484
2399
|
/** The iterator methods the lowering DOES cover — in exactly one context. */
|
|
2485
2400
|
const MAP_ITER_METHODS = new Set(["keys", "values", "entries"]);
|
|
@@ -2599,12 +2514,52 @@ function buildMapIterDrainFn(name, mapT, method, elemT, tupleT, loc) {
|
|
|
2599
2514
|
* callback throws. That is JS's live-iteration contract, Node-verified
|
|
2600
2515
|
* (SEMANTICS.md). The callback receives (value, key) like JS; declaring
|
|
2601
2516
|
* fewer parameters — (value) or () — is ordinary TS and supported. */
|
|
2602
|
-
export function lowerMapForEachCall(lowerer, call, receiver, mapT) {
|
|
2517
|
+
export function lowerMapForEachCall(lowerer, call, receiver, mapT, bindUntyped = false) {
|
|
2603
2518
|
const loc = locOf(call);
|
|
2604
2519
|
const argNode = call.arguments[0];
|
|
2605
2520
|
if (!argNode)
|
|
2606
2521
|
lowerer.unsupported("SC1090", call, "this call form"); // tsc-guarded
|
|
2607
|
-
const
|
|
2522
|
+
const previousRuntime = [];
|
|
2523
|
+
const previousImplicit = lowerer.implicitParamTypes;
|
|
2524
|
+
let contextualTs = null;
|
|
2525
|
+
if (bindUntyped && (ts.isArrowFunction(argNode) || ts.isFunctionExpression(argNode))) {
|
|
2526
|
+
const expected = [mapT.value, mapT.key];
|
|
2527
|
+
argNode.parameters.slice(0, 2).forEach((param, i) => {
|
|
2528
|
+
const type = expected[i];
|
|
2529
|
+
if (type === undefined || !ts.isIdentifier(param.name) || param.type || param.initializer || param.dotDotDotToken)
|
|
2530
|
+
return;
|
|
2531
|
+
const checkerType = lowerer.checker.getTypeAtLocation(param.name);
|
|
2532
|
+
if ((checkerType.flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown)) === 0)
|
|
2533
|
+
return;
|
|
2534
|
+
const symbol = lowerer.checker.getSymbolAtLocation(param.name);
|
|
2535
|
+
if (!symbol)
|
|
2536
|
+
return;
|
|
2537
|
+
previousRuntime.push({ symbol, type: lowerer.runtimeOptionalBindingTypes.get(symbol) });
|
|
2538
|
+
lowerer.runtimeOptionalBindingTypes.set(symbol, type);
|
|
2539
|
+
const primitive = type.kind === "string" ? lowerer.checker.getStringType() :
|
|
2540
|
+
type.kind === "f64" ? lowerer.checker.getNumberType() :
|
|
2541
|
+
type.kind === "bool" ? lowerer.checker.getBooleanType() : null;
|
|
2542
|
+
if (primitive !== null) {
|
|
2543
|
+
contextualTs ??= new Map(previousImplicit ?? []);
|
|
2544
|
+
contextualTs.set(symbol, primitive);
|
|
2545
|
+
}
|
|
2546
|
+
});
|
|
2547
|
+
}
|
|
2548
|
+
if (contextualTs !== null)
|
|
2549
|
+
lowerer.implicitParamTypes = contextualTs;
|
|
2550
|
+
let fnArg;
|
|
2551
|
+
try {
|
|
2552
|
+
fnArg = lowerer.lowerExpr(argNode);
|
|
2553
|
+
}
|
|
2554
|
+
finally {
|
|
2555
|
+
lowerer.implicitParamTypes = previousImplicit;
|
|
2556
|
+
for (const { symbol, type } of previousRuntime) {
|
|
2557
|
+
if (type === undefined)
|
|
2558
|
+
lowerer.runtimeOptionalBindingTypes.delete(symbol);
|
|
2559
|
+
else
|
|
2560
|
+
lowerer.runtimeOptionalBindingTypes.set(symbol, type);
|
|
2561
|
+
}
|
|
2562
|
+
}
|
|
2608
2563
|
if (fnArg.type.kind !== "func" ||
|
|
2609
2564
|
fnArg.type.params.length > 2 ||
|
|
2610
2565
|
(fnArg.type.params.length >= 1 && !typeEquals(fnArg.type.params[0], mapT.value)) ||
|
|
@@ -2727,8 +2682,8 @@ export function lowerSetMethodCall(lowerer, call, access) {
|
|
|
2727
2682
|
// Set<union-of-signatures>, but the VALUE lowered as a real Set of
|
|
2728
2683
|
// identity tokens (the new-Set probe) — the lowered receiver's type
|
|
2729
2684
|
// is the honest dispatch key.
|
|
2730
|
-
if (receiverIr
|
|
2731
|
-
const probed =
|
|
2685
|
+
if (receiverIr?.kind !== "set" && isJsSourceFile(access.getSourceFile())) {
|
|
2686
|
+
const probed = tryLowerExpression(lowerer, access.expression);
|
|
2732
2687
|
if (probed?.type.kind === "set")
|
|
2733
2688
|
receiverIr = probed.type;
|
|
2734
2689
|
}
|
|
@@ -3906,7 +3861,7 @@ export function lowerForOfSearchParams(lowerer, stmt, iterable, proj) {
|
|
|
3906
3861
|
export function lowerForOfArrayIter(lowerer, stmt, iterable, proj) {
|
|
3907
3862
|
const loc = locOf(stmt);
|
|
3908
3863
|
const arrT = iterable.type;
|
|
3909
|
-
const elemT = arrT.kind === "array" ? arrT.elem : F64;
|
|
3864
|
+
const elemT = arrT.kind === "array" ? arrayValueType(lowerer, arrT.elem) : F64;
|
|
3910
3865
|
const yieldsPair = proj === "entries";
|
|
3911
3866
|
if (!ts.isVariableDeclarationList(stmt.initializer)) {
|
|
3912
3867
|
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 +3880,7 @@ export function lowerForOfArrayIter(lowerer, stmt, iterable, proj) {
|
|
|
3925
3880
|
i.mutable = true;
|
|
3926
3881
|
const keyRead = () => varRef(i.id, F64, loc);
|
|
3927
3882
|
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
|
-
}
|
|
3883
|
+
? arrayValueRead(lowerer, varRef(arr.id, arrT, loc), varRef(i.id, F64, loc), arrT.elem, loc)
|
|
3935
3884
|
: {
|
|
3936
3885
|
kind: "bytesIntrinsic",
|
|
3937
3886
|
method: "get",
|
|
@@ -3954,6 +3903,11 @@ export function lowerForOfArrayIter(lowerer, stmt, iterable, proj) {
|
|
|
3954
3903
|
binds.push({ kind: "varDecl", localId: kLocal.id, init: keyRead(), loc });
|
|
3955
3904
|
if (els[1]) {
|
|
3956
3905
|
const vLocal = lowerer.declareLocal(els[1].name, els[1].name.text, elemT, isLet);
|
|
3906
|
+
if (arrT.kind === "array" && !typeEquals(elemT, arrT.elem)) {
|
|
3907
|
+
const root = lowerer.runtimeOptionalRootOf(vLocal);
|
|
3908
|
+
lowerer.runtimeOptionalLocals.add(root);
|
|
3909
|
+
lowerer.runtimeOptionalStorageLocals.add(root);
|
|
3910
|
+
}
|
|
3957
3911
|
binds.push({ kind: "varDecl", localId: vLocal.id, init: valRead(), loc });
|
|
3958
3912
|
}
|
|
3959
3913
|
}
|
|
@@ -3964,6 +3918,13 @@ export function lowerForOfArrayIter(lowerer, stmt, iterable, proj) {
|
|
|
3964
3918
|
const declT = lowerer.mapTypeOf(lowerer.checker.getTypeAtLocation(decl.name));
|
|
3965
3919
|
if (yieldsPair) {
|
|
3966
3920
|
const shape = declT?.kind === "record" ? lowerer.shapes.get(declT.shapeId) : null;
|
|
3921
|
+
if (declT?.kind === "record" && shape?.tuple && arrT.kind === "array") {
|
|
3922
|
+
const valueField = shape.fields.find((field) => field.name === "1");
|
|
3923
|
+
if (valueField && typeEquals(valueField.type, arrT.elem) && !typeEquals(elemT, arrT.elem)) {
|
|
3924
|
+
valueField.type = elemT;
|
|
3925
|
+
lowerer.runtimeOptionalFields.add(lowerer.runtimeOptionalFieldKey(declT.shapeId, "1"));
|
|
3926
|
+
}
|
|
3927
|
+
}
|
|
3967
3928
|
if (declT?.kind !== "record" || !shape?.tuple || shape.fields.length !== 2 ||
|
|
3968
3929
|
shape.fields.find((f) => f.name === "0")?.type.kind !== "f64" ||
|
|
3969
3930
|
!typeEquals(shape.fields.find((f) => f.name === "1")?.type ?? F64, elemT)) {
|
|
@@ -4373,1226 +4334,6 @@ function buildMapSeedFn(mapT, name, arrT, tupleT, kT, vT, loc) {
|
|
|
4373
4334
|
loc,
|
|
4374
4335
|
};
|
|
4375
4336
|
}
|
|
4376
|
-
/** Regex method calls, both directions: `re.test(s)` on a regex receiver,
|
|
4377
|
-
* and `s.replace(re, tpl)` / `s.replaceAll(re, tpl)` / `s.split(re)` on a
|
|
4378
|
-
* string receiver whose FIRST ARGUMENT is a regex (the string-pattern
|
|
4379
|
-
* overloads keep their island lowering — the argument's mapped type is
|
|
4380
|
-
* what routes here, before lowerIslandMethodCall can claim the name).
|
|
4381
|
-
* Null when neither shape matches. */
|
|
4382
|
-
export function lowerRegexMethodCall(lowerer, call, access, dynReceiver) {
|
|
4383
|
-
// chainBlocked, not a raw token test: an optional-chain re-dispatch
|
|
4384
|
-
// (`rawName?.match(re)` — the receiver reads back chain-narrowed)
|
|
4385
|
-
// rides the same lowering as the plain spelling.
|
|
4386
|
-
if (lowerer.chainBlocked(access, call))
|
|
4387
|
-
return null;
|
|
4388
|
-
// A validated dyn receiver (lowerStringMethodCall's story) is a STRING
|
|
4389
|
-
// by construction — the symbol gate doesn't apply to `any` receivers.
|
|
4390
|
-
if (dynReceiver === undefined && !lowerer.isStdlibMember(access))
|
|
4391
|
-
return null;
|
|
4392
|
-
const name = access.name.text;
|
|
4393
|
-
const receiverKind = dynReceiver ? "string" : lowerer.mapTypeOf(lowerer.typeOf(access.expression))?.kind;
|
|
4394
|
-
const lowerReceiver = () => (dynReceiver ? dynReceiver() : lowerer.lowerExpr(access.expression));
|
|
4395
|
-
const loc = locOf(call);
|
|
4396
|
-
if (receiverKind === "regex" && name === "test") {
|
|
4397
|
-
// The statefulness fence, at compile time where the flags are
|
|
4398
|
-
// visible: a literal receiver (possibly parenthesized). Values that
|
|
4399
|
-
// flow through variables hit the same fence at runtime.
|
|
4400
|
-
let recv = access.expression;
|
|
4401
|
-
while (ts.isParenthesizedExpression(recv))
|
|
4402
|
-
recv = recv.expression;
|
|
4403
|
-
if (ts.isRegularExpressionLiteral(recv)) {
|
|
4404
|
-
const flags = recv.text.slice(recv.text.lastIndexOf("/") + 1);
|
|
4405
|
-
if (flags.includes("g") || flags.includes("y")) {
|
|
4406
|
-
lowerer.unsupported("SC1121", call);
|
|
4407
|
-
}
|
|
4408
|
-
}
|
|
4409
|
-
const receiver = lowerer.lowerExpr(access.expression);
|
|
4410
|
-
const args = call.arguments.map((a) => lowerer.lowerExpr(a));
|
|
4411
|
-
return { kind: "regexIntrinsic", method: "test", receiver, args, type: BOOL, loc };
|
|
4412
|
-
}
|
|
4413
|
-
// `re.exec(s)` for non-g/y regexes: spec-identical to `s.match(re)`
|
|
4414
|
-
// (Symbol.match delegates to exec when lastIndex is out of play), so
|
|
4415
|
-
// it lowers to the SAME match intrinsic with the operands swapped —
|
|
4416
|
-
// the honest `string[] | null` slice, nonparticipating captures ""
|
|
4417
|
-
// (match's documented rule). The g/y statefulness fence applies at
|
|
4418
|
-
// compile time on literal receivers, exactly test()'s stance; values
|
|
4419
|
-
// reaching the runtime with those flags abort there.
|
|
4420
|
-
if (receiverKind === "regex" && name === "exec") {
|
|
4421
|
-
if (call.arguments.length !== 1)
|
|
4422
|
-
return null; // exec takes exactly the subject
|
|
4423
|
-
let recv = access.expression;
|
|
4424
|
-
while (ts.isParenthesizedExpression(recv))
|
|
4425
|
-
recv = recv.expression;
|
|
4426
|
-
if (ts.isRegularExpressionLiteral(recv)) {
|
|
4427
|
-
const flags = recv.text.slice(recv.text.lastIndexOf("/") + 1);
|
|
4428
|
-
if (flags.includes("g") || flags.includes("y")) {
|
|
4429
|
-
lowerer.unsupported("SC1121", call);
|
|
4430
|
-
}
|
|
4431
|
-
}
|
|
4432
|
-
const re = lowerer.lowerExpr(access.expression);
|
|
4433
|
-
const subject = lowerer.lowerExprExpecting(call.arguments[0], STRING);
|
|
4434
|
-
const resultT = { kind: "union", unionId: lowerer.unions.intern([arrayOf(STRING), { kind: "nullT" }]) };
|
|
4435
|
-
return { kind: "regexIntrinsic", method: "match", receiver: subject, args: [re], type: resultT, loc };
|
|
4436
|
-
}
|
|
4437
|
-
// `s.match(re)` for non-g/y regexes: Node's exec-shaped result reduced
|
|
4438
|
-
// to the honest slice — the `string[] | null` union holding
|
|
4439
|
-
// [whole match, ...captures] or the null arm. The g-flag match returns
|
|
4440
|
-
// EVERY match (a different shape) and /y is stateful — both fence at
|
|
4441
|
-
// compile time on literal arguments (values reaching the runtime with
|
|
4442
|
-
// those flags abort, the test() stance). `.index`/`.input` reads on
|
|
4443
|
-
// the result fence per member (array-typed value); `.groups` reads
|
|
4444
|
-
// desugar at their access sites when the regex is statically known
|
|
4445
|
-
// (lowerMatchGroupsRead).
|
|
4446
|
-
// `s.match(re)` also claims a NULLABLE string receiver (string + unit
|
|
4447
|
-
// arms — `process.versions.openssl.match(...)`, the Dict<string>
|
|
4448
|
-
// member the node suite's crypto helper reads): the checked
|
|
4449
|
-
// extraction narrows to the string arm and a unit value throws the
|
|
4450
|
-
// catchable TypeError at the read, where Node's own member read
|
|
4451
|
-
// throws — tsc only admits the spelling in JS sources.
|
|
4452
|
-
const nullableStringRecv = receiverKind === "union" &&
|
|
4453
|
-
name === "match" &&
|
|
4454
|
-
(() => {
|
|
4455
|
-
const t = lowerer.mapTypeOf(lowerer.typeOf(access.expression));
|
|
4456
|
-
if (t?.kind !== "union")
|
|
4457
|
-
return false;
|
|
4458
|
-
const arms = lowerer.unions.get(t.unionId)?.arms ?? [];
|
|
4459
|
-
return arms.some((a) => a.kind === "string") && arms.every((a) => a.kind === "string" || isUnitType(a));
|
|
4460
|
-
})();
|
|
4461
|
-
if ((receiverKind === "string" || nullableStringRecv) && name === "match") {
|
|
4462
|
-
const arg0 = call.arguments[0];
|
|
4463
|
-
if (!arg0 || lowerer.mapTypeOf(lowerer.typeOf(arg0))?.kind !== "regex")
|
|
4464
|
-
return null; // string-pattern match: the SC2020 fence
|
|
4465
|
-
if (call.arguments.length !== 1)
|
|
4466
|
-
return null;
|
|
4467
|
-
let reNode = arg0;
|
|
4468
|
-
while (ts.isParenthesizedExpression(reNode))
|
|
4469
|
-
reNode = reNode.expression;
|
|
4470
|
-
if (ts.isRegularExpressionLiteral(reNode)) {
|
|
4471
|
-
const flags = reNode.text.slice(reNode.text.lastIndexOf("/") + 1);
|
|
4472
|
-
if (flags.includes("g") || flags.includes("y")) {
|
|
4473
|
-
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
|
-
}
|
|
4475
|
-
}
|
|
4476
|
-
const receiver = nullableStringRecv ? lowerer.lowerExprExpecting(access.expression, STRING) : lowerReceiver();
|
|
4477
|
-
const re = lowerer.lowerExpr(arg0);
|
|
4478
|
-
// RegExpMatchArray | null maps to the string[] | null union by
|
|
4479
|
-
// itself; intern it directly when the checker's spelling doesn't —
|
|
4480
|
-
// or when it maps to something WIDER (an optional-chain call node
|
|
4481
|
-
// types `s?.match(re)` with the chain's `| undefined`; the intrinsic
|
|
4482
|
-
// itself answers string[] | null, and the chain wrapper widens).
|
|
4483
|
-
const exactT = { kind: "union", unionId: lowerer.unions.intern([arrayOf(STRING), { kind: "nullT" }]) };
|
|
4484
|
-
const mapped = lowerer.mapTypeOf(lowerer.typeOf(call));
|
|
4485
|
-
const resultT = mapped && typeEquals(mapped, exactT) ? mapped : exactT;
|
|
4486
|
-
return { kind: "regexIntrinsic", method: "match", receiver, args: [re], type: resultT, loc };
|
|
4487
|
-
}
|
|
4488
|
-
// `s.matchAll(re)` — the every-match iterator drained EAGERLY into a
|
|
4489
|
-
// string[][] (one honest match slice per row — match's rule). Lazy vs
|
|
4490
|
-
// eager is unobservable here: strings are immutable, and the spec
|
|
4491
|
-
// clones the regex at the call, so nothing can perturb the drain. The
|
|
4492
|
-
// eager array IS what the two lowered consumers see anyway (the
|
|
4493
|
-
// immediate [...spread] and the for-of walk); a stored iterator's
|
|
4494
|
-
// .next() fences as an array member like any other. Non-global
|
|
4495
|
-
// regexes throw Node's exact TypeError at runtime, catchably
|
|
4496
|
-
// (replaceAll's stance).
|
|
4497
|
-
if (receiverKind === "string" && name === "matchAll") {
|
|
4498
|
-
const arg0 = call.arguments[0];
|
|
4499
|
-
if (!arg0 || lowerer.mapTypeOf(lowerer.typeOf(arg0))?.kind !== "regex")
|
|
4500
|
-
return null; // string-pattern form: the SC2020 fence
|
|
4501
|
-
if (call.arguments.length !== 1)
|
|
4502
|
-
return null;
|
|
4503
|
-
const receiver = lowerReceiver();
|
|
4504
|
-
const re = lowerer.lowerExpr(arg0);
|
|
4505
|
-
return {
|
|
4506
|
-
kind: "regexIntrinsic",
|
|
4507
|
-
method: "matchAll",
|
|
4508
|
-
receiver,
|
|
4509
|
-
args: [re],
|
|
4510
|
-
type: arrayOf(arrayOf(STRING)),
|
|
4511
|
-
loc,
|
|
4512
|
-
};
|
|
4513
|
-
}
|
|
4514
|
-
// `s.search(re)` — the first match's UTF-16 index, or -1. No g/y fence
|
|
4515
|
-
// (unlike test/match): Symbol.search neither reads nor writes lastIndex
|
|
4516
|
-
// — a fresh exec from position 0, so /g is irrelevant and /y anchors at
|
|
4517
|
-
// 0, exactly Node. Never throws.
|
|
4518
|
-
if (receiverKind === "string" && name === "search") {
|
|
4519
|
-
const arg0 = call.arguments[0];
|
|
4520
|
-
if (!arg0 || lowerer.mapTypeOf(lowerer.typeOf(arg0))?.kind !== "regex")
|
|
4521
|
-
return null; // string-pattern form: the SC2020 fence
|
|
4522
|
-
if (call.arguments.length !== 1)
|
|
4523
|
-
return null;
|
|
4524
|
-
const receiver = lowerReceiver();
|
|
4525
|
-
const re = lowerer.lowerExpr(arg0);
|
|
4526
|
-
return { kind: "regexIntrinsic", method: "search", receiver, args: [re], type: F64, loc };
|
|
4527
|
-
}
|
|
4528
|
-
if (receiverKind === "string" &&
|
|
4529
|
-
(name === "replace" || name === "replaceAll" || name === "split")) {
|
|
4530
|
-
const arg0 = call.arguments[0];
|
|
4531
|
-
if (!arg0 || lowerer.mapTypeOf(lowerer.typeOf(arg0))?.kind !== "regex")
|
|
4532
|
-
return null;
|
|
4533
|
-
const receiver = lowerReceiver();
|
|
4534
|
-
// Split's omitted or undefined limit is 2^32-1. Complete it here so
|
|
4535
|
-
// both IR backends and the runtime have one required (regex, limit)
|
|
4536
|
-
// shape; an optional-number value selects the default at runtime.
|
|
4537
|
-
const args = name === "split"
|
|
4538
|
-
? [lowerer.lowerExpr(arg0), lowerSplitLimitArg(lowerer, call.arguments[1], loc)]
|
|
4539
|
-
: call.arguments.map((a) => lowerer.lowerExpr(a));
|
|
4540
|
-
if (name !== "split" && args[1]?.type.kind !== "string") {
|
|
4541
|
-
lowerer.unsupported("SC1120", call.arguments[1] ?? call, "function replacement values (replacements must be string templates)");
|
|
4542
|
-
}
|
|
4543
|
-
return {
|
|
4544
|
-
kind: "regexIntrinsic",
|
|
4545
|
-
method: name,
|
|
4546
|
-
receiver,
|
|
4547
|
-
args,
|
|
4548
|
-
type: name === "split" ? arrayOf(STRING) : STRING,
|
|
4549
|
-
loc,
|
|
4550
|
-
};
|
|
4551
|
-
}
|
|
4552
|
-
return null;
|
|
4553
|
-
}
|
|
4554
|
-
/** `s.slice(1, 4)` and friends → strIntrinsic. Null when this isn't an
|
|
4555
|
-
* ambient string method call (caller keeps its generic rejection). Missing
|
|
4556
|
-
* optional arguments are omitted from `args` — the backend fills the
|
|
4557
|
-
* documented defaults; the IR never encodes them (Infinity isn't
|
|
4558
|
-
* JSON-safe). tsc has already checked arity and argument types against
|
|
4559
|
-
* ambient/scriptc.d.ts. */
|
|
4560
|
-
export function lowerStringMethodCall(lowerer, call, access, dynReceiver) {
|
|
4561
|
-
if (lowerer.chainBlocked(access, call))
|
|
4562
|
-
return null;
|
|
4563
|
-
if (dynReceiver === undefined && access.name.text === "localeCompare")
|
|
4564
|
-
return lowerLocaleCompareCall(lowerer, call, access);
|
|
4565
|
-
const entry = own(STR_METHODS, access.name.text);
|
|
4566
|
-
if (!entry)
|
|
4567
|
-
return null;
|
|
4568
|
-
// A validated dyn receiver (`pkg.name.replace(...)` on a JSON.parse
|
|
4569
|
-
// value) arrives pre-extracted through `dynReceiver`; its checker type
|
|
4570
|
-
// is `any`, so the type/symbol gates don't apply — the dyn value's
|
|
4571
|
-
// methods can only BE the string intrinsics.
|
|
4572
|
-
if (dynReceiver === undefined) {
|
|
4573
|
-
const receiverIr = lowerer.mapTypeOf(lowerer.typeOf(access.expression));
|
|
4574
|
-
// `require.main?.filename.startsWith(...)`: the checker types the
|
|
4575
|
-
// receiver `string | undefined` (the chain's short-circuit arm), but
|
|
4576
|
-
// the entry-module fold lowers it to a compile-time STRING — the
|
|
4577
|
-
// suite harness's skip() shape. Everything else keeps the strict
|
|
4578
|
-
// string gate.
|
|
4579
|
-
if (receiverIr?.kind !== "string" && !isRequireMainFilename(lowerer, access.expression))
|
|
4580
|
-
return null;
|
|
4581
|
-
if (!lowerer.isStdlibMember(access))
|
|
4582
|
-
return null;
|
|
4583
|
-
}
|
|
4584
|
-
// The lib declares optional parameters beyond the lowered forms
|
|
4585
|
-
// (includes/startsWith/endsWith take a position); fence the unlowered
|
|
4586
|
-
// arities instead of passing arguments the runtime doesn't take.
|
|
4587
|
-
if (call.arguments.length < entry.minArgs || call.arguments.length > entry.maxArgs) {
|
|
4588
|
-
lowerer.noLowering(`.${access.name.text} with ${call.arguments.length} argument${call.arguments.length === 1 ? "" : "s"} on strings`, call);
|
|
4589
|
-
}
|
|
4590
|
-
const receiver = dynReceiver ? dynReceiver() : lowerer.lowerExpr(access.expression);
|
|
4591
|
-
const args = entry.method === "split"
|
|
4592
|
-
? [lowerer.lowerExpr(call.arguments[0]), lowerSplitLimitArg(lowerer, call.arguments[1], locOf(call))]
|
|
4593
|
-
: call.arguments.map((a) => lowerer.lowerExpr(a));
|
|
4594
|
-
// split's separator must BE a string here (a regex argument was
|
|
4595
|
-
// claimed by lowerRegexMethodCall before this path) — the lib's
|
|
4596
|
-
// `string | RegExp` union has no lowering as a VALUE.
|
|
4597
|
-
if (entry.method === "split" && args[0].type.kind !== "string") {
|
|
4598
|
-
lowerer.unsupported("SC1090", call.arguments[0], `'.split()' on a '${lowerer.fmt(args[0].type)}' separator (pass a string, or a regex literal)`);
|
|
4599
|
-
}
|
|
4600
|
-
// padStart/padEnd with the fill omitted: Node pads with " " — the
|
|
4601
|
-
// same call with the default made explicit.
|
|
4602
|
-
if ((entry.method === "padStart" || entry.method === "padEnd") && args.length === 1) {
|
|
4603
|
-
args.push({ kind: "strLit", value: " ", type: STRING, loc: locOf(call) });
|
|
4604
|
-
}
|
|
4605
|
-
return {
|
|
4606
|
-
kind: "strIntrinsic",
|
|
4607
|
-
method: entry.method,
|
|
4608
|
-
receiver,
|
|
4609
|
-
args,
|
|
4610
|
-
type: entry.result,
|
|
4611
|
-
loc: locOf(call),
|
|
4612
|
-
};
|
|
4613
|
-
}
|
|
4614
|
-
/** `a.localeCompare(b)` — the one-argument form only (locales/options
|
|
4615
|
-
* select ICU collations that do not exist here). Lowers to an interned
|
|
4616
|
-
* synthetic function returning -1/0/1 by CODE-UNIT order — the same
|
|
4617
|
-
* ordering as the string relational operators — NOT Node's ICU default
|
|
4618
|
-
* collation: a documented divergence (SEMANTICS.md; e.g. Node says
|
|
4619
|
-
* "a" < "B" under ICU while code units say "B" < "a"). For same-case
|
|
4620
|
-
* ASCII the orders agree. Null when the receiver isn't a stdlib string
|
|
4621
|
-
* (caller keeps its generic rejection). */
|
|
4622
|
-
function lowerLocaleCompareCall(lowerer, call, access) {
|
|
4623
|
-
const receiverIr = lowerer.mapTypeOf(lowerer.typeOf(access.expression));
|
|
4624
|
-
if (receiverIr?.kind !== "string")
|
|
4625
|
-
return null;
|
|
4626
|
-
if (!lowerer.isStdlibMember(access))
|
|
4627
|
-
return null;
|
|
4628
|
-
const loc = locOf(call);
|
|
4629
|
-
if (call.arguments.length !== 1) {
|
|
4630
|
-
lowerer.noLowering(`.localeCompare with ${call.arguments.length} arguments`, call, "the locales/options parameters select ICU collations that have no lowering — " +
|
|
4631
|
-
"pass exactly the comparison string");
|
|
4632
|
-
}
|
|
4633
|
-
const receiver = lowerer.lowerExpr(access.expression);
|
|
4634
|
-
const arg = lowerer.lowerExpr(call.arguments[0]);
|
|
4635
|
-
if (arg.type.kind !== "string")
|
|
4636
|
-
lowerer.badType(call.arguments[0], lowerer.typeOf(call.arguments[0]));
|
|
4637
|
-
const key = "localeCompare";
|
|
4638
|
-
let helper = lowerer.arrHofHelpers.get(key);
|
|
4639
|
-
if (!helper) {
|
|
4640
|
-
helper = `%str.localeCompare`;
|
|
4641
|
-
lowerer.arrHofHelpers.set(key, helper);
|
|
4642
|
-
lowerer.liftedFns.push(buildLocaleCompareFn(helper, loc));
|
|
4643
|
-
}
|
|
4644
|
-
return { kind: "call", callee: helper, args: [receiver, arg], type: F64, loc };
|
|
4645
|
-
}
|
|
4646
|
-
/** `return a < b ? -1 : a > b ? 1 : 0` over the strCmp primitive (the
|
|
4647
|
-
* relational operators' exact machinery — one interned helper, no new IR
|
|
4648
|
-
* or runtime surface). */
|
|
4649
|
-
function buildLocaleCompareFn(name, loc) {
|
|
4650
|
-
const cmp = (op) => ({ kind: "strCmp", op, left: varRef("a.0", STRING, loc), right: varRef("b.0", STRING, loc), type: BOOL, loc });
|
|
4651
|
-
const body = [
|
|
4652
|
-
{
|
|
4653
|
-
kind: "return",
|
|
4654
|
-
value: {
|
|
4655
|
-
kind: "ternary",
|
|
4656
|
-
cond: cmp("<"),
|
|
4657
|
-
then: numLit(-1, loc),
|
|
4658
|
-
else_: { kind: "ternary", cond: cmp(">"), then: numLit(1, loc), else_: numLit(0, loc), type: F64, loc },
|
|
4659
|
-
type: F64,
|
|
4660
|
-
loc,
|
|
4661
|
-
},
|
|
4662
|
-
loc,
|
|
4663
|
-
},
|
|
4664
|
-
];
|
|
4665
|
-
return {
|
|
4666
|
-
name,
|
|
4667
|
-
params: [
|
|
4668
|
-
{ localId: "a.0", name: "a", type: STRING },
|
|
4669
|
-
{ localId: "b.0", name: "b", type: STRING },
|
|
4670
|
-
],
|
|
4671
|
-
returnType: F64,
|
|
4672
|
-
locals: [
|
|
4673
|
-
{ id: "a.0", name: "a", type: STRING, mutable: true },
|
|
4674
|
-
{ id: "b.0", name: "b", type: STRING, mutable: true },
|
|
4675
|
-
],
|
|
4676
|
-
body,
|
|
4677
|
-
loc,
|
|
4678
|
-
};
|
|
4679
|
-
}
|
|
4680
|
-
/* ── typed arrays / Buffer ─────────────────────────────────────────────── */
|
|
4681
|
-
/** The typed-array constructors with a runtime representation, by lib
|
|
4682
|
-
* interface name. The other TypedArray flavors (Int8Array, Float64Array,
|
|
4683
|
-
* DataView, ...) fall through to the generic stdlib-constructor fence. */
|
|
4684
|
-
const BYTES_CTORS = {
|
|
4685
|
-
Uint8Array: "u8",
|
|
4686
|
-
Uint32Array: "u32",
|
|
4687
|
-
Int32Array: "i32",
|
|
4688
|
-
Float32Array: "f32",
|
|
4689
|
-
};
|
|
4690
|
-
/** `new Uint8Array(...)` / `new Uint32Array(...)` / `new Float32Array(...)`
|
|
4691
|
-
* (stdlib provenance — a user's own class with the name resolves through
|
|
4692
|
-
* classBySymbol). Lowered argument shapes: none (empty), a length
|
|
4693
|
-
* (zero-filled; ToIndex at runtime — invalid lengths throw Node's
|
|
4694
|
-
* RangeError), a same-kind typed array or Buffer (an independent COPY —
|
|
4695
|
-
* the readFile chain's `new Uint8Array(await readFile(p))`), a number[]
|
|
4696
|
-
* literal (element-coerced; its contextual type is the lib's
|
|
4697
|
-
* ArrayLike/Iterable union, which cannot map — the Set-seed pattern), or
|
|
4698
|
-
* a number[]-typed value. ArrayBuffer/view forms are fenced: scriptc
|
|
4699
|
-
* typed arrays own their storage. Null when this isn't a stdlib
|
|
4700
|
-
* typed-array construction. */
|
|
4701
|
-
export function lowerBytesNew(lowerer, expr, symbol) {
|
|
4702
|
-
if (symbol && symbol.name === "DataView" && lowerer.isStdlibSymbol(symbol)) {
|
|
4703
|
-
return lowerDataViewNew(lowerer, expr);
|
|
4704
|
-
}
|
|
4705
|
-
const elem = symbol ? own(BYTES_CTORS, symbol.name) : undefined;
|
|
4706
|
-
if (!elem || !symbol || !lowerer.isStdlibSymbol(symbol))
|
|
4707
|
-
return null;
|
|
4708
|
-
const name = symbol.name;
|
|
4709
|
-
const type = bytesOf(elem);
|
|
4710
|
-
const loc = locOf(expr);
|
|
4711
|
-
const args = expr.arguments ?? [];
|
|
4712
|
-
if (args.length === 0)
|
|
4713
|
-
return { kind: "bytesNew", source: null, type, loc };
|
|
4714
|
-
if (args.length === 1 && !ts.isSpreadElement(args[0])) {
|
|
4715
|
-
const argNode = args[0];
|
|
4716
|
-
if (ts.isArrayLiteralExpression(argNode) && !argNode.elements.some(ts.isSpreadElement)) {
|
|
4717
|
-
const elems = argNode.elements.map((el) => lowerer.lowerExprExpecting(el, F64));
|
|
4718
|
-
const seed = { kind: "arrayLit", elems, type: arrayOf(F64), loc };
|
|
4719
|
-
return { kind: "bytesNew", source: seed, type, loc };
|
|
4720
|
-
}
|
|
4721
|
-
// The SYNTACTIC `new T(new ArrayBuffer(n))` and `new T(new
|
|
4722
|
-
// SharedArrayBuffer(n))` forms — fresh-buffer construction (the
|
|
4723
|
-
// shared spelling is the Atomics.wait sleep idiom's). The buffer
|
|
4724
|
-
// never exists as a value: n must be a byte-length LITERAL divisible
|
|
4725
|
-
// by the element size (tsc would admit any number; a bad one is
|
|
4726
|
-
// Node's RangeError — rejected at compile time instead of
|
|
4727
|
-
// half-lowering), and the whole expression is a zero-filled typed
|
|
4728
|
-
// array of n/elemSize elements. Erasing the buffer is exact: nothing
|
|
4729
|
-
// else can ever reference it, so neither sharing (scriptc has no
|
|
4730
|
-
// threads) nor aliasing is observable — SEMANTICS.md documents the
|
|
4731
|
-
// stance. The RESIZABLE form (a maxByteLength options bag) fences by
|
|
4732
|
-
// name: resize needs the buffer to exist as a value, and none does.
|
|
4733
|
-
if (ts.isNewExpression(argNode) &&
|
|
4734
|
-
ts.isIdentifier(argNode.expression) &&
|
|
4735
|
-
(argNode.expression.text === "ArrayBuffer" ||
|
|
4736
|
-
argNode.expression.text === "SharedArrayBuffer") &&
|
|
4737
|
-
lowerer.isStdlibSymbol(lowerer.resolveValueSymbol(argNode.expression) ?? undefined)) {
|
|
4738
|
-
const bufCtor = argNode.expression.text;
|
|
4739
|
-
if ((argNode.arguments?.length ?? 0) > 1) {
|
|
4740
|
-
lowerer.noLowering(`new ${name} over a resizable ${bufCtor}`, argNode, "a maxByteLength options bag makes the buffer resizable, and resizing needs the buffer " +
|
|
4741
|
-
"to exist as a runtime value — no free-standing ArrayBuffer value exists (the buffer here " +
|
|
4742
|
-
"erases into the view): drop the options bag");
|
|
4743
|
-
}
|
|
4744
|
-
const elemSize = elem === "u8" ? 1 : 4;
|
|
4745
|
-
const lenArg = argNode.arguments?.length === 1 ? argNode.arguments[0] : undefined;
|
|
4746
|
-
const lenT = lenArg ? lowerer.typeOf(lenArg) : null;
|
|
4747
|
-
const byteLen = lenT?.isNumberLiteralType() ? lenT.value : null;
|
|
4748
|
-
if (byteLen === null || byteLen % elemSize !== 0 || byteLen < 0) {
|
|
4749
|
-
lowerer.noLowering(`new ${name} over this ${bufCtor}`, argNode, `the byte length must be a number literal divisible by ${elemSize} — new ${name}(new ${bufCtor}(${elemSize}))`);
|
|
4750
|
-
}
|
|
4751
|
-
const count = { kind: "numLit", value: byteLen / elemSize, type: F64, loc };
|
|
4752
|
-
return { kind: "bytesNew", source: count, type, loc };
|
|
4753
|
-
}
|
|
4754
|
-
const src = lowerer.lowerExpr(argNode);
|
|
4755
|
-
if (src.type.kind === "f64" ||
|
|
4756
|
-
typeEquals(src.type, type) ||
|
|
4757
|
-
(src.type.kind === "array" && src.type.elem.kind === "f64")) {
|
|
4758
|
-
return { kind: "bytesNew", source: src, type, loc };
|
|
4759
|
-
}
|
|
4760
|
-
if (src.type.kind === "bytes") {
|
|
4761
|
-
lowerer.noLowering(`new ${name} over a '${lowerer.fmt(src.type)}'`, argNode, "cross-kind typed-array conversion has no lowering — copy element by element");
|
|
4762
|
-
}
|
|
4763
|
-
lowerer.noLowering(`new ${name} over '${lowerer.fmt(src.type)}' values`, argNode, `supported: new ${name}(), (length), (typedArray) — always a copy — or (number[]); ` +
|
|
4764
|
-
"ArrayBuffers and views do not exist here (narrow unions first)");
|
|
4765
|
-
}
|
|
4766
|
-
lowerer.noLowering(`new ${name} with ${args.length} arguments`, expr, `supported: new ${name}(), (length), (typedArray), or (number[])`);
|
|
4767
|
-
}
|
|
4768
|
-
/** `new DataView(x.buffer, byteOffset?, byteLength?)` (stdlib provenance).
|
|
4769
|
-
* The first argument must be the SYNTACTIC `.buffer` of a typed-array/
|
|
4770
|
-
* Buffer value — `x.buffer` names x's own storage (scriptc typed arrays
|
|
4771
|
-
* always own it whole, byteOffset 0), so the view construction takes x
|
|
4772
|
-
* itself as the receiver and no ArrayBuffer value ever exists.
|
|
4773
|
-
* byteOffset/byteLength are optional f64s (omitted args OMITTED, like
|
|
4774
|
-
* slice); bad indices THROW Node's RangeErrors catchably. The result is
|
|
4775
|
-
* a true borrowed view: reads and writes through it alias x. */
|
|
4776
|
-
function lowerDataViewNew(lowerer, expr) {
|
|
4777
|
-
const loc = locOf(expr);
|
|
4778
|
-
const args = expr.arguments ?? [];
|
|
4779
|
-
const bufNode = args[0];
|
|
4780
|
-
if (!bufNode || args.length > 3 || args.some(ts.isSpreadElement)) {
|
|
4781
|
-
lowerer.noLowering(`new DataView with ${args.length} arguments`, expr, "supported: new DataView(x.buffer), (x.buffer, byteOffset), or (x.buffer, byteOffset, byteLength)");
|
|
4782
|
-
}
|
|
4783
|
-
// The FRESH-BUFFER form: `new DataView(new ArrayBuffer(n), ...)` — the
|
|
4784
|
-
// buffer erases into the view (nothing else can ever reference it, so
|
|
4785
|
-
// the aliasing a shared buffer would exhibit is unobservable): the
|
|
4786
|
-
// view's storage is a fresh zero-filled n-byte allocation. n must be a
|
|
4787
|
-
// non-negative integer LITERAL (tsc admits any number; a fractional
|
|
4788
|
-
// one is ToIndex truncation nothing here implements). byteOffset and
|
|
4789
|
-
// byteLength keep their runtime Node-RangeError story. The RESIZABLE
|
|
4790
|
-
// form (a maxByteLength options bag) fences by name.
|
|
4791
|
-
if (ts.isNewExpression(bufNode) &&
|
|
4792
|
-
ts.isIdentifier(bufNode.expression) &&
|
|
4793
|
-
bufNode.expression.text === "ArrayBuffer" &&
|
|
4794
|
-
lowerer.isStdlibSymbol(lowerer.resolveValueSymbol(bufNode.expression) ?? undefined)) {
|
|
4795
|
-
if ((bufNode.arguments?.length ?? 0) > 1) {
|
|
4796
|
-
lowerer.noLowering("new DataView over a resizable ArrayBuffer", bufNode, "a maxByteLength options bag makes the buffer resizable, and resizing needs the buffer " +
|
|
4797
|
-
"to exist as a runtime value — no free-standing ArrayBuffer value exists (the buffer here " +
|
|
4798
|
-
"erases into the view): drop the options bag");
|
|
4799
|
-
}
|
|
4800
|
-
const lenArg = bufNode.arguments?.length === 1 ? bufNode.arguments[0] : undefined;
|
|
4801
|
-
const lenT = lenArg ? lowerer.typeOf(lenArg) : null;
|
|
4802
|
-
const byteLen = lenT?.isNumberLiteralType() ? lenT.value : null;
|
|
4803
|
-
if (byteLen === null || !Number.isInteger(byteLen) || byteLen < 0) {
|
|
4804
|
-
lowerer.noLowering("new DataView over this ArrayBuffer", bufNode, "the byte length must be a non-negative integer literal — new DataView(new ArrayBuffer(8))");
|
|
4805
|
-
}
|
|
4806
|
-
const count = { kind: "numLit", value: byteLen, type: F64, loc };
|
|
4807
|
-
const receiver = { kind: "bytesNew", source: count, type: BYTES_U8, loc };
|
|
4808
|
-
const idxArgs = args.slice(1).map((a) => lowerer.lowerExprExpecting(a, F64));
|
|
4809
|
-
return { kind: "bytesIntrinsic", method: "dataViewNew", receiver, args: idxArgs, type: BYTES_U8, loc };
|
|
4810
|
-
}
|
|
4811
|
-
const hint = "views compile over a typed array's own storage — new DataView(x.buffer, byteOffset?, byteLength?) " +
|
|
4812
|
-
"where x is a Uint8Array/Uint32Array/Float32Array/Buffer value — or a fresh buffer erased into " +
|
|
4813
|
-
"the view: new DataView(new ArrayBuffer(n), ...); free-standing ArrayBuffers have no representation";
|
|
4814
|
-
if (!ts.isPropertyAccessExpression(bufNode) || bufNode.name.text !== "buffer" || bufNode.questionDotToken) {
|
|
4815
|
-
lowerer.noLowering("new DataView over this buffer expression", bufNode, hint);
|
|
4816
|
-
}
|
|
4817
|
-
const srcIr = lowerer.mapTypeOf(lowerer.typeOf(bufNode.expression));
|
|
4818
|
-
if (srcIr?.kind !== "bytes" || !lowerer.isStdlibMember(bufNode)) {
|
|
4819
|
-
lowerer.noLowering(`new DataView over '.buffer' of a '${lowerer.checker.typeToString(lowerer.typeOf(bufNode.expression))}'`, bufNode, hint);
|
|
4820
|
-
}
|
|
4821
|
-
const receiver = lowerer.lowerExpr(bufNode.expression);
|
|
4822
|
-
const idxArgs = args.slice(1).map((a) => lowerer.lowerExprExpecting(a, F64));
|
|
4823
|
-
return { kind: "bytesIntrinsic", method: "dataViewNew", receiver, args: idxArgs, type: BYTES_U8, loc };
|
|
4824
|
-
}
|
|
4825
|
-
/** Method calls on typed-array/Buffer receivers: slice and subarray (BOTH
|
|
4826
|
-
* copy — subarray's sharing is the documented divergence), set(src,
|
|
4827
|
-
* offset?), and the u8-only Buffer surface: toString(enc?) plus the
|
|
4828
|
-
* whole numeric read/write family (fixed widths BE/LE and the
|
|
4829
|
-
* variable-width read/writeUIntLE quartet — BUF_NUM_METHODS). Everything
|
|
4830
|
-
* else the lib declares (fill, indexOf, reverse, ...) falls through to
|
|
4831
|
-
* the SC2020 member fence. Null when this isn't a bytes method call. */
|
|
4832
|
-
export function lowerBytesMethodCall(lowerer, call, access) {
|
|
4833
|
-
if (lowerer.chainBlocked(access, call))
|
|
4834
|
-
return null;
|
|
4835
|
-
const name = access.name.text;
|
|
4836
|
-
const receiverIr = lowerer.mapTypeOf(lowerer.typeOf(access.expression));
|
|
4837
|
-
if (receiverIr?.kind !== "bytes")
|
|
4838
|
-
return null;
|
|
4839
|
-
if (!lowerer.isStdlibMember(access))
|
|
4840
|
-
return null;
|
|
4841
|
-
const loc = locOf(call);
|
|
4842
|
-
const nArgs = call.arguments.length;
|
|
4843
|
-
if (receiverIr.elem === "u8" && name === "toSorted") {
|
|
4844
|
-
return lowerBytesToSortedCall(lowerer, call, access, receiverIr);
|
|
4845
|
-
}
|
|
4846
|
-
if (receiverIr.elem === "u8" && name === "toReversed") {
|
|
4847
|
-
if (nArgs !== 0) {
|
|
4848
|
-
lowerer.noLowering(`.toReversed with ${nArgs} arguments on Uint8Array`, call);
|
|
4849
|
-
}
|
|
4850
|
-
return {
|
|
4851
|
-
kind: "bytesIntrinsic",
|
|
4852
|
-
method: "toReversed",
|
|
4853
|
-
receiver: lowerer.lowerExpr(access.expression),
|
|
4854
|
-
args: [],
|
|
4855
|
-
type: receiverIr,
|
|
4856
|
-
loc,
|
|
4857
|
-
};
|
|
4858
|
-
}
|
|
4859
|
-
if (receiverIr.elem === "u8" && name === "with") {
|
|
4860
|
-
if (nArgs !== 2 || call.arguments.some(ts.isSpreadElement)) {
|
|
4861
|
-
lowerer.noLowering(`.with with ${nArgs} arguments on Uint8Array`, call);
|
|
4862
|
-
}
|
|
4863
|
-
return {
|
|
4864
|
-
kind: "bytesIntrinsic",
|
|
4865
|
-
method: "with",
|
|
4866
|
-
receiver: lowerer.lowerExpr(access.expression),
|
|
4867
|
-
args: [
|
|
4868
|
-
lowerer.lowerExprExpecting(call.arguments[0], F64),
|
|
4869
|
-
lowerer.lowerExprExpecting(call.arguments[1], F64),
|
|
4870
|
-
],
|
|
4871
|
-
type: receiverIr,
|
|
4872
|
-
loc,
|
|
4873
|
-
};
|
|
4874
|
-
}
|
|
4875
|
-
if (receiverIr.elem === "u8" && name === "join") {
|
|
4876
|
-
if (nArgs > 1 || call.arguments.some(ts.isSpreadElement)) {
|
|
4877
|
-
lowerer.noLowering(`.join with ${nArgs} arguments on Uint8Array`, call);
|
|
4878
|
-
}
|
|
4879
|
-
const separatorDefault = {
|
|
4880
|
-
kind: "strLit",
|
|
4881
|
-
value: ",",
|
|
4882
|
-
type: STRING,
|
|
4883
|
-
loc,
|
|
4884
|
-
};
|
|
4885
|
-
const separator = call.arguments[0]
|
|
4886
|
-
? lowerOptionalDefaultArg(lowerer, call.arguments[0], STRING, separatorDefault)
|
|
4887
|
-
: separatorDefault;
|
|
4888
|
-
return {
|
|
4889
|
-
kind: "bytesIntrinsic",
|
|
4890
|
-
method: "join",
|
|
4891
|
-
receiver: lowerer.lowerExpr(access.expression),
|
|
4892
|
-
args: [separator],
|
|
4893
|
-
type: STRING,
|
|
4894
|
-
loc,
|
|
4895
|
-
};
|
|
4896
|
-
}
|
|
4897
|
-
if (name === "slice" || name === "subarray") {
|
|
4898
|
-
if (nArgs > 2) {
|
|
4899
|
-
lowerer.noLowering(`.${name} with ${nArgs} arguments on typed arrays`, call);
|
|
4900
|
-
}
|
|
4901
|
-
const receiver = lowerer.lowerExpr(access.expression);
|
|
4902
|
-
const args = call.arguments.map((a) => lowerer.lowerExprExpecting(a, F64));
|
|
4903
|
-
// subarray is a VIEW (TypedArray.prototype.subarray aliases), and
|
|
4904
|
-
// Buffer's slice() is subarray's deprecated Node alias — resolved by
|
|
4905
|
-
// where the member is declared, the toString discipline below. Only
|
|
4906
|
-
// the plain typed arrays' slice() copies (JS-exact).
|
|
4907
|
-
const declaredOnBuffer = name === "slice" &&
|
|
4908
|
-
(() => {
|
|
4909
|
-
const nameSym = lowerer.checker.getSymbolAtLocation(access.name);
|
|
4910
|
-
return nameSym !== undefined && lowerer.checker.declarationsOf(nameSym).some((d) => ts.isInterfaceDeclaration(d.parent) && d.parent.name.text === "Buffer");
|
|
4911
|
-
})();
|
|
4912
|
-
const method = name === "subarray" || declaredOnBuffer ? "subarray" : "slice";
|
|
4913
|
-
return { kind: "bytesIntrinsic", method, receiver, args, type: receiverIr, loc };
|
|
4914
|
-
}
|
|
4915
|
-
if (name === "fill" && receiverIr.elem !== "u8") {
|
|
4916
|
-
// TypedArray.prototype.fill on the non-u8 kinds: per-element value
|
|
4917
|
-
// coercion, slice-clamped relative indices, never throws. u8
|
|
4918
|
-
// receivers keep the Buffer fill family (string patterns, throwing
|
|
4919
|
-
// offset validation) — lowerBufferInstanceMethod's surface.
|
|
4920
|
-
if (nArgs < 1 || nArgs > 3) {
|
|
4921
|
-
lowerer.noLowering(`.fill with ${nArgs} arguments on typed arrays`, call);
|
|
4922
|
-
}
|
|
4923
|
-
const receiver = lowerer.lowerExpr(access.expression);
|
|
4924
|
-
const v = lowerer.lowerExprExpecting(call.arguments[0], F64);
|
|
4925
|
-
const idx = call.arguments.slice(1).map((a) => lowerer.lowerExprExpecting(a, F64));
|
|
4926
|
-
return { kind: "bytesIntrinsic", method: "fillElem", receiver, args: [v, ...idx], type: receiverIr, loc };
|
|
4927
|
-
}
|
|
4928
|
-
if (name === "set") {
|
|
4929
|
-
if (nArgs < 1 || nArgs > 2) {
|
|
4930
|
-
lowerer.noLowering(`.set with ${nArgs} arguments on typed arrays`, call);
|
|
4931
|
-
}
|
|
4932
|
-
const receiver = lowerer.lowerExpr(access.expression);
|
|
4933
|
-
const src = lowerer.lowerExpr(call.arguments[0]);
|
|
4934
|
-
if (!typeEquals(src.type, receiverIr)) {
|
|
4935
|
-
lowerer.noLowering(`.set from '${lowerer.fmt(src.type)}' values`, call.arguments[0], "only a same-kind typed array copies in (number[] sources have no lowering — narrow unions first)");
|
|
4936
|
-
}
|
|
4937
|
-
const args = [src];
|
|
4938
|
-
if (nArgs === 2)
|
|
4939
|
-
args.push(lowerer.lowerExprExpecting(call.arguments[1], F64));
|
|
4940
|
-
return { kind: "bytesIntrinsic", method: "setFrom", receiver, args, type: VOID, loc };
|
|
4941
|
-
}
|
|
4942
|
-
if (name === "toString") {
|
|
4943
|
-
// Buffer's toString(encoding?) — utf8 by default. A 0-arg toString
|
|
4944
|
-
// resolved against the plain Uint8Array interface is JS's
|
|
4945
|
-
// Array-toString (comma join), a different operation: fenced.
|
|
4946
|
-
const declaredOnBuffer = (() => {
|
|
4947
|
-
const nameSym = lowerer.checker.getSymbolAtLocation(access.name);
|
|
4948
|
-
return nameSym !== undefined && lowerer.checker.declarationsOf(nameSym).some((d) => ts.isInterfaceDeclaration(d.parent) && d.parent.name.text === "Buffer");
|
|
4949
|
-
})();
|
|
4950
|
-
if (receiverIr.elem !== "u8" || !declaredOnBuffer) {
|
|
4951
|
-
lowerer.noLowering("typed-array toString", call, 'Buffer.from(x).toString("utf8" | "hex" | "base64") is the lowered string conversion');
|
|
4952
|
-
}
|
|
4953
|
-
if (nArgs > 3)
|
|
4954
|
-
lowerer.noLowering(`.toString with ${nArgs} arguments on Buffers`, call);
|
|
4955
|
-
const receiver = lowerer.lowerExpr(access.expression);
|
|
4956
|
-
const encNode = call.arguments[0];
|
|
4957
|
-
let method = "toString";
|
|
4958
|
-
let enc = { kind: "strLit", value: "utf8", type: STRING, loc };
|
|
4959
|
-
if (encNode) {
|
|
4960
|
-
const encType = lowerer.typeOf(encNode);
|
|
4961
|
-
const encName = encType.isStringLiteralType()
|
|
4962
|
-
? knownBufEncoding(encType.value)
|
|
4963
|
-
: undefined;
|
|
4964
|
-
if (encName !== undefined) {
|
|
4965
|
-
// Keep literals on the canonical, non-throwing fast path.
|
|
4966
|
-
enc = { kind: "strLit", value: encName, type: STRING, loc };
|
|
4967
|
-
}
|
|
4968
|
-
else {
|
|
4969
|
-
const undefinedArg = lowerStaticallyUndefinedArg(lowerer, encNode);
|
|
4970
|
-
if (undefinedArg) {
|
|
4971
|
-
// An explicit undefined is the omitted-encoding default. Keep
|
|
4972
|
-
// the canonical, non-throwing path while preserving effects
|
|
4973
|
-
// from equivalent spellings such as `void sideEffect()`.
|
|
4974
|
-
enc = defaultAfterUndefined(undefinedArg, enc);
|
|
4975
|
-
}
|
|
4976
|
-
else {
|
|
4977
|
-
// A BufferEncoding-typed variable selects its decoder at
|
|
4978
|
-
// runtime. Optional variables default their undefined arm to
|
|
4979
|
-
// utf8; the checked intrinsic canonicalizes every present
|
|
4980
|
-
// alias/case and raises Node's ERR_UNKNOWN_ENCODING when a cast
|
|
4981
|
-
// lets a bad value through.
|
|
4982
|
-
enc = lowerOptionalDefaultArg(lowerer, encNode, STRING, enc);
|
|
4983
|
-
method = "toStringVar";
|
|
4984
|
-
}
|
|
4985
|
-
}
|
|
4986
|
-
}
|
|
4987
|
-
// The range form toString(enc, start[, end]) decodes the clamped
|
|
4988
|
-
// [start, end) byte window (Node's slice-then-decode). An omitted
|
|
4989
|
-
// end stays omitted (2 intrinsic args) — the emitter supplies the
|
|
4990
|
-
// receiver's length, because EXPLICIT negative ends clamp to empty
|
|
4991
|
-
// in Node and no in-band sentinel can represent "omitted" safely.
|
|
4992
|
-
if (nArgs > 1) {
|
|
4993
|
-
const start = lowerer.lowerExprExpecting(call.arguments[1], F64);
|
|
4994
|
-
if (nArgs === 3) {
|
|
4995
|
-
const end = lowerer.lowerExprExpecting(call.arguments[2], F64);
|
|
4996
|
-
return { kind: "bytesIntrinsic", method, receiver, args: [enc, start, end], type: STRING, loc };
|
|
4997
|
-
}
|
|
4998
|
-
return { kind: "bytesIntrinsic", method, receiver, args: [enc, start], type: STRING, loc };
|
|
4999
|
-
}
|
|
5000
|
-
return { kind: "bytesIntrinsic", method, receiver, args: [enc], type: STRING, loc };
|
|
5001
|
-
}
|
|
5002
|
-
// The Buffer-declared comparison/search/mutation surface. All of
|
|
5003
|
-
// these resolve against the Buffer interface (the checker keeps most
|
|
5004
|
-
// off plain typed arrays; where the lib DOES declare a same-named
|
|
5005
|
-
// TypedArray member — indexOf, includes, fill — the semantics differ,
|
|
5006
|
-
// so only Buffer-declared resolutions lower and u8 receivers gate the
|
|
5007
|
-
// rest).
|
|
5008
|
-
const declOnBuffer = (() => {
|
|
5009
|
-
const nameSym = lowerer.checker.getSymbolAtLocation(access.name);
|
|
5010
|
-
return nameSym !== undefined && lowerer.checker.declarationsOf(nameSym).some((d) => ts.isInterfaceDeclaration(d.parent) && d.parent.name.text === "Buffer");
|
|
5011
|
-
})();
|
|
5012
|
-
if (declOnBuffer && receiverIr.elem === "u8") {
|
|
5013
|
-
const bufMethod = lowerBufferInstanceMethod(lowerer, call, access, name, loc);
|
|
5014
|
-
if (bufMethod)
|
|
5015
|
-
return bufMethod;
|
|
5016
|
-
}
|
|
5017
|
-
// The Buffer numeric read/write families — every fixed-width kind in
|
|
5018
|
-
// both endiannesses ("Uint" and "UInt" alike: Node aliases both), plus
|
|
5019
|
-
// the variable-width read/writeUIntLE quartet. The kind token rides as
|
|
5020
|
-
// a strLit args[0]; omitted offsets complete to Node's default 0.
|
|
5021
|
-
const numKind = own(BUF_NUM_METHODS, name);
|
|
5022
|
-
if (numKind !== undefined) {
|
|
5023
|
-
if (receiverIr.elem !== "u8") {
|
|
5024
|
-
lowerer.noLowering(`.${name} on a '${lowerer.fmt(receiverIr)}'`, call, "the numeric families read/write Buffer bytes");
|
|
5025
|
-
}
|
|
5026
|
-
const write = name.startsWith("write");
|
|
5027
|
-
const required = write ? 1 : 0; // value; offset defaults to 0
|
|
5028
|
-
if (nArgs < required || nArgs > required + 1) {
|
|
5029
|
-
lowerer.noLowering(`.${name} with ${nArgs} arguments`, call);
|
|
5030
|
-
}
|
|
5031
|
-
const receiver = lowerer.lowerExpr(access.expression);
|
|
5032
|
-
const kind = { kind: "strLit", value: numKind, type: STRING, loc };
|
|
5033
|
-
const args = [kind, ...call.arguments.map((a) => lowerer.lowerExprExpecting(a, F64))];
|
|
5034
|
-
if (nArgs === required) {
|
|
5035
|
-
args.push({ kind: "numLit", value: 0, type: F64, loc }); // Node's offset default
|
|
5036
|
-
}
|
|
5037
|
-
return { kind: "bytesIntrinsic", method: write ? "writeNum" : "readNum", receiver, args, type: F64, loc };
|
|
5038
|
-
}
|
|
5039
|
-
const varKind = own(BUF_NUM_VAR_METHODS, name);
|
|
5040
|
-
if (varKind !== undefined) {
|
|
5041
|
-
if (receiverIr.elem !== "u8") {
|
|
5042
|
-
lowerer.noLowering(`.${name} on a '${lowerer.fmt(receiverIr)}'`, call, "the numeric families read/write Buffer bytes");
|
|
5043
|
-
}
|
|
5044
|
-
const write = name.startsWith("write");
|
|
5045
|
-
const required = write ? 3 : 2; // Node declares offset AND byteLength required
|
|
5046
|
-
if (nArgs !== required) {
|
|
5047
|
-
lowerer.noLowering(`.${name} with ${nArgs} arguments`, call);
|
|
5048
|
-
}
|
|
5049
|
-
const receiver = lowerer.lowerExpr(access.expression);
|
|
5050
|
-
const kind = { kind: "strLit", value: varKind, type: STRING, loc };
|
|
5051
|
-
const args = [kind, ...call.arguments.map((a) => lowerer.lowerExprExpecting(a, F64))];
|
|
5052
|
-
return { kind: "bytesIntrinsic", method: write ? "writeNumVar" : "readNumVar", receiver, args, type: F64, loc };
|
|
5053
|
-
}
|
|
5054
|
-
// DataView getters (DataView maps to bytes<u8>, so the receiver kind
|
|
5055
|
-
// and stdlib provenance land here; the checker keeps these names off
|
|
5056
|
-
// typed arrays and Buffers). The multi-byte kinds take the optional
|
|
5057
|
-
// littleEndian bool (omitted = big-endian, the JS default). All THROW
|
|
5058
|
-
// Node's RangeError on a bad offset (may-throw seeds).
|
|
5059
|
-
const dvGetter = own(DV_GETTERS, name);
|
|
5060
|
-
if (dvGetter !== undefined && receiverIr.elem === "u8") {
|
|
5061
|
-
if (dvGetter.method === "dvGetBigUint64Number" || dvGetter.method === "dvGetBigInt64Number") {
|
|
5062
|
-
// bigint values have no representation — only the COMPOSED
|
|
5063
|
-
// `Number(view.getBigUint64/getBigInt64(...))` compiles (the
|
|
5064
|
-
// randomBytesToString pattern): the 8-byte integer converts to
|
|
5065
|
-
// double exactly as Number(bigint) would, and the bigint never
|
|
5066
|
-
// exists as a value (the f64-typed intrinsic result IS what the
|
|
5067
|
-
// identity Number(f64) lowering hands back).
|
|
5068
|
-
const p = call.parent;
|
|
5069
|
-
const composed = ts.isCallExpression(p) &&
|
|
5070
|
-
p.arguments.length === 1 &&
|
|
5071
|
-
p.arguments[0] === call &&
|
|
5072
|
-
ts.isIdentifier(p.expression) &&
|
|
5073
|
-
p.expression.text === "Number" &&
|
|
5074
|
-
lowerer.isStdlibSymbol(lowerer.resolveValueSymbol(p.expression) ?? undefined);
|
|
5075
|
-
if (!composed) {
|
|
5076
|
-
lowerer.noLowering(`.${name} outside Number(...)`, call, `bigint values have no representation — Number(view.${name}(offset, littleEndian?)) is the lowered form`);
|
|
5077
|
-
}
|
|
5078
|
-
}
|
|
5079
|
-
const maxArgs = dvGetter.le ? 2 : 1;
|
|
5080
|
-
if (nArgs < 1 || nArgs > maxArgs) {
|
|
5081
|
-
lowerer.noLowering(`.${name} with ${nArgs} arguments`, call);
|
|
5082
|
-
}
|
|
5083
|
-
const receiver = lowerer.lowerExpr(access.expression);
|
|
5084
|
-
const args = [lowerer.lowerExprExpecting(call.arguments[0], F64)];
|
|
5085
|
-
if (nArgs === 2)
|
|
5086
|
-
args.push(lowerer.lowerExprExpecting(call.arguments[1], BOOL));
|
|
5087
|
-
return { kind: "bytesIntrinsic", method: dvGetter.method, receiver, args, type: F64, loc };
|
|
5088
|
-
}
|
|
5089
|
-
// DataView setters — the getters' mirror: (byteOffset, value) plus the
|
|
5090
|
-
// optional littleEndian bool on the multi-byte kinds. Void results;
|
|
5091
|
-
// the same constant Node RangeError on a bad offset (may-throw seeds).
|
|
5092
|
-
// setBigUint64/setBigInt64 never lower (bigint ARGUMENTS have no
|
|
5093
|
-
// representation and no composed form exists) — they keep the member
|
|
5094
|
-
// fence, as does setFloat16.
|
|
5095
|
-
const dvSetter = own(DV_SETTERS, name);
|
|
5096
|
-
if (dvSetter !== undefined && receiverIr.elem === "u8") {
|
|
5097
|
-
const maxArgs = dvSetter.le ? 3 : 2;
|
|
5098
|
-
if (nArgs < 2 || nArgs > maxArgs) {
|
|
5099
|
-
lowerer.noLowering(`.${name} with ${nArgs} arguments`, call);
|
|
5100
|
-
}
|
|
5101
|
-
const receiver = lowerer.lowerExpr(access.expression);
|
|
5102
|
-
const args = [
|
|
5103
|
-
lowerer.lowerExprExpecting(call.arguments[0], F64),
|
|
5104
|
-
lowerer.lowerExprExpecting(call.arguments[1], F64),
|
|
5105
|
-
];
|
|
5106
|
-
if (nArgs === 3)
|
|
5107
|
-
args.push(lowerer.lowerExprExpecting(call.arguments[2], BOOL));
|
|
5108
|
-
return { kind: "bytesIntrinsic", method: dvSetter.method, receiver, args, type: VOID, loc };
|
|
5109
|
-
}
|
|
5110
|
-
return null;
|
|
5111
|
-
}
|
|
5112
|
-
/** Node's Buffer encoding names → the runtime's canonical spelling (every
|
|
5113
|
-
* alias folds at compile time; the runtime sees only canonical names).
|
|
5114
|
-
* Shared by toString, Buffer.from, and Buffer.byteLength. */
|
|
5115
|
-
const BUF_ENCODINGS = {
|
|
5116
|
-
utf8: "utf8",
|
|
5117
|
-
"utf-8": "utf8",
|
|
5118
|
-
hex: "hex",
|
|
5119
|
-
base64: "base64",
|
|
5120
|
-
base64url: "base64url",
|
|
5121
|
-
latin1: "latin1",
|
|
5122
|
-
binary: "latin1",
|
|
5123
|
-
ascii: "ascii",
|
|
5124
|
-
utf16le: "utf16le",
|
|
5125
|
-
"utf-16le": "utf16le",
|
|
5126
|
-
ucs2: "utf16le",
|
|
5127
|
-
"ucs-2": "utf16le",
|
|
5128
|
-
};
|
|
5129
|
-
/** A literal encoding's canonical name, or undefined for a spelling Node
|
|
5130
|
-
* does not know — the ladder callers (stream options) turn unknown
|
|
5131
|
-
* literals into Node's runtime ERR_UNKNOWN_ENCODING throw. */
|
|
5132
|
-
export function knownBufEncoding(name) {
|
|
5133
|
-
return own(BUF_ENCODINGS, name);
|
|
5134
|
-
}
|
|
5135
|
-
/** The literal encoding argument of a Buffer surface, normalized — or a
|
|
5136
|
-
* fence when it isn't a literal alias Node knows. */
|
|
5137
|
-
export function bufEncoding(lowerer, what, encNode) {
|
|
5138
|
-
const t = lowerer.typeOf(encNode);
|
|
5139
|
-
const v = t.isStringLiteralType() ? own(BUF_ENCODINGS, t.value) : undefined;
|
|
5140
|
-
if (v === undefined) {
|
|
5141
|
-
lowerer.noLowering(`${what} with this encoding`, encNode, 'a literal "utf8", "hex", "base64", "base64url", "latin1", "binary", "ascii", "utf16le", or "ucs2" spelling is the lowered encoding surface');
|
|
5142
|
-
}
|
|
5143
|
-
return v;
|
|
5144
|
-
}
|
|
5145
|
-
/** The fixed-width Buffer numeric methods by source name → the readNum/
|
|
5146
|
-
* writeNum kind token. Node declares BOTH capitalizations ("UInt" is the
|
|
5147
|
-
* original, "Uint" the aliased spelling) — the tables carry both. */
|
|
5148
|
-
const BUF_NUM_METHODS = (() => {
|
|
5149
|
-
const out = {};
|
|
5150
|
-
for (const rw of ["read", "write"]) {
|
|
5151
|
-
for (const u of ["UInt", "Uint"]) {
|
|
5152
|
-
out[`${rw}${u}8`] = "u8";
|
|
5153
|
-
out[`${rw}${u}16BE`] = "u16be";
|
|
5154
|
-
out[`${rw}${u}16LE`] = "u16le";
|
|
5155
|
-
out[`${rw}${u}32BE`] = "u32be";
|
|
5156
|
-
out[`${rw}${u}32LE`] = "u32le";
|
|
5157
|
-
}
|
|
5158
|
-
out[`${rw}Int8`] = "i8";
|
|
5159
|
-
out[`${rw}Int16BE`] = "i16be";
|
|
5160
|
-
out[`${rw}Int16LE`] = "i16le";
|
|
5161
|
-
out[`${rw}Int32BE`] = "i32be";
|
|
5162
|
-
out[`${rw}Int32LE`] = "i32le";
|
|
5163
|
-
out[`${rw}FloatBE`] = "f32be";
|
|
5164
|
-
out[`${rw}FloatLE`] = "f32le";
|
|
5165
|
-
out[`${rw}DoubleBE`] = "f64be";
|
|
5166
|
-
out[`${rw}DoubleLE`] = "f64le";
|
|
5167
|
-
}
|
|
5168
|
-
return out;
|
|
5169
|
-
})();
|
|
5170
|
-
/** The variable-width quartet (offset + byteLength) → sign/endian token. */
|
|
5171
|
-
const BUF_NUM_VAR_METHODS = {
|
|
5172
|
-
readUIntBE: "ube",
|
|
5173
|
-
readUintBE: "ube",
|
|
5174
|
-
readUIntLE: "ule",
|
|
5175
|
-
readUintLE: "ule",
|
|
5176
|
-
readIntBE: "ibe",
|
|
5177
|
-
readIntLE: "ile",
|
|
5178
|
-
writeUIntBE: "ube",
|
|
5179
|
-
writeUintBE: "ube",
|
|
5180
|
-
writeUIntLE: "ule",
|
|
5181
|
-
writeUintLE: "ule",
|
|
5182
|
-
writeIntBE: "ibe",
|
|
5183
|
-
writeIntLE: "ile",
|
|
5184
|
-
};
|
|
5185
|
-
/** The Buffer-declared comparison/search/mutation methods on a u8
|
|
5186
|
-
* receiver: equals, compare, indexOf/lastIndexOf/includes (number,
|
|
5187
|
-
* string-with-encoding, and Buffer needles), fill, copy, swap16/32/64,
|
|
5188
|
-
* and write. Null when `name` isn't one of them (the numeric families
|
|
5189
|
-
* and the generic bytes surface try next). Encodings must be literals
|
|
5190
|
-
* (bufEncoding's fence); a trailing string-typed argument is the
|
|
5191
|
-
* encoding, exactly Node's overloads. */
|
|
5192
|
-
function lowerBufferInstanceMethod(lowerer, call, access, name, loc) {
|
|
5193
|
-
const args = call.arguments;
|
|
5194
|
-
const nArgs = args.length;
|
|
5195
|
-
if (args.some(ts.isSpreadElement))
|
|
5196
|
-
return null;
|
|
5197
|
-
const isStringArg = (i) => lowerer.mapTypeOf(lowerer.typeOf(args[i]))?.kind === "string";
|
|
5198
|
-
const u8Arg = (i) => {
|
|
5199
|
-
const v = lowerer.lowerExpr(args[i]);
|
|
5200
|
-
if (!(v.type.kind === "bytes" && v.type.elem === "u8")) {
|
|
5201
|
-
lowerer.noLowering(`.${name} of '${lowerer.fmt(v.type)}' values`, args[i], "a Buffer/Uint8Array argument is the lowered shape (narrow unions first)");
|
|
5202
|
-
}
|
|
5203
|
-
return v;
|
|
5204
|
-
};
|
|
5205
|
-
// The checked-dynamic crossing for a compare/equals argument slot: dyn
|
|
5206
|
-
// passes through, convertible statics (the invalid-input probes'
|
|
5207
|
-
// literals) wrap in dynFrom, `undefined` literals ride the same wrap.
|
|
5208
|
-
// Fences (never returns) when the value cannot cross — an island jsval.
|
|
5209
|
-
const chkArg = (i) => {
|
|
5210
|
-
// Object literals take the dyn literal path directly (method members
|
|
5211
|
-
// box as dyn functions — the typed record fence never applies).
|
|
5212
|
-
if (ts.isObjectLiteralExpression(args[i])) {
|
|
5213
|
-
return lowerDynObjectLiteral(lowerer, args[i]);
|
|
5214
|
-
}
|
|
5215
|
-
const v = lowerer.lowerExpr(args[i]);
|
|
5216
|
-
if (v.type.kind === "dyn")
|
|
5217
|
-
return v;
|
|
5218
|
-
if (v.kind === "unitLit" || lowerer.dynConvertible(v.type)) {
|
|
5219
|
-
return { kind: "dynFrom", value: v, type: DYN, loc: v.loc };
|
|
5220
|
-
}
|
|
5221
|
-
lowerer.noLowering(`.${name} of '${lowerer.fmt(v.type)}' values`, args[i], "a Buffer/Uint8Array argument is the lowered shape (narrow unions first)");
|
|
5222
|
-
};
|
|
5223
|
-
const argIrKind = (i) => lowerer.mapTypeOf(lowerer.typeOf(args[i]));
|
|
5224
|
-
if (name === "equals") {
|
|
5225
|
-
if (nArgs !== 1)
|
|
5226
|
-
lowerer.noLowering(`.equals with ${nArgs} arguments`, call);
|
|
5227
|
-
if (argIrKind(0)?.kind === "bytes") {
|
|
5228
|
-
const other = u8Arg(0);
|
|
5229
|
-
const receiver = lowerer.lowerExpr(access.expression);
|
|
5230
|
-
return { kind: "bytesIntrinsic", method: "equals", receiver, args: [other], type: BOOL, loc };
|
|
5231
|
-
}
|
|
5232
|
-
// Not statically bytes (the invalid-input probes, untyped JS
|
|
5233
|
-
// helpers): Node's "otherBuffer" argument ladder runs at runtime.
|
|
5234
|
-
const receiver = lowerer.lowerExpr(access.expression);
|
|
5235
|
-
return { kind: "libCall", fn: "bytes.equalsChk", args: [receiver, chkArg(0)], type: BOOL, loc };
|
|
5236
|
-
}
|
|
5237
|
-
if (name === "compare") {
|
|
5238
|
-
if (nArgs > 5)
|
|
5239
|
-
lowerer.noLowering(`.compare with ${nArgs} arguments`, call);
|
|
5240
|
-
const fast = nArgs >= 1 &&
|
|
5241
|
-
argIrKind(0)?.kind === "bytes" &&
|
|
5242
|
-
call.arguments.slice(1).every((a) => lowerer.mapTypeOf(lowerer.typeOf(a))?.kind === "f64");
|
|
5243
|
-
if (fast) {
|
|
5244
|
-
const target = u8Arg(0);
|
|
5245
|
-
const idx = call.arguments.slice(1).map((a) => lowerer.lowerExprExpecting(a, F64));
|
|
5246
|
-
const receiver = lowerer.lowerExpr(access.expression);
|
|
5247
|
-
return { kind: "bytesIntrinsic", method: "compareBuf", receiver, args: [target, ...idx], type: F64, loc };
|
|
5248
|
-
}
|
|
5249
|
-
// An absent/ill-typed target or offset (`a.compare()`, string
|
|
5250
|
-
// offsets, explicit undefined): Node's target/targetStart/targetEnd/
|
|
5251
|
-
// sourceStart/sourceEnd ladder runs at runtime; absent slots pass
|
|
5252
|
-
// the undefined dyn (Node defaults apply there).
|
|
5253
|
-
const receiver = lowerer.lowerExpr(access.expression);
|
|
5254
|
-
const slots = [];
|
|
5255
|
-
for (let i = 0; i < 5; i++)
|
|
5256
|
-
slots.push(i < nArgs ? chkArg(i) : dynUndefinedExpr(loc));
|
|
5257
|
-
return { kind: "libCall", fn: "bytes.compareChk", args: [receiver, ...slots], type: F64, loc };
|
|
5258
|
-
}
|
|
5259
|
-
if (name === "indexOf" || name === "lastIndexOf" || name === "includes") {
|
|
5260
|
-
if (nArgs < 1 || nArgs > 3)
|
|
5261
|
-
lowerer.noLowering(`.${name} with ${nArgs} arguments`, call);
|
|
5262
|
-
// The overloads: (v), (v, byteOffset), (v, encoding), (v, byteOffset,
|
|
5263
|
-
// encoding) — a trailing string-typed arg is the encoding.
|
|
5264
|
-
let encName = "utf8";
|
|
5265
|
-
let offNode;
|
|
5266
|
-
if (nArgs === 3) {
|
|
5267
|
-
offNode = args[1];
|
|
5268
|
-
encName = bufEncoding(lowerer, `.${name}`, args[2]);
|
|
5269
|
-
}
|
|
5270
|
-
else if (nArgs === 2) {
|
|
5271
|
-
if (isStringArg(1))
|
|
5272
|
-
encName = bufEncoding(lowerer, `.${name}`, args[1]);
|
|
5273
|
-
else
|
|
5274
|
-
offNode = args[1];
|
|
5275
|
-
}
|
|
5276
|
-
const resultT = name === "includes" ? BOOL : F64;
|
|
5277
|
-
const vT = lowerer.mapTypeOf(lowerer.typeOf(args[0]));
|
|
5278
|
-
const receiver = lowerer.lowerExpr(access.expression);
|
|
5279
|
-
if (vT?.kind === "f64") {
|
|
5280
|
-
// A number needle wraps & 0xFF at runtime (Buffer semantics; the
|
|
5281
|
-
// encoding is irrelevant, like Node).
|
|
5282
|
-
const v = lowerer.lowerExprExpecting(args[0], F64);
|
|
5283
|
-
const numArgs = [v, ...(offNode ? [lowerer.lowerExprExpecting(offNode, F64)] : [])];
|
|
5284
|
-
const method = name === "indexOf" ? "indexOfNum" : name === "lastIndexOf" ? "lastIndexOfNum" : "includesNum";
|
|
5285
|
-
return { kind: "bytesIntrinsic", method, receiver, args: numArgs, type: resultT, loc };
|
|
5286
|
-
}
|
|
5287
|
-
let needle;
|
|
5288
|
-
let align = 1;
|
|
5289
|
-
if (vT?.kind === "string") {
|
|
5290
|
-
const s = lowerer.lowerExprExpecting(args[0], STRING);
|
|
5291
|
-
const enc = { kind: "strLit", value: encName, type: STRING, loc };
|
|
5292
|
-
needle = { kind: "libCall", fn: "buffer.fromStr", args: [s, enc], type: BYTES_U8, loc };
|
|
5293
|
-
if (encName === "utf16le")
|
|
5294
|
-
align = 2;
|
|
5295
|
-
}
|
|
5296
|
-
else if (vT?.kind === "bytes" && vT.elem === "u8") {
|
|
5297
|
-
needle = lowerer.lowerExpr(args[0]);
|
|
5298
|
-
}
|
|
5299
|
-
else {
|
|
5300
|
-
lowerer.noLowering(`.${name} of '${lowerer.checker.typeToString(lowerer.typeOf(args[0]))}' values`, args[0], "string, number, and Buffer/Uint8Array needles search (narrow unions first)");
|
|
5301
|
-
}
|
|
5302
|
-
const alignLit = { kind: "numLit", value: align, type: F64, loc };
|
|
5303
|
-
const searchArgs = [needle, alignLit, ...(offNode ? [lowerer.lowerExprExpecting(offNode, F64)] : [])];
|
|
5304
|
-
return { kind: "bytesIntrinsic", method: name, receiver, args: searchArgs, type: resultT, loc };
|
|
5305
|
-
}
|
|
5306
|
-
if (name === "fill") {
|
|
5307
|
-
if (nArgs < 1 || nArgs > 4)
|
|
5308
|
-
lowerer.noLowering(`.fill with ${nArgs} arguments`, call);
|
|
5309
|
-
// A trailing string-typed arg past the value is the encoding.
|
|
5310
|
-
let encNode;
|
|
5311
|
-
let idxNodes = call.arguments.slice(1);
|
|
5312
|
-
if (idxNodes.length > 0 && isStringArg(nArgs - 1)) {
|
|
5313
|
-
encNode = idxNodes[idxNodes.length - 1];
|
|
5314
|
-
idxNodes = idxNodes.slice(0, -1);
|
|
5315
|
-
}
|
|
5316
|
-
if (idxNodes.length > 2)
|
|
5317
|
-
lowerer.noLowering(`.fill with ${nArgs} arguments`, call);
|
|
5318
|
-
const idx = idxNodes.map((a) => lowerer.lowerExprExpecting(a, F64));
|
|
5319
|
-
const receiverT = lowerer.mapTypeOf(lowerer.typeOf(access.expression));
|
|
5320
|
-
if (receiverT?.kind !== "bytes")
|
|
5321
|
-
lowerer.badType(access.expression, lowerer.typeOf(access.expression));
|
|
5322
|
-
const vT = lowerer.mapTypeOf(lowerer.typeOf(args[0]));
|
|
5323
|
-
const receiver = lowerer.lowerExpr(access.expression);
|
|
5324
|
-
if (vT?.kind === "string") {
|
|
5325
|
-
const encName = encNode ? bufEncoding(lowerer, ".fill", encNode) : "utf8";
|
|
5326
|
-
const s = lowerer.lowerExprExpecting(args[0], STRING);
|
|
5327
|
-
const enc = { kind: "strLit", value: encName, type: STRING, loc };
|
|
5328
|
-
return { kind: "bytesIntrinsic", method: "fillStr", receiver, args: [s, enc, ...idx], type: BYTES_U8, loc };
|
|
5329
|
-
}
|
|
5330
|
-
if (encNode)
|
|
5331
|
-
lowerer.noLowering(".fill with an encoding on a non-string value", encNode);
|
|
5332
|
-
if (vT?.kind === "f64") {
|
|
5333
|
-
const v = lowerer.lowerExprExpecting(args[0], F64);
|
|
5334
|
-
return { kind: "bytesIntrinsic", method: "fillNum", receiver, args: [v, ...idx], type: BYTES_U8, loc };
|
|
5335
|
-
}
|
|
5336
|
-
const pattern = u8Arg(0);
|
|
5337
|
-
return { kind: "bytesIntrinsic", method: "fill", receiver, args: [pattern, ...idx], type: BYTES_U8, loc };
|
|
5338
|
-
}
|
|
5339
|
-
if (name === "copy") {
|
|
5340
|
-
if (nArgs < 1 || nArgs > 4)
|
|
5341
|
-
lowerer.noLowering(`.copy with ${nArgs} arguments`, call);
|
|
5342
|
-
const target = u8Arg(0);
|
|
5343
|
-
const idx = call.arguments.slice(1).map((a) => lowerer.lowerExprExpecting(a, F64));
|
|
5344
|
-
const receiver = lowerer.lowerExpr(access.expression);
|
|
5345
|
-
return { kind: "bytesIntrinsic", method: "copy", receiver, args: [target, ...idx], type: F64, loc };
|
|
5346
|
-
}
|
|
5347
|
-
if (name === "swap16" || name === "swap32" || name === "swap64") {
|
|
5348
|
-
if (nArgs !== 0)
|
|
5349
|
-
lowerer.noLowering(`.${name} with ${nArgs} arguments`, call);
|
|
5350
|
-
const receiver = lowerer.lowerExpr(access.expression);
|
|
5351
|
-
return { kind: "bytesIntrinsic", method: name, receiver, args: [], type: BYTES_U8, loc };
|
|
5352
|
-
}
|
|
5353
|
-
if (name === "write") {
|
|
5354
|
-
if (nArgs < 1 || nArgs > 4)
|
|
5355
|
-
lowerer.noLowering(`.write with ${nArgs} arguments`, call);
|
|
5356
|
-
// (str), (str, enc), (str, offset), (str, offset, enc),
|
|
5357
|
-
// (str, offset, length), (str, offset, length, enc).
|
|
5358
|
-
let encNode;
|
|
5359
|
-
let idxNodes = call.arguments.slice(1);
|
|
5360
|
-
if (idxNodes.length > 0 && isStringArg(nArgs - 1)) {
|
|
5361
|
-
encNode = idxNodes[idxNodes.length - 1];
|
|
5362
|
-
idxNodes = idxNodes.slice(0, -1);
|
|
5363
|
-
}
|
|
5364
|
-
if (idxNodes.length > 2)
|
|
5365
|
-
lowerer.noLowering(`.write with ${nArgs} arguments`, call);
|
|
5366
|
-
const encName = encNode ? bufEncoding(lowerer, "Buffer.write", encNode) : "utf8";
|
|
5367
|
-
const s = lowerer.lowerExprExpecting(args[0], STRING);
|
|
5368
|
-
const enc = { kind: "strLit", value: encName, type: STRING, loc };
|
|
5369
|
-
const offset = idxNodes[0]
|
|
5370
|
-
? lowerer.lowerExprExpecting(idxNodes[0], F64)
|
|
5371
|
-
: { kind: "numLit", value: 0, type: F64, loc };
|
|
5372
|
-
const writeArgs = [s, enc, offset, ...(idxNodes[1] ? [lowerer.lowerExprExpecting(idxNodes[1], F64)] : [])];
|
|
5373
|
-
const receiver = lowerer.lowerExpr(access.expression);
|
|
5374
|
-
return { kind: "bytesIntrinsic", method: "writeStr", receiver, args: writeArgs, type: F64, loc };
|
|
5375
|
-
}
|
|
5376
|
-
return null;
|
|
5377
|
-
}
|
|
5378
|
-
/** The DataView getter surface by source name. `le` marks the multi-byte
|
|
5379
|
-
* kinds whose lib signature declares the optional littleEndian parameter
|
|
5380
|
-
* (the 8-bit getters take none). The Big pair only compiles composed
|
|
5381
|
-
* inside Number(...) — see the use site. */
|
|
5382
|
-
const DV_GETTERS = {
|
|
5383
|
-
getUint8: { method: "dvGetUint8", le: false },
|
|
5384
|
-
getInt8: { method: "dvGetInt8", le: false },
|
|
5385
|
-
getUint16: { method: "dvGetUint16", le: true },
|
|
5386
|
-
getInt16: { method: "dvGetInt16", le: true },
|
|
5387
|
-
getUint32: { method: "dvGetUint32", le: true },
|
|
5388
|
-
getInt32: { method: "dvGetInt32", le: true },
|
|
5389
|
-
getFloat32: { method: "dvGetFloat32", le: true },
|
|
5390
|
-
getFloat64: { method: "dvGetFloat64", le: true },
|
|
5391
|
-
getBigUint64: { method: "dvGetBigUint64Number", le: true },
|
|
5392
|
-
getBigInt64: { method: "dvGetBigInt64Number", le: true },
|
|
5393
|
-
};
|
|
5394
|
-
const DV_SETTERS = {
|
|
5395
|
-
setUint8: { method: "dvSetUint8", le: false },
|
|
5396
|
-
setInt8: { method: "dvSetInt8", le: false },
|
|
5397
|
-
setUint16: { method: "dvSetUint16", le: true },
|
|
5398
|
-
setInt16: { method: "dvSetInt16", le: true },
|
|
5399
|
-
setUint32: { method: "dvSetUint32", le: true },
|
|
5400
|
-
setInt32: { method: "dvSetInt32", le: true },
|
|
5401
|
-
setFloat32: { method: "dvSetFloat32", le: true },
|
|
5402
|
-
setFloat64: { method: "dvSetFloat64", le: true },
|
|
5403
|
-
};
|
|
5404
|
-
/** The Buffer statics — `Buffer.from(...)`, `Buffer.alloc(n)`,
|
|
5405
|
-
* `Buffer.concat(list)`, `Buffer.isBuffer(x)` — on THE stdlib Buffer
|
|
5406
|
-
* global (name + provenance; fallback and @types/node alike). Null when
|
|
5407
|
-
* the callee isn't a Buffer-static access. */
|
|
5408
|
-
export function lowerBufferStaticCall(lowerer, call, access) {
|
|
5409
|
-
if (call.questionDotToken || access.questionDotToken)
|
|
5410
|
-
return null;
|
|
5411
|
-
if (!lowerer.isStdlibGlobal(access.expression, "Buffer"))
|
|
5412
|
-
return null;
|
|
5413
|
-
const member = access.name.text;
|
|
5414
|
-
const loc = locOf(call);
|
|
5415
|
-
const args = call.arguments;
|
|
5416
|
-
if (member === "from") {
|
|
5417
|
-
// Buffer.from(x.buffer[, byteOffset[, length]]): a u8 VIEW sharing
|
|
5418
|
-
// x's storage (Node shares the ArrayBuffer; length is in BYTES).
|
|
5419
|
-
// The first argument must be the SYNTACTIC `.buffer` of a typed
|
|
5420
|
-
// array — the DataView peel — and the construction rides the same
|
|
5421
|
-
// dataViewNew intrinsic (offset/length validation throws Node's
|
|
5422
|
-
// DataView-shaped RangeErrors; valid indices behave exactly).
|
|
5423
|
-
if (args.length >= 1 && args.length <= 3 && !args.some(ts.isSpreadElement) &&
|
|
5424
|
-
ts.isPropertyAccessExpression(args[0]) && args[0].name.text === "buffer") {
|
|
5425
|
-
const srcNode = args[0].expression;
|
|
5426
|
-
const srcIr = lowerer.mapTypeOf(lowerer.typeOf(srcNode));
|
|
5427
|
-
if (srcIr?.kind === "bytes") {
|
|
5428
|
-
const receiver = lowerer.lowerExpr(srcNode);
|
|
5429
|
-
const idxArgs = args.slice(1).map((a) => lowerer.lowerExprExpecting(a, F64));
|
|
5430
|
-
return { kind: "bytesIntrinsic", method: "dataViewNew", receiver, args: idxArgs, type: BYTES_U8, loc };
|
|
5431
|
-
}
|
|
5432
|
-
}
|
|
5433
|
-
if (args.length >= 1 && args.length <= 2 && !ts.isSpreadElement(args[0])) {
|
|
5434
|
-
const argNode = args[0];
|
|
5435
|
-
if (ts.isArrayLiteralExpression(argNode) && !argNode.elements.some(ts.isSpreadElement)) {
|
|
5436
|
-
// A number[] literal (contextually typed by the lib's readonly
|
|
5437
|
-
// number[] slot — build it element-wise, the Set-seed pattern).
|
|
5438
|
-
if (args.length === 1) {
|
|
5439
|
-
const elems = argNode.elements.map((el) => lowerer.lowerExprExpecting(el, F64));
|
|
5440
|
-
const seed = { kind: "arrayLit", elems, type: arrayOf(F64), loc };
|
|
5441
|
-
return { kind: "bytesNew", source: seed, type: BYTES_U8, loc };
|
|
5442
|
-
}
|
|
5443
|
-
}
|
|
5444
|
-
else {
|
|
5445
|
-
const srcIr = lowerer.mapTypeOf(lowerer.typeOf(argNode));
|
|
5446
|
-
if (srcIr?.kind === "string") {
|
|
5447
|
-
const encNode = args[1];
|
|
5448
|
-
const encName = encNode ? bufEncoding(lowerer, "Buffer.from", encNode) : "utf8";
|
|
5449
|
-
const s = lowerer.lowerExprExpecting(argNode, STRING);
|
|
5450
|
-
const enc = { kind: "strLit", value: encName, type: STRING, loc };
|
|
5451
|
-
return { kind: "libCall", fn: "buffer.fromStr", args: [s, enc], type: BYTES_U8, loc };
|
|
5452
|
-
}
|
|
5453
|
-
if (args.length === 1 && srcIr?.kind === "bytes" && srcIr.elem === "u8") {
|
|
5454
|
-
return { kind: "bytesNew", source: lowerer.lowerExpr(argNode), type: BYTES_U8, loc };
|
|
5455
|
-
}
|
|
5456
|
-
if (args.length === 1 && srcIr?.kind === "array" && srcIr.elem.kind === "f64") {
|
|
5457
|
-
return { kind: "bytesNew", source: lowerer.lowerExpr(argNode), type: BYTES_U8, loc };
|
|
5458
|
-
}
|
|
5459
|
-
}
|
|
5460
|
-
}
|
|
5461
|
-
lowerer.noLowering("Buffer.from with this argument shape", call, "supported: Buffer.from(string, encoding?) with a literal encoding, Buffer.from(u8Array) — a copy — " +
|
|
5462
|
-
"Buffer.from(number[]), or Buffer.from(x.buffer, byteOffset?, length?) — a view sharing x's " +
|
|
5463
|
-
"storage (no free-standing ArrayBuffer value exists; narrow unions first)");
|
|
5464
|
-
}
|
|
5465
|
-
if (member === "alloc" || member === "allocUnsafe") {
|
|
5466
|
-
const maxArgs = member === "alloc" ? 3 : 1;
|
|
5467
|
-
if (args.length < 1 || args.length > maxArgs || args.some(ts.isSpreadElement)) {
|
|
5468
|
-
lowerer.noLowering(`Buffer.${member} with ${args.length} arguments`, call);
|
|
5469
|
-
}
|
|
5470
|
-
// allocUnsafe's contents are UNSPECIFIED in Node (uninitialized pool
|
|
5471
|
-
// memory); a zero-filled buffer is a valid instance of unspecified,
|
|
5472
|
-
// and the deterministic choice — a program observing the difference
|
|
5473
|
-
// is depending on garbage.
|
|
5474
|
-
const size = lowerer.lowerExprExpecting(args[0], F64);
|
|
5475
|
-
const fresh = { kind: "bytesNew", source: size, type: BYTES_U8, loc };
|
|
5476
|
-
if (args.length === 1)
|
|
5477
|
-
return fresh;
|
|
5478
|
-
// alloc(size, fill, encoding?): the fill semantics ARE fill()'s —
|
|
5479
|
-
// the fresh buffer is the receiver of a whole-range fill.
|
|
5480
|
-
const fillT = lowerer.mapTypeOf(lowerer.typeOf(args[1]));
|
|
5481
|
-
if (fillT?.kind === "string") {
|
|
5482
|
-
const encName = args[2] ? bufEncoding(lowerer, "Buffer.alloc", args[2]) : "utf8";
|
|
5483
|
-
const s = lowerer.lowerExprExpecting(args[1], STRING);
|
|
5484
|
-
const enc = { kind: "strLit", value: encName, type: STRING, loc };
|
|
5485
|
-
return { kind: "bytesIntrinsic", method: "fillStr", receiver: fresh, args: [s, enc], type: BYTES_U8, loc };
|
|
5486
|
-
}
|
|
5487
|
-
if (args.length > 2) {
|
|
5488
|
-
lowerer.noLowering("Buffer.alloc with an encoding on a non-string fill", args[2]);
|
|
5489
|
-
}
|
|
5490
|
-
if (fillT?.kind === "f64") {
|
|
5491
|
-
const v = lowerer.lowerExprExpecting(args[1], F64);
|
|
5492
|
-
return { kind: "bytesIntrinsic", method: "fillNum", receiver: fresh, args: [v], type: BYTES_U8, loc };
|
|
5493
|
-
}
|
|
5494
|
-
if (fillT?.kind === "bytes" && fillT.elem === "u8") {
|
|
5495
|
-
const pattern = lowerer.lowerExpr(args[1]);
|
|
5496
|
-
return { kind: "bytesIntrinsic", method: "fill", receiver: fresh, args: [pattern], type: BYTES_U8, loc };
|
|
5497
|
-
}
|
|
5498
|
-
lowerer.noLowering(`Buffer.alloc with a '${lowerer.checker.typeToString(lowerer.typeOf(args[1]))}' fill`, args[1], "number, string, and Buffer/Uint8Array fills are the lowered shapes");
|
|
5499
|
-
}
|
|
5500
|
-
if (member === "concat") {
|
|
5501
|
-
if (args.length < 1 || args.length > 2 || args.some(ts.isSpreadElement)) {
|
|
5502
|
-
lowerer.noLowering(`Buffer.concat with ${args.length} arguments`, call);
|
|
5503
|
-
}
|
|
5504
|
-
const argNode = args[0];
|
|
5505
|
-
let list;
|
|
5506
|
-
if (ts.isArrayLiteralExpression(argNode) && !argNode.elements.some(ts.isSpreadElement)) {
|
|
5507
|
-
const elems = argNode.elements.map((el) => lowerer.lowerExprExpecting(el, BYTES_U8));
|
|
5508
|
-
list = { kind: "arrayLit", elems, type: arrayOf(BYTES_U8), loc };
|
|
5509
|
-
}
|
|
5510
|
-
else {
|
|
5511
|
-
list = lowerer.lowerExpr(argNode);
|
|
5512
|
-
if (!typeEquals(list.type, arrayOf(BYTES_U8))) {
|
|
5513
|
-
lowerer.noLowering(`Buffer.concat of '${lowerer.fmt(list.type)}' values`, argNode, "one Uint8Array[]/Buffer[] value (or literal) is the lowered list shape");
|
|
5514
|
-
}
|
|
5515
|
-
}
|
|
5516
|
-
if (args.length === 2) {
|
|
5517
|
-
// The totalLength form truncates or zero-pads (and THROWS Node's
|
|
5518
|
-
// 'length' RangeError on bad totals).
|
|
5519
|
-
const total = lowerer.lowerExprExpecting(args[1], F64);
|
|
5520
|
-
return { kind: "libCall", fn: "buffer.concatLen", args: [list, total], type: BYTES_U8, loc };
|
|
5521
|
-
}
|
|
5522
|
-
return { kind: "libCall", fn: "buffer.concat", args: [list], type: BYTES_U8, loc };
|
|
5523
|
-
}
|
|
5524
|
-
if (member === "compare") {
|
|
5525
|
-
// The static form: Buffer.compare(a, b) IS a.compare(b).
|
|
5526
|
-
if (args.length !== 2 || args.some(ts.isSpreadElement)) {
|
|
5527
|
-
lowerer.noLowering(`Buffer.compare with ${args.length} arguments`, call);
|
|
5528
|
-
}
|
|
5529
|
-
const sides = args.map((a) => lowerer.lowerExpr(a));
|
|
5530
|
-
if (sides.every((v) => v.type.kind === "bytes" && v.type.elem === "u8")) {
|
|
5531
|
-
return { kind: "bytesIntrinsic", method: "compareBuf", receiver: sides[0], args: [sides[1]], type: F64, loc };
|
|
5532
|
-
}
|
|
5533
|
-
// A side that is not statically bytes (the invalid-input probes,
|
|
5534
|
-
// untyped JS helpers): Node's "buf1"/"buf2" argument ladder runs
|
|
5535
|
-
// at runtime — a well-typed dyn still compares.
|
|
5536
|
-
const dyns = sides.map((v, i) => {
|
|
5537
|
-
if (v.type.kind === "dyn")
|
|
5538
|
-
return v;
|
|
5539
|
-
if (v.kind === "unitLit" || lowerer.dynConvertible(v.type)) {
|
|
5540
|
-
return { kind: "dynFrom", value: v, type: DYN, loc: v.loc };
|
|
5541
|
-
}
|
|
5542
|
-
lowerer.noLowering(`Buffer.compare of '${lowerer.fmt(v.type)}' values`, args[i], "Buffer/Uint8Array values compare (narrow unions first)");
|
|
5543
|
-
});
|
|
5544
|
-
return { kind: "libCall", fn: "buffer.compareChk", args: dyns, type: F64, loc };
|
|
5545
|
-
}
|
|
5546
|
-
if (member === "isBuffer") {
|
|
5547
|
-
// The type-predicate narrowing test. Lowered where it DECIDES
|
|
5548
|
-
// something: a union-typed argument with a Buffer arm becomes a
|
|
5549
|
-
// runtime tag test (tsc's narrowing then types the branches, the
|
|
5550
|
-
// discriminated-union machinery). Statically-decided arguments are
|
|
5551
|
-
// fenced — the answer is a constant, and the operand's evaluation
|
|
5552
|
-
// would have to be discarded.
|
|
5553
|
-
if (args.length === 1 && !ts.isSpreadElement(args[0])) {
|
|
5554
|
-
const v = lowerer.lowerExpr(args[0]);
|
|
5555
|
-
if (v.type.kind === "union") {
|
|
5556
|
-
const def = lowerer.unions.get(v.type.unionId);
|
|
5557
|
-
const tag = def ? def.arms.findIndex((a) => a.kind === "bytes" && a.elem === "u8") : -1;
|
|
5558
|
-
if (tag >= 0) {
|
|
5559
|
-
return { kind: "unionIsTag", unionId: v.type.unionId, tag, negated: false, value: v, type: BOOL, loc };
|
|
5560
|
-
}
|
|
5561
|
-
}
|
|
5562
|
-
lowerer.noLowering(`Buffer.isBuffer of '${lowerer.fmt(v.type)}' values`, args[0], "the check lowers as a runtime tag test on unions with a Buffer arm — here the answer is static");
|
|
5563
|
-
}
|
|
5564
|
-
lowerer.noLowering("Buffer.isBuffer with this argument shape", call);
|
|
5565
|
-
}
|
|
5566
|
-
if (member === "byteLength") {
|
|
5567
|
-
// Buffer.byteLength(string, enc?) — the UTF-16-aware per-encoding
|
|
5568
|
-
// count — or of a typed array/Buffer (its byte length, a property
|
|
5569
|
-
// read at heart).
|
|
5570
|
-
if (args.length >= 1 && args.length <= 2 && !ts.isSpreadElement(args[0])) {
|
|
5571
|
-
const srcIr = lowerer.mapTypeOf(lowerer.typeOf(args[0]));
|
|
5572
|
-
if (srcIr?.kind === "string") {
|
|
5573
|
-
const encName = args[1] ? bufEncoding(lowerer, "Buffer.byteLength", args[1]) : "utf8";
|
|
5574
|
-
const s = lowerer.lowerExprExpecting(args[0], STRING);
|
|
5575
|
-
const enc = { kind: "strLit", value: encName, type: STRING, loc };
|
|
5576
|
-
return { kind: "libCall", fn: "buffer.byteLenStr", args: [s, enc], type: F64, loc };
|
|
5577
|
-
}
|
|
5578
|
-
if (srcIr?.kind === "bytes" && args.length === 1) {
|
|
5579
|
-
const receiver = lowerer.lowerExpr(args[0]);
|
|
5580
|
-
return { kind: "bytesIntrinsic", method: "byteLength", receiver, args: [], type: F64, loc };
|
|
5581
|
-
}
|
|
5582
|
-
}
|
|
5583
|
-
lowerer.noLowering("Buffer.byteLength with this argument shape", call, "supported: Buffer.byteLength(string, literalEncoding?) or Buffer.byteLength(typedArray)");
|
|
5584
|
-
}
|
|
5585
|
-
if (member === "isEncoding") {
|
|
5586
|
-
// The runtime alias-set test — a plain bool over any string value
|
|
5587
|
-
// (Node's case-insensitive normalizeEncoding check).
|
|
5588
|
-
if (args.length === 1 && !ts.isSpreadElement(args[0]) && lowerer.mapTypeOf(lowerer.typeOf(args[0]))?.kind === "string") {
|
|
5589
|
-
const s = lowerer.lowerExprExpecting(args[0], STRING);
|
|
5590
|
-
return { kind: "libCall", fn: "buffer.isEncoding", args: [s], type: BOOL, loc };
|
|
5591
|
-
}
|
|
5592
|
-
lowerer.noLowering("Buffer.isEncoding with this argument shape", call, "one string-typed argument is the lowered form");
|
|
5593
|
-
}
|
|
5594
|
-
return null; // of, copyBytesFrom, ... → the SC2020 member fence
|
|
5595
|
-
}
|
|
5596
4337
|
/** `Object.keys/values/entries` over an INDEX-SIGNATURE (overflow-carrying)
|
|
5597
4338
|
* record shape: declared fields answer first from the compile-time field
|
|
5598
4339
|
* list (declaration order, undefined-valued fields skipped — exactly the
|