@scriptc/compiler 0.1.1 → 0.1.3
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 +104 -31
- package/ambient/scriptc.d.ts +2 -1
- package/dist/backend/build-cache.js.map +1 -1
- package/dist/backend/c/async.d.ts +5 -0
- package/dist/backend/c/async.js +81 -2
- package/dist/backend/c/async.js.map +1 -1
- package/dist/backend/c/c-emitter.d.ts +25 -10
- package/dist/backend/c/c-emitter.js +74 -22
- package/dist/backend/c/c-emitter.js.map +1 -1
- package/dist/backend/c/exprs.d.ts +8 -1
- package/dist/backend/c/exprs.js +371 -18
- package/dist/backend/c/exprs.js.map +1 -1
- package/dist/backend/c/island.js.map +1 -1
- package/dist/backend/c/may-throw.js.map +1 -1
- package/dist/backend/c/shapes.js +15 -13
- package/dist/backend/c/shapes.js.map +1 -1
- package/dist/backend/c/stmts.d.ts +3 -5
- package/dist/backend/c/stmts.js +58 -17
- package/dist/backend/c/stmts.js.map +1 -1
- package/dist/backend/c/types.d.ts +4 -0
- package/dist/backend/c/types.js +18 -1
- package/dist/backend/c/types.js.map +1 -1
- package/dist/backend/c/types.test.js +8 -1
- package/dist/backend/c/types.test.js.map +1 -1
- package/dist/backend/c/walkers.js +63 -16
- package/dist/backend/c/walkers.js.map +1 -1
- package/dist/backend/external-c.js.map +1 -1
- package/dist/backend/json-literal.d.ts +7 -0
- package/dist/backend/json-literal.js +17 -0
- package/dist/backend/json-literal.js.map +1 -0
- package/dist/backend/json-literal.test.d.ts +1 -0
- package/dist/backend/json-literal.test.js +17 -0
- package/dist/backend/json-literal.test.js.map +1 -0
- package/dist/backend/link-plan.d.ts +1 -1
- package/dist/backend/link-plan.js +5 -1
- package/dist/backend/link-plan.js.map +1 -1
- package/dist/backend/linker.js.map +1 -1
- package/dist/backend/llvm/blocks.js.map +1 -1
- package/dist/backend/llvm/classes.js +8 -7
- package/dist/backend/llvm/classes.js.map +1 -1
- package/dist/backend/llvm/common.d.ts +4 -0
- package/dist/backend/llvm/common.js +6 -0
- package/dist/backend/llvm/common.js.map +1 -1
- package/dist/backend/llvm/common.test.d.ts +1 -0
- package/dist/backend/llvm/common.test.js +9 -0
- package/dist/backend/llvm/common.test.js.map +1 -0
- package/dist/backend/llvm/dyn.d.ts +4 -0
- package/dist/backend/llvm/dyn.js +80 -16
- package/dist/backend/llvm/dyn.js.map +1 -1
- package/dist/backend/llvm/emitter.js +132 -25
- package/dist/backend/llvm/emitter.js.map +1 -1
- package/dist/backend/llvm/expr-async.js +85 -3
- package/dist/backend/llvm/expr-async.js.map +1 -1
- package/dist/backend/llvm/expr-bytes.js.map +1 -1
- package/dist/backend/llvm/expr-calls.js.map +1 -1
- package/dist/backend/llvm/expr-containers.js.map +1 -1
- package/dist/backend/llvm/expr-context.d.ts +5 -0
- package/dist/backend/llvm/expr-control.js.map +1 -1
- package/dist/backend/llvm/expr-dispatch.js.map +1 -1
- package/dist/backend/llvm/expr-dynamic.js +15 -3
- package/dist/backend/llvm/expr-dynamic.js.map +1 -1
- package/dist/backend/llvm/expr-island.js.map +1 -1
- package/dist/backend/llvm/expr-primitives.js +8 -0
- package/dist/backend/llvm/expr-primitives.js.map +1 -1
- package/dist/backend/llvm/expr-records.js +3 -3
- package/dist/backend/llvm/expr-records.js.map +1 -1
- package/dist/backend/llvm/expr-stream-bridges.js +222 -10
- package/dist/backend/llvm/expr-stream-bridges.js.map +1 -1
- package/dist/backend/llvm/expr-stream-callbacks.d.ts +3 -0
- package/dist/backend/llvm/expr-stream-callbacks.js +53 -0
- package/dist/backend/llvm/expr-stream-callbacks.js.map +1 -1
- package/dist/backend/llvm/lib-dispatch.js +24 -0
- package/dist/backend/llvm/lib-dispatch.js.map +1 -1
- package/dist/backend/llvm/lib-process.js +50 -5
- package/dist/backend/llvm/lib-process.js.map +1 -1
- package/dist/backend/llvm/lib-shared.js +64 -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/shapes.js +10 -8
- package/dist/backend/llvm/shapes.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 +7 -5
- package/dist/backend/llvm/walkers.js.map +1 -1
- package/dist/backend/native-codegen.js.map +1 -1
- package/dist/backend/native-codegen.test.js +2 -0
- package/dist/backend/native-codegen.test.js.map +1 -1
- package/dist/backend/native-link-info.d.ts +2 -1
- package/dist/backend/native-link-info.js +4 -0
- package/dist/backend/native-link-info.js.map +1 -1
- package/dist/backend/native-link-info.test.js +24 -1
- package/dist/backend/native-link-info.test.js.map +1 -1
- package/dist/backend/native-toolchain.d.ts +8 -6
- package/dist/backend/native-toolchain.js +28 -13
- package/dist/backend/native-toolchain.js.map +1 -1
- package/dist/backend/native-toolchain.test.js +36 -1
- package/dist/backend/native-toolchain.test.js.map +1 -1
- package/dist/backend/runtime-pack-targets.test.js +1 -1
- package/dist/backend/runtime-pack-targets.test.js.map +1 -1
- package/dist/backend/runtime-pack.js.map +1 -1
- package/dist/backend/runtime-pack.test.js +1 -0
- package/dist/backend/runtime-pack.test.js.map +1 -1
- package/dist/backend/targets.d.ts +5 -0
- package/dist/backend/targets.js +9 -0
- package/dist/backend/targets.js.map +1 -1
- package/dist/backend/targets.test.js +6 -1
- 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 +13 -0
- package/dist/coverage/surface-manifest.js.map +1 -1
- package/dist/diagnostics/diagnostic.d.ts +6 -5
- package/dist/diagnostics/diagnostic.js +10 -9
- package/dist/diagnostics/diagnostic.js.map +1 -1
- package/dist/diagnostics/render.js.map +1 -1
- package/dist/executable/executable-cache.d.ts +1 -0
- package/dist/executable/executable-cache.js +1 -0
- package/dist/executable/executable-cache.js.map +1 -1
- package/dist/executable/executable-cache.test.js +1 -0
- package/dist/executable/executable-cache.test.js.map +1 -1
- package/dist/ffi/ffi-manifest.js.map +1 -1
- package/dist/frontend/cjs-lexer.js.map +1 -1
- package/dist/frontend/dts-paths.js.map +1 -1
- package/dist/frontend/input-tracker.d.ts +10 -0
- package/dist/frontend/input-tracker.js +26 -3
- package/dist/frontend/input-tracker.js.map +1 -1
- package/dist/frontend/input-tracker.test.js +8 -1
- package/dist/frontend/input-tracker.test.js.map +1 -1
- package/dist/frontend/lib-contract.js.map +1 -1
- package/dist/frontend/lowering/array-values.d.ts +4 -0
- package/dist/frontend/lowering/array-values.js +18 -2
- package/dist/frontend/lowering/array-values.js.map +1 -1
- package/dist/frontend/lowering/callback-arg.js.map +1 -1
- package/dist/frontend/lowering/containers/bytes.d.ts +31 -0
- package/dist/frontend/lowering/containers/bytes.js +1105 -0
- package/dist/frontend/lowering/containers/bytes.js.map +1 -0
- package/dist/frontend/lowering/containers/string-and-regexp.d.ts +17 -0
- package/dist/frontend/lowering/containers/string-and-regexp.js +345 -0
- package/dist/frontend/lowering/containers/string-and-regexp.js.map +1 -0
- package/dist/frontend/lowering/expressions/evaluation-safety.d.ts +13 -0
- package/dist/frontend/lowering/expressions/evaluation-safety.js +76 -0
- package/dist/frontend/lowering/expressions/evaluation-safety.js.map +1 -0
- package/dist/frontend/lowering/expressions/object-literals.d.ts +44 -0
- package/dist/frontend/lowering/expressions/object-literals.js +1815 -0
- package/dist/frontend/lowering/expressions/object-literals.js.map +1 -0
- package/dist/frontend/lowering/expressions/optional-chains.d.ts +31 -0
- package/dist/frontend/lowering/expressions/optional-chains.js +382 -0
- package/dist/frontend/lowering/expressions/optional-chains.js.map +1 -0
- package/dist/frontend/lowering/expressions/try-lower-expression.d.ts +7 -0
- package/dist/frontend/lowering/expressions/try-lower-expression.js +26 -0
- package/dist/frontend/lowering/expressions/try-lower-expression.js.map +1 -0
- package/dist/frontend/lowering/lib-boundary.js +7 -2
- package/dist/frontend/lowering/lib-boundary.js.map +1 -1
- package/dist/frontend/lowering/lower-assert.js +16 -10
- 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 +40 -21
- package/dist/frontend/lowering/lower-builtins.js +611 -115
- package/dist/frontend/lowering/lower-builtins.js.map +1 -1
- package/dist/frontend/lowering/lower-calls.d.ts +21 -10
- package/dist/frontend/lowering/lower-calls.js +516 -75
- 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 +147 -1475
- package/dist/frontend/lowering/lower-containers.js.map +1 -1
- package/dist/frontend/lowering/lower-event-emitter.js +51 -5
- package/dist/frontend/lowering/lower-event-emitter.js.map +1 -1
- package/dist/frontend/lowering/lower-exprs.d.ts +0 -78
- package/dist/frontend/lowering/lower-exprs.js +503 -2353
- 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 +10 -3
- package/dist/frontend/lowering/lower-inspect.js.map +1 -1
- package/dist/frontend/lowering/lower-island.d.ts +6 -0
- package/dist/frontend/lowering/lower-island.js +37 -5
- package/dist/frontend/lowering/lower-island.js.map +1 -1
- package/dist/frontend/lowering/lower-modules.js +60 -15
- package/dist/frontend/lowering/lower-modules.js.map +1 -1
- package/dist/frontend/lowering/lower-node-module.d.ts +18 -0
- package/dist/frontend/lowering/lower-node-module.js +302 -0
- package/dist/frontend/lowering/lower-node-module.js.map +1 -0
- package/dist/frontend/lowering/lower-server.js +1 -1
- package/dist/frontend/lowering/lower-server.js.map +1 -1
- package/dist/frontend/lowering/lower-stmts.d.ts +10 -28
- package/dist/frontend/lowering/lower-stmts.js +542 -259
- 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 +41 -38
- package/dist/frontend/lowering/lowerer.js +148 -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 +5 -3
- package/dist/frontend/lowering/surfaces.js +33 -13
- 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 +519 -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 +132 -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 +513 -70
- package/dist/frontend/program.js.map +1 -1
- package/dist/frontend/resolve.d.ts +26 -3
- package/dist/frontend/resolve.js +166 -29
- package/dist/frontend/resolve.js.map +1 -1
- package/dist/frontend/resolve.test.js +24 -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 +171 -16
- package/dist/frontend/type-mapper.js.map +1 -1
- package/dist/index.js +11 -5
- package/dist/index.js.map +1 -1
- package/dist/ir/analysis.js +1 -0
- package/dist/ir/analysis.js.map +1 -1
- package/dist/ir/ir.d.ts +125 -52
- package/dist/ir/ir.js +136 -7
- 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 +120 -63
- package/dist/ir/validate.js.map +1 -1
- package/dist/library/cache-primitives.js.map +1 -1
- package/dist/library/cache-reset.test.js.map +1 -1
- package/dist/library/compiler-self-identity.js.map +1 -1
- package/dist/library/int-infer.js.map +1 -1
- package/dist/library/int-infer.test.js +4 -4
- package/dist/library/int-infer.test.js.map +1 -1
- package/dist/library/library-cache.d.ts +1 -0
- package/dist/library/library-cache.js +1 -0
- package/dist/library/library-cache.js.map +1 -1
- package/dist/library/library-cache.test.js +18 -5
- package/dist/library/library-cache.test.js.map +1 -1
- package/dist/library/library-profile.js.map +1 -1
- package/dist/library/sidecar-validate.js.map +1 -1
- package/dist/library/sidecar.js.map +1 -1
- package/dist/startup-cache.js.map +1 -1
- package/dist/wasi-paths.js.map +1 -1
- package/package.json +18 -18
- package/surface-manifest.json +91 -20
|
@@ -1,96 +1,21 @@
|
|
|
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";
|
|
13
|
-
import { buildArraySortFn
|
|
14
|
-
import { arrayValueRead, arrayValueStore, arrayValueType } from "./array-values.js";
|
|
14
|
+
import { buildArraySortFn } from "./lower-array-sort.js";
|
|
15
|
+
import { arrayIndexPresent, arrayValueRead, arrayValueStore, arrayValueType, currentArrayIndexPresent } from "./array-values.js";
|
|
15
16
|
import { typeKey } from "../type-mapper.js";
|
|
16
|
-
import { dynUndefinedExpr, own } from "./lowerer.js";
|
|
17
17
|
import { boolLit, countedFor, numLit, varRef } from "../../ir/build.js";
|
|
18
|
-
|
|
19
|
-
* Optional builtin arguments use this before their ordinary expected-type
|
|
20
|
-
* coercion so every equivalent spelling (`undefined`, `void 0`, a typed
|
|
21
|
-
* binding, or an effectful call returning undefined) selects the default.
|
|
22
|
-
* An effectful `void e` is exact in this context: evaluate e, then produce
|
|
23
|
-
* undefined, instead of hitting value-position void's general fence. */
|
|
24
|
-
function lowerStaticallyUndefinedArg(lowerer, node) {
|
|
25
|
-
const peelErasableWrappers = (value) => {
|
|
26
|
-
let expr = value;
|
|
27
|
-
while (ts.isParenthesizedExpression(expr) ||
|
|
28
|
-
ts.isAsExpression(expr) ||
|
|
29
|
-
ts.isTypeAssertion(expr) ||
|
|
30
|
-
ts.isSatisfiesExpression(expr)) {
|
|
31
|
-
expr = expr.expression;
|
|
32
|
-
}
|
|
33
|
-
return expr;
|
|
34
|
-
};
|
|
35
|
-
let expr = node;
|
|
36
|
-
while (ts.isParenthesizedExpression(expr))
|
|
37
|
-
expr = expr.expression;
|
|
38
|
-
if ((lowerer.typeOf(expr).flags &
|
|
39
|
-
(ts.TypeFlags.Undefined | ts.TypeFlags.Void)) ===
|
|
40
|
-
0) {
|
|
41
|
-
return null;
|
|
42
|
-
}
|
|
43
|
-
// TypeScript erases `as`, angle-bracket assertions, and `satisfies`.
|
|
44
|
-
// Peel them only for the void recognition: a non-void assertion still
|
|
45
|
-
// lowers through the ordinary path below, preserving the checked-dynamic
|
|
46
|
-
// cast discipline. Nested voids add no effects of their own, so the one
|
|
47
|
-
// innermost operand carries the complete runtime evaluation.
|
|
48
|
-
expr = peelErasableWrappers(expr);
|
|
49
|
-
let sawVoid = false;
|
|
50
|
-
while (ts.isVoidExpression(expr)) {
|
|
51
|
-
sawVoid = true;
|
|
52
|
-
expr = peelErasableWrappers(expr.expression);
|
|
53
|
-
}
|
|
54
|
-
if (sawVoid) {
|
|
55
|
-
// The caller discards this token before producing the parameter
|
|
56
|
-
// default, so the operand itself carries exactly the required effects;
|
|
57
|
-
// no bare undefined value needs to enter the IR.
|
|
58
|
-
return lowerer.lowerExpr(expr);
|
|
59
|
-
}
|
|
60
|
-
return lowerer.lowerExpr(node);
|
|
61
|
-
}
|
|
62
|
-
/** Preserve a statically-undefined argument's effects, then answer the
|
|
63
|
-
* optional parameter's already-lowered default value. */
|
|
64
|
-
function defaultAfterUndefined(value, defaultValue) {
|
|
65
|
-
if (droppableStatic(value))
|
|
66
|
-
return defaultValue;
|
|
67
|
-
return {
|
|
68
|
-
kind: "seqExpr",
|
|
69
|
-
stmts: [{ kind: "exprStmt", expr: value, loc: value.loc }],
|
|
70
|
-
result: defaultValue,
|
|
71
|
-
type: defaultValue.type,
|
|
72
|
-
loc: value.loc,
|
|
73
|
-
};
|
|
74
|
-
}
|
|
75
|
-
function lowerOptionalDefaultArg(lowerer, node, expected, defaultValue) {
|
|
76
|
-
const undefinedArg = lowerStaticallyUndefinedArg(lowerer, node);
|
|
77
|
-
if (undefinedArg)
|
|
78
|
-
return defaultAfterUndefined(undefinedArg, defaultValue);
|
|
79
|
-
const value = lowerer.lowerExpr(node);
|
|
80
|
-
if (value.type.kind === "union") {
|
|
81
|
-
const def = lowerer.unions.get(value.type.unionId);
|
|
82
|
-
if (def?.arms.length === 2 &&
|
|
83
|
-
def.arms.some((arm) => arm.kind === "undefinedT") &&
|
|
84
|
-
def.arms.some((arm) => typeEquals(arm, expected))) {
|
|
85
|
-
// A runtime optional argument uses its API default only on the
|
|
86
|
-
// undefined arm. The exact two-arm gate deliberately excludes null:
|
|
87
|
-
// optional parameters do not admit it, and an asserted null must keep
|
|
88
|
-
// the ordinary checked-narrow failure instead of defaulting.
|
|
89
|
-
return { kind: "nullish", left: value, right: defaultValue, type: expected, loc: value.loc };
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
return lowerer.coerceInto(node, value, expected);
|
|
93
|
-
}
|
|
18
|
+
import { lowerOptionalArgument } from "./optional-arguments.js";
|
|
94
19
|
/** Build the argument array for a mutating/copying operation while keeping
|
|
95
20
|
* the receiver's scalar payload ABI. A missing read is represented as the
|
|
96
21
|
* present UNDEFINED state in this temporary, so push/unshift and toSpliced
|
|
@@ -117,23 +42,6 @@ function lowerArrayValueItems(lowerer, values, elem, arrType, loc) {
|
|
|
117
42
|
}
|
|
118
43
|
return { kind: "seqExpr", stmts: body, result: outRef, type: arrType, loc };
|
|
119
44
|
}
|
|
120
|
-
function lowerSplitLimitArg(lowerer, node, loc) {
|
|
121
|
-
const defaultValue = { kind: "numLit", value: 4294967295, type: F64, loc };
|
|
122
|
-
return node ? lowerOptionalDefaultArg(lowerer, node, F64, defaultValue) : defaultValue;
|
|
123
|
-
}
|
|
124
|
-
/** Extract the checker-proven receiver arm from a runtime-optional array
|
|
125
|
-
* read. The receiver is stabilized once before its tag is tested, and a
|
|
126
|
-
* missing value throws the same property-read TypeError Node throws before
|
|
127
|
-
* evaluating method arguments. */
|
|
128
|
-
function lowerMethodReceiver(lowerer, node, expected, member) {
|
|
129
|
-
const lowered = lowerer.lowerExpr(node);
|
|
130
|
-
const optional = lowerer.runtimeOptionalPropertyReceiver(node, lowered, expected, member);
|
|
131
|
-
if (optional)
|
|
132
|
-
return optional;
|
|
133
|
-
if (typeEquals(lowered.type, expected))
|
|
134
|
-
return lowered;
|
|
135
|
-
return lowerer.coerceInto(node, lowered, expected);
|
|
136
|
-
}
|
|
137
45
|
/** Callback-driven array producers bypass mapType's ordinary T[] mapping:
|
|
138
46
|
* the callback is lowered first, then the helper's result array is built
|
|
139
47
|
* directly from its IR return type. Fence element kinds ScrArr cannot hold
|
|
@@ -160,6 +68,9 @@ export function lowerArrayMethodCall(lowerer, call, access) {
|
|
|
160
68
|
if (!ARRAY_METHODS.has(name) && name !== "sort" && name !== "shift" && name !== "splice")
|
|
161
69
|
return null;
|
|
162
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));
|
|
163
74
|
// A checker-`any[]` receiver (the readonly-array Array.isArray quirk)
|
|
164
75
|
// whose VALUE lowers to a real static array (maybeNarrow's isArray
|
|
165
76
|
// bridge): ride the ordinary tables on the lowered element type — the
|
|
@@ -168,7 +79,10 @@ export function lowerArrayMethodCall(lowerer, call, access) {
|
|
|
168
79
|
// the isStdlibMember gate is skipped for probed receivers (nothing
|
|
169
80
|
// user-declared can shadow a method on a value the checker calls any).
|
|
170
81
|
let probedUntyped = false;
|
|
171
|
-
if (
|
|
82
|
+
if (implicitArrayReceiver)
|
|
83
|
+
probedUntyped = true;
|
|
84
|
+
if ((receiverIr === null || receiverIr.kind === "dyn" ||
|
|
85
|
+
(receiverIr.kind === "array" && receiverIr.elem.kind === "dyn")) &&
|
|
172
86
|
(lowerer.checkerAnyArray(access.expression) ||
|
|
173
87
|
// A checker-`any` CHAIN whose value lowers to a real static array
|
|
174
88
|
// (`context.stack.split('\n').slice(2)` — the dyn-receiver string
|
|
@@ -345,10 +259,10 @@ export function lowerArrayMethodCall(lowerer, call, access) {
|
|
|
345
259
|
const deleteCount = call.arguments.length === 0
|
|
346
260
|
? { kind: "numLit", value: 0, type: F64, loc }
|
|
347
261
|
: call.arguments[1]
|
|
348
|
-
?
|
|
262
|
+
? lowerOptionalArgument(lowerer, call.arguments[1], F64, deleteCountDefault)
|
|
349
263
|
: { kind: "numLit", value: Infinity, type: F64, loc };
|
|
350
264
|
const itemNodes = call.arguments.slice(2);
|
|
351
|
-
const itemProbes = itemNodes.map((arg) =>
|
|
265
|
+
const itemProbes = itemNodes.map((arg) => tryLowerExpression(lowerer, arg));
|
|
352
266
|
const statefulItems = itemProbes.some((probe) => probe !== null && lowerer.runtimeOptionalWidening(probe.type, elem) !== null);
|
|
353
267
|
const items = statefulItems
|
|
354
268
|
? lowerArrayValueItems(lowerer, itemNodes.map((arg) => lowerer.lowerExpr(arg)), elem, receiverIr, loc)
|
|
@@ -431,7 +345,7 @@ export function lowerArrayMethodCall(lowerer, call, access) {
|
|
|
431
345
|
loc,
|
|
432
346
|
};
|
|
433
347
|
}
|
|
434
|
-
const valueProbes = call.arguments.map((arg) =>
|
|
348
|
+
const valueProbes = call.arguments.map((arg) => tryLowerExpression(lowerer, arg));
|
|
435
349
|
if (valueProbes.some((probe) => probe !== null && lowerer.runtimeOptionalWidening(probe.type, elem) !== null)) {
|
|
436
350
|
const items = lowerArrayValueItems(lowerer, call.arguments.map((arg) => lowerer.lowerExpr(arg)), elem, receiverIr, loc);
|
|
437
351
|
return {
|
|
@@ -494,8 +408,12 @@ export function lowerArrayMethodCall(lowerer, call, access) {
|
|
|
494
408
|
lowerer.unsupported("SC1090", argNode, "spread arguments to concat (pass the array itself — concat already spreads array arguments)");
|
|
495
409
|
}
|
|
496
410
|
const argIr = lowerer.mapTypeOf(lowerer.typeOf(argNode));
|
|
497
|
-
|
|
498
|
-
|
|
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)) {
|
|
499
417
|
// number[][].concat(inner: number[]) — TS says element, JS's
|
|
500
418
|
// IsArray says spread; no honest static answer exists.
|
|
501
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]))");
|
|
@@ -596,7 +514,7 @@ export function lowerArrayMethodCall(lowerer, call, access) {
|
|
|
596
514
|
}
|
|
597
515
|
if (name === "find" || name === "findIndex" || name === "findLast" ||
|
|
598
516
|
name === "findLastIndex" || name === "some" || name === "every") {
|
|
599
|
-
return lowerArrayFindLikeCall(lowerer, call, access, name, elem);
|
|
517
|
+
return lowerArrayFindLikeCall(lowerer, call, access, name, elem, probedUntyped);
|
|
600
518
|
}
|
|
601
519
|
if (name === "at")
|
|
602
520
|
return lowerArrayAtCall(lowerer, call, access, elem);
|
|
@@ -623,7 +541,7 @@ function lowerArraySearchCall(lowerer, call, access, method, elem) {
|
|
|
623
541
|
}
|
|
624
542
|
// Preserve the existing fast path for an ordinary, non-union needle. Its
|
|
625
543
|
// runtime implementation already has exact primitive/reference equality.
|
|
626
|
-
if (elem.kind !== "union" && typeEquals(needle.type, elem)) {
|
|
544
|
+
if (elem.kind !== "union" && elem.kind !== "bigint" && typeEquals(needle.type, elem)) {
|
|
627
545
|
return {
|
|
628
546
|
kind: "arrIntrinsic",
|
|
629
547
|
method,
|
|
@@ -823,7 +741,8 @@ function arraySearchHelper(lowerer, method, elem, valueT, loc) {
|
|
|
823
741
|
* loop passes exactly what it declares (JS passes everything; a callback
|
|
824
742
|
* only sees the parameters it names). Returns the lowered callback and
|
|
825
743
|
* its declared arity. */
|
|
826
|
-
function hofCallbackArg(lowerer, argNode, lead, arrT) {
|
|
744
|
+
function hofCallbackArg(lowerer, argNode, lead, arrT, bindUntyped = false) {
|
|
745
|
+
const full = [...lead, F64, arrT];
|
|
827
746
|
// A DYN-receiver HOF's callback (`parsed.flatMap((value) => ...)`):
|
|
828
747
|
// the contextual signature types the unannotated param `any` (the
|
|
829
748
|
// receiver is checker-`any[]`), while the VALUE each call receives is
|
|
@@ -857,6 +776,43 @@ function hofCallbackArg(lowerer, argNode, lead, arrT) {
|
|
|
857
776
|
lowerer.jsvalParamOverrides.add(p);
|
|
858
777
|
});
|
|
859
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;
|
|
860
816
|
let fnArg;
|
|
861
817
|
try {
|
|
862
818
|
fnArg = lowerer.lowerExpr(argNode);
|
|
@@ -864,8 +820,14 @@ function hofCallbackArg(lowerer, argNode, lead, arrT) {
|
|
|
864
820
|
finally {
|
|
865
821
|
for (const n of overridden)
|
|
866
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;
|
|
867
830
|
}
|
|
868
|
-
const full = [...lead, F64, arrT];
|
|
869
831
|
if (fnArg.type.kind !== "func" ||
|
|
870
832
|
fnArg.type.params.length > full.length ||
|
|
871
833
|
!fnArg.type.params.every((p, i) => typeEquals(p, full[i]))) {
|
|
@@ -1024,8 +986,9 @@ function truthyFilterCallback(lowerer, valueT, loc) {
|
|
|
1024
986
|
}
|
|
1025
987
|
/** The predicate result kinds `.filter()` accepts. JS applies ToBoolean to
|
|
1026
988
|
* whatever the callback answers, so a bool is not required: the scalars and
|
|
1027
|
-
* the reference kinds have constant or by-value answers,
|
|
1028
|
-
*
|
|
989
|
+
* the reference kinds have constant or by-value answers, checked-dynamic
|
|
990
|
+
* values ask their runtime kind, and a union is fine when every arm does.
|
|
991
|
+
* void/jsval/caught stay out — see below. */
|
|
1029
992
|
function filterPredicateOk(lowerer, ret) {
|
|
1030
993
|
if (ret.kind === "bool")
|
|
1031
994
|
return true;
|
|
@@ -1035,10 +998,13 @@ function filterPredicateOk(lowerer, ret) {
|
|
|
1035
998
|
// compiled ABI has already discarded it. Treating void as constantly
|
|
1036
999
|
// falsy would silently answer [] where Node answers [1]. Fenced until
|
|
1037
1000
|
// the returned value can be preserved through the void ABI.
|
|
1038
|
-
//
|
|
1001
|
+
// A checked-dynamic value retains its runtime kind, so scr_dyn_truthy can
|
|
1002
|
+
// apply JavaScript ToBoolean exactly. Island and caught values stay out.
|
|
1039
1003
|
if (ret.kind === "void")
|
|
1040
1004
|
return false;
|
|
1041
|
-
if (ret.kind === "dyn"
|
|
1005
|
+
if (ret.kind === "dyn")
|
|
1006
|
+
return true;
|
|
1007
|
+
if (ret.kind === "jsval" || ret.kind === "caught")
|
|
1042
1008
|
return false;
|
|
1043
1009
|
if (ret.kind === "union") {
|
|
1044
1010
|
const def = lowerer.unions.get(ret.unionId);
|
|
@@ -1058,9 +1024,9 @@ function filterCond(call, fnRet, loc) {
|
|
|
1058
1024
|
// `void` returns to void and a standalone `null` return to the unit-ONLY
|
|
1059
1025
|
// UNION, which routes through toBool below; ir/validate.ts rejects a
|
|
1060
1026
|
// bare unit return type outright). filterPredicateOk already rejected
|
|
1061
|
-
//
|
|
1062
|
-
//
|
|
1063
|
-
//
|
|
1027
|
+
// unsafe union arms (dyn/caught) at the call site, where a real node
|
|
1028
|
+
// exists for the diagnostic. A bare dyn value is handled directly by
|
|
1029
|
+
// the runtime's kind-aware ToBoolean.
|
|
1064
1030
|
return { kind: "toBool", operand: call, type: BOOL, loc };
|
|
1065
1031
|
}
|
|
1066
1032
|
/** Interned synthetic loop function for one (method, elem, fnRet, arity)
|
|
@@ -1113,7 +1079,7 @@ export function lowerTupleReadMethodCall(lowerer, call, access) {
|
|
|
1113
1079
|
receiver ??= lowerer.lowerExpr(access.expression);
|
|
1114
1080
|
if (receiver.type.kind !== "record")
|
|
1115
1081
|
return null;
|
|
1116
|
-
if (!
|
|
1082
|
+
if (!isSafeToRepeat(receiver)) {
|
|
1117
1083
|
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");
|
|
1118
1084
|
}
|
|
1119
1085
|
const loc = locOf(call);
|
|
@@ -1400,14 +1366,14 @@ function buildArrayHofFn(lowerer, name, method, elem, fnRet, arity, loc, outElem
|
|
|
1400
1366
|
* backwards (`i = n - 1; i >= 0; i--`), exactly the es2023 spec's
|
|
1401
1367
|
* descending index walk. All require a bool-returning callback
|
|
1402
1368
|
* (JS's ToBoolean of arbitrary predicate results has no lowering). */
|
|
1403
|
-
function lowerArrayFindLikeCall(lowerer, call, access, method, elem) {
|
|
1369
|
+
function lowerArrayFindLikeCall(lowerer, call, access, method, elem, bindUntyped = false) {
|
|
1404
1370
|
const loc = locOf(call);
|
|
1405
1371
|
const receiver = lowerer.lowerExpr(access.expression);
|
|
1406
1372
|
const arrT = arrayOf(elem);
|
|
1407
1373
|
const argNode = call.arguments[0];
|
|
1408
1374
|
if (!argNode)
|
|
1409
1375
|
lowerer.unsupported("SC1090", call, "this call form"); // tsc-guarded
|
|
1410
|
-
const { fnArg, arity } = hofCallbackArg(lowerer, argNode, [arrayValueType(lowerer, elem)], arrT);
|
|
1376
|
+
const { fnArg, arity } = hofCallbackArg(lowerer, argNode, [arrayValueType(lowerer, elem)], arrT, bindUntyped);
|
|
1411
1377
|
// JS takes the ToBoolean of the predicate's result — allowed wherever
|
|
1412
1378
|
// that ToBoolean has a static answer (bool passes through; f64/string
|
|
1413
1379
|
// by value; a truthy-answerable union by its arm — the
|
|
@@ -1437,7 +1403,7 @@ function lowerArrayFindLikeCall(lowerer, call, access, method, elem) {
|
|
|
1437
1403
|
// found element passes through untouched; otherwise it wraps into its
|
|
1438
1404
|
// arm. A union element whose result union differs would need the
|
|
1439
1405
|
// union-into-union re-tag that doesn't exist — fenced.
|
|
1440
|
-
const resultT = lowerer.irTypeOf(call);
|
|
1406
|
+
const resultT = bindUntyped ? arrayValueType(lowerer, elem) : lowerer.irTypeOf(call);
|
|
1441
1407
|
if (resultT.kind !== "union")
|
|
1442
1408
|
lowerer.badType(call, lowerer.typeOf(call)); // defensive: T | undefined always maps to a union
|
|
1443
1409
|
const undefTag = lowerer.armTag(resultT.unionId, UNDEFINED_T);
|
|
@@ -1605,7 +1571,7 @@ function someEveryHelper(lowerer, method, elem, fnRet, arity, loc) {
|
|
|
1605
1571
|
countedFor(loc, varRef("n.0", F64, loc), () => [
|
|
1606
1572
|
{
|
|
1607
1573
|
kind: "if",
|
|
1608
|
-
cond:
|
|
1574
|
+
cond: currentArrayIndexPresent(arrT, loc),
|
|
1609
1575
|
then: [{
|
|
1610
1576
|
kind: "if",
|
|
1611
1577
|
cond: method === "some" ? callF : { kind: "unary", op: "!", operand: callF, type: BOOL, loc },
|
|
@@ -1703,7 +1669,7 @@ function flatMapHelper(lowerer, elem, fnRet, arity, loc) {
|
|
|
1703
1669
|
body: [
|
|
1704
1670
|
{
|
|
1705
1671
|
kind: "if",
|
|
1706
|
-
cond:
|
|
1672
|
+
cond: arrayIndexPresent(varRef("r.0", fnRet, loc), varRef("j.0", F64, loc), loc),
|
|
1707
1673
|
then: [
|
|
1708
1674
|
{ kind: "varDecl", localId: "inner.0", init: arrayValueRead(lowerer, varRef("r.0", fnRet, loc), varRef("j.0", F64, loc), inner, loc), loc },
|
|
1709
1675
|
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),
|
|
@@ -1720,7 +1686,7 @@ function flatMapHelper(lowerer, elem, fnRet, arity, loc) {
|
|
|
1720
1686
|
countedFor(loc, varRef("n.0", F64, loc), () => [
|
|
1721
1687
|
{
|
|
1722
1688
|
kind: "if",
|
|
1723
|
-
cond:
|
|
1689
|
+
cond: currentArrayIndexPresent(arrT, loc),
|
|
1724
1690
|
then: [
|
|
1725
1691
|
{
|
|
1726
1692
|
kind: "varDecl",
|
|
@@ -1843,7 +1809,7 @@ function reduceHelper(lowerer, method, elem, accT, fnRet, arity, hasInit, loc) {
|
|
|
1843
1809
|
...(hasInit
|
|
1844
1810
|
? [{ kind: "varDecl", localId: "acc.0", init: varRef("z.0", accT, loc), loc }]
|
|
1845
1811
|
: [
|
|
1846
|
-
{ kind: "while", cond: { kind: "ternary", cond: inRange, then: { kind: "unary", op: "!", operand:
|
|
1812
|
+
{ 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 },
|
|
1847
1813
|
{ kind: "if", cond: { kind: "unary", op: "!", operand: inRange, type: BOOL, loc }, then: [{ kind: "throw", value: { kind: "libCall", fn: "error.new", args: [{ kind: "strLit", value: "Reduce of empty array with no initial value", type: STRING, loc }], type: { kind: "object", className: "%TypeError" }, loc }, loc }], else_: null, loc },
|
|
1848
1814
|
{ kind: "varDecl", localId: "acc.0", init: lowerer.coerceToExpected(at(i), accT), loc },
|
|
1849
1815
|
advance,
|
|
@@ -1861,7 +1827,7 @@ function reduceHelper(lowerer, method, elem, accT, fnRet, arity, hasInit, loc) {
|
|
|
1861
1827
|
value: { kind: "bin", op: right ? "-" : "+", left: i, right: numLit(1, loc), type: F64, loc },
|
|
1862
1828
|
loc,
|
|
1863
1829
|
},
|
|
1864
|
-
body: [{ kind: "if", cond:
|
|
1830
|
+
body: [{ kind: "if", cond: currentArrayIndexPresent(arrT, loc), then: [
|
|
1865
1831
|
{
|
|
1866
1832
|
kind: "assign",
|
|
1867
1833
|
localId: "acc.0",
|
|
@@ -1997,83 +1963,6 @@ function defaultStringCmpHelper(lowerer, loc) {
|
|
|
1997
1963
|
});
|
|
1998
1964
|
return name;
|
|
1999
1965
|
}
|
|
2000
|
-
/** Uint8Array.prototype.toSorted. The receiver/comparator expressions are
|
|
2001
|
-
* evaluated before entering the helper; the helper snapshots with
|
|
2002
|
-
* TypedArray.prototype.slice before its first comparison, then performs
|
|
2003
|
-
* the same stable merge walk as Array.toSorted. Uint8Array's default
|
|
2004
|
-
* comparator is numeric ascending, so its comparator-less form needs no
|
|
2005
|
-
* string-conversion machinery. */
|
|
2006
|
-
function lowerBytesToSortedCall(lowerer, call, access, bytesT) {
|
|
2007
|
-
const loc = locOf(call);
|
|
2008
|
-
if (call.arguments.length > 1 || call.arguments.some(ts.isSpreadElement)) {
|
|
2009
|
-
lowerer.noLowering(`.toSorted with ${call.arguments.length} arguments on Uint8Array`, call);
|
|
2010
|
-
}
|
|
2011
|
-
const receiver = lowerer.lowerExpr(access.expression);
|
|
2012
|
-
const undefinedArg = call.arguments[0]
|
|
2013
|
-
? lowerStaticallyUndefinedArg(lowerer, call.arguments[0])
|
|
2014
|
-
: null;
|
|
2015
|
-
if (call.arguments.length === 0 || undefinedArg) {
|
|
2016
|
-
const key = "bytes.toSorted:u8:default";
|
|
2017
|
-
let helper = lowerer.arrHofHelpers.get(key);
|
|
2018
|
-
if (!helper) {
|
|
2019
|
-
helper = `%bytes.toSorted.${lowerer.arrHofHelpers.size}`;
|
|
2020
|
-
lowerer.arrHofHelpers.set(key, helper);
|
|
2021
|
-
lowerer.liftedFns.push(buildBytesSortFn(helper, 0, false, loc));
|
|
2022
|
-
}
|
|
2023
|
-
if (!undefinedArg || droppableStatic(undefinedArg)) {
|
|
2024
|
-
return { kind: "call", callee: helper, args: [receiver], type: bytesT, loc };
|
|
2025
|
-
}
|
|
2026
|
-
// The default helper takes no comparator argument. Snapshot the
|
|
2027
|
-
// receiver into a hidden local so the discarded undefined argument
|
|
2028
|
-
// still evaluates after the receiver and before the helper call.
|
|
2029
|
-
const saved = lowerer.declareHiddenLocal("%bytesSortRecv", bytesT);
|
|
2030
|
-
const savedRef = {
|
|
2031
|
-
kind: "varRef",
|
|
2032
|
-
localId: saved.id,
|
|
2033
|
-
type: bytesT,
|
|
2034
|
-
loc,
|
|
2035
|
-
};
|
|
2036
|
-
return {
|
|
2037
|
-
kind: "seqExpr",
|
|
2038
|
-
stmts: [
|
|
2039
|
-
{ kind: "varDecl", localId: saved.id, init: receiver, loc },
|
|
2040
|
-
{ kind: "exprStmt", expr: undefinedArg, loc: undefinedArg.loc },
|
|
2041
|
-
],
|
|
2042
|
-
result: {
|
|
2043
|
-
kind: "call",
|
|
2044
|
-
callee: helper,
|
|
2045
|
-
args: [savedRef],
|
|
2046
|
-
type: bytesT,
|
|
2047
|
-
loc,
|
|
2048
|
-
},
|
|
2049
|
-
type: bytesT,
|
|
2050
|
-
loc,
|
|
2051
|
-
};
|
|
2052
|
-
}
|
|
2053
|
-
const argNode = call.arguments[0];
|
|
2054
|
-
const fnArg = lowerer.lowerExpr(argNode);
|
|
2055
|
-
if (fnArg.type.kind !== "func" ||
|
|
2056
|
-
fnArg.type.params.length > 2 ||
|
|
2057
|
-
!fnArg.type.params.every((p) => p.kind === "f64") ||
|
|
2058
|
-
fnArg.type.ret.kind !== "f64") {
|
|
2059
|
-
lowerer.badType(argNode, lowerer.typeOf(argNode));
|
|
2060
|
-
}
|
|
2061
|
-
const arity = fnArg.type.params.length;
|
|
2062
|
-
const key = `bytes.toSorted:u8:${arity}`;
|
|
2063
|
-
let helper = lowerer.arrHofHelpers.get(key);
|
|
2064
|
-
if (!helper) {
|
|
2065
|
-
helper = `%bytes.toSorted.${lowerer.arrHofHelpers.size}`;
|
|
2066
|
-
lowerer.arrHofHelpers.set(key, helper);
|
|
2067
|
-
lowerer.liftedFns.push(buildBytesSortFn(helper, arity, true, loc));
|
|
2068
|
-
}
|
|
2069
|
-
return {
|
|
2070
|
-
kind: "call",
|
|
2071
|
-
callee: helper,
|
|
2072
|
-
args: [receiver, fnArg],
|
|
2073
|
-
type: bytesT,
|
|
2074
|
-
loc,
|
|
2075
|
-
};
|
|
2076
|
-
}
|
|
2077
1966
|
/** `n = a.length` — the once-up-front length read every array HOF loop
|
|
2078
1967
|
* starts with (locals a.0/n.0 by convention). */
|
|
2079
1968
|
function readLenStmt(arrT, loc) {
|
|
@@ -2457,7 +2346,15 @@ export function lowerMapMethodCall(lowerer, call, access) {
|
|
|
2457
2346
|
const name = access.name.text;
|
|
2458
2347
|
if (!MAP_METHODS.has(name) && !MAP_ITER_METHODS.has(name))
|
|
2459
2348
|
return null;
|
|
2460
|
-
|
|
2349
|
+
let receiverIr = lowerer.mapTypeOf(lowerer.typeOf(access.expression));
|
|
2350
|
+
let probedUntyped = false;
|
|
2351
|
+
if (receiverIr?.kind !== "map" && isJsSourceFile(access.getSourceFile())) {
|
|
2352
|
+
const probed = tryLowerExpression(lowerer, access.expression);
|
|
2353
|
+
if (probed?.type.kind === "map") {
|
|
2354
|
+
receiverIr = probed.type;
|
|
2355
|
+
probedUntyped = true;
|
|
2356
|
+
}
|
|
2357
|
+
}
|
|
2461
2358
|
if (receiverIr?.kind !== "map")
|
|
2462
2359
|
return null;
|
|
2463
2360
|
if (!lowerer.isStdlibMember(access))
|
|
@@ -2501,7 +2398,7 @@ export function lowerMapMethodCall(lowerer, call, access) {
|
|
|
2501
2398
|
return lowerMapIterDrainCall(lowerer, call, receiver, receiverIr, name);
|
|
2502
2399
|
}
|
|
2503
2400
|
// forEach
|
|
2504
|
-
return
|
|
2401
|
+
return lowerMapForEachCall(lowerer, call, receiver, receiverIr, probedUntyped);
|
|
2505
2402
|
}
|
|
2506
2403
|
/** The iterator methods the lowering DOES cover — in exactly one context. */
|
|
2507
2404
|
const MAP_ITER_METHODS = new Set(["keys", "values", "entries"]);
|
|
@@ -2621,12 +2518,52 @@ function buildMapIterDrainFn(name, mapT, method, elemT, tupleT, loc) {
|
|
|
2621
2518
|
* callback throws. That is JS's live-iteration contract, Node-verified
|
|
2622
2519
|
* (SEMANTICS.md). The callback receives (value, key) like JS; declaring
|
|
2623
2520
|
* fewer parameters — (value) or () — is ordinary TS and supported. */
|
|
2624
|
-
export function lowerMapForEachCall(lowerer, call, receiver, mapT) {
|
|
2521
|
+
export function lowerMapForEachCall(lowerer, call, receiver, mapT, bindUntyped = false) {
|
|
2625
2522
|
const loc = locOf(call);
|
|
2626
2523
|
const argNode = call.arguments[0];
|
|
2627
2524
|
if (!argNode)
|
|
2628
2525
|
lowerer.unsupported("SC1090", call, "this call form"); // tsc-guarded
|
|
2629
|
-
const
|
|
2526
|
+
const previousRuntime = [];
|
|
2527
|
+
const previousImplicit = lowerer.implicitParamTypes;
|
|
2528
|
+
let contextualTs = null;
|
|
2529
|
+
if (bindUntyped && (ts.isArrowFunction(argNode) || ts.isFunctionExpression(argNode))) {
|
|
2530
|
+
const expected = [mapT.value, mapT.key];
|
|
2531
|
+
argNode.parameters.slice(0, 2).forEach((param, i) => {
|
|
2532
|
+
const type = expected[i];
|
|
2533
|
+
if (type === undefined || !ts.isIdentifier(param.name) || param.type || param.initializer || param.dotDotDotToken)
|
|
2534
|
+
return;
|
|
2535
|
+
const checkerType = lowerer.checker.getTypeAtLocation(param.name);
|
|
2536
|
+
if ((checkerType.flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown)) === 0)
|
|
2537
|
+
return;
|
|
2538
|
+
const symbol = lowerer.checker.getSymbolAtLocation(param.name);
|
|
2539
|
+
if (!symbol)
|
|
2540
|
+
return;
|
|
2541
|
+
previousRuntime.push({ symbol, type: lowerer.runtimeOptionalBindingTypes.get(symbol) });
|
|
2542
|
+
lowerer.runtimeOptionalBindingTypes.set(symbol, type);
|
|
2543
|
+
const primitive = type.kind === "string" ? lowerer.checker.getStringType() :
|
|
2544
|
+
type.kind === "f64" ? lowerer.checker.getNumberType() :
|
|
2545
|
+
type.kind === "bool" ? lowerer.checker.getBooleanType() : null;
|
|
2546
|
+
if (primitive !== null) {
|
|
2547
|
+
contextualTs ??= new Map(previousImplicit ?? []);
|
|
2548
|
+
contextualTs.set(symbol, primitive);
|
|
2549
|
+
}
|
|
2550
|
+
});
|
|
2551
|
+
}
|
|
2552
|
+
if (contextualTs !== null)
|
|
2553
|
+
lowerer.implicitParamTypes = contextualTs;
|
|
2554
|
+
let fnArg;
|
|
2555
|
+
try {
|
|
2556
|
+
fnArg = lowerer.lowerExpr(argNode);
|
|
2557
|
+
}
|
|
2558
|
+
finally {
|
|
2559
|
+
lowerer.implicitParamTypes = previousImplicit;
|
|
2560
|
+
for (const { symbol, type } of previousRuntime) {
|
|
2561
|
+
if (type === undefined)
|
|
2562
|
+
lowerer.runtimeOptionalBindingTypes.delete(symbol);
|
|
2563
|
+
else
|
|
2564
|
+
lowerer.runtimeOptionalBindingTypes.set(symbol, type);
|
|
2565
|
+
}
|
|
2566
|
+
}
|
|
2630
2567
|
if (fnArg.type.kind !== "func" ||
|
|
2631
2568
|
fnArg.type.params.length > 2 ||
|
|
2632
2569
|
(fnArg.type.params.length >= 1 && !typeEquals(fnArg.type.params[0], mapT.value)) ||
|
|
@@ -2749,8 +2686,8 @@ export function lowerSetMethodCall(lowerer, call, access) {
|
|
|
2749
2686
|
// Set<union-of-signatures>, but the VALUE lowered as a real Set of
|
|
2750
2687
|
// identity tokens (the new-Set probe) — the lowered receiver's type
|
|
2751
2688
|
// is the honest dispatch key.
|
|
2752
|
-
if (receiverIr
|
|
2753
|
-
const probed =
|
|
2689
|
+
if (receiverIr?.kind !== "set" && isJsSourceFile(access.getSourceFile())) {
|
|
2690
|
+
const probed = tryLowerExpression(lowerer, access.expression);
|
|
2754
2691
|
if (probed?.type.kind === "set")
|
|
2755
2692
|
receiverIr = probed.type;
|
|
2756
2693
|
}
|
|
@@ -4401,1271 +4338,6 @@ function buildMapSeedFn(mapT, name, arrT, tupleT, kT, vT, loc) {
|
|
|
4401
4338
|
loc,
|
|
4402
4339
|
};
|
|
4403
4340
|
}
|
|
4404
|
-
/** Regex method calls, both directions: `re.test(s)` on a regex receiver,
|
|
4405
|
-
* and `s.replace(re, tpl)` / `s.replaceAll(re, tpl)` / `s.split(re)` on a
|
|
4406
|
-
* string receiver whose FIRST ARGUMENT is a regex (the string-pattern
|
|
4407
|
-
* overloads keep their island lowering — the argument's mapped type is
|
|
4408
|
-
* what routes here, before lowerIslandMethodCall can claim the name).
|
|
4409
|
-
* Null when neither shape matches. */
|
|
4410
|
-
export function lowerRegexMethodCall(lowerer, call, access, dynReceiver) {
|
|
4411
|
-
// chainBlocked, not a raw token test: an optional-chain re-dispatch
|
|
4412
|
-
// (`rawName?.match(re)` — the receiver reads back chain-narrowed)
|
|
4413
|
-
// rides the same lowering as the plain spelling.
|
|
4414
|
-
if (lowerer.chainBlocked(access, call))
|
|
4415
|
-
return null;
|
|
4416
|
-
// A validated dyn receiver (lowerStringMethodCall's story) is a STRING
|
|
4417
|
-
// by construction — the symbol gate doesn't apply to `any` receivers.
|
|
4418
|
-
if (dynReceiver === undefined && !lowerer.isStdlibMember(access))
|
|
4419
|
-
return null;
|
|
4420
|
-
const name = access.name.text;
|
|
4421
|
-
const receiverKind = dynReceiver ? "string" : lowerer.mapTypeOf(lowerer.typeOf(access.expression))?.kind;
|
|
4422
|
-
const lowerReceiver = () => {
|
|
4423
|
-
if (dynReceiver)
|
|
4424
|
-
return dynReceiver();
|
|
4425
|
-
if (receiverKind === "string")
|
|
4426
|
-
return lowerMethodReceiver(lowerer, access.expression, STRING, access.name.text);
|
|
4427
|
-
if (receiverKind === "regex")
|
|
4428
|
-
return lowerMethodReceiver(lowerer, access.expression, { kind: "regex" }, access.name.text);
|
|
4429
|
-
return lowerer.lowerExpr(access.expression);
|
|
4430
|
-
};
|
|
4431
|
-
const loc = locOf(call);
|
|
4432
|
-
if (receiverKind === "regex" && name === "test") {
|
|
4433
|
-
// The statefulness fence, at compile time where the flags are
|
|
4434
|
-
// visible: a literal receiver (possibly parenthesized). Values that
|
|
4435
|
-
// flow through variables hit the same fence at runtime.
|
|
4436
|
-
let recv = access.expression;
|
|
4437
|
-
while (ts.isParenthesizedExpression(recv))
|
|
4438
|
-
recv = recv.expression;
|
|
4439
|
-
if (ts.isRegularExpressionLiteral(recv)) {
|
|
4440
|
-
const flags = recv.text.slice(recv.text.lastIndexOf("/") + 1);
|
|
4441
|
-
if (flags.includes("g") || flags.includes("y")) {
|
|
4442
|
-
lowerer.unsupported("SC1121", call);
|
|
4443
|
-
}
|
|
4444
|
-
}
|
|
4445
|
-
const receiver = lowerReceiver();
|
|
4446
|
-
const args = call.arguments.map((a) => lowerer.lowerExpr(a));
|
|
4447
|
-
return { kind: "regexIntrinsic", method: "test", receiver, args, type: BOOL, loc };
|
|
4448
|
-
}
|
|
4449
|
-
// `re.exec(s)` for non-g/y regexes: spec-identical to `s.match(re)`
|
|
4450
|
-
// (Symbol.match delegates to exec when lastIndex is out of play), so
|
|
4451
|
-
// it lowers to the SAME match intrinsic with the operands swapped —
|
|
4452
|
-
// the honest `string[] | null` slice, nonparticipating captures ""
|
|
4453
|
-
// (match's documented rule). The g/y statefulness fence applies at
|
|
4454
|
-
// compile time on literal receivers, exactly test()'s stance; values
|
|
4455
|
-
// reaching the runtime with those flags abort there.
|
|
4456
|
-
if (receiverKind === "regex" && name === "exec") {
|
|
4457
|
-
if (call.arguments.length !== 1)
|
|
4458
|
-
return null; // exec takes exactly the subject
|
|
4459
|
-
let recv = access.expression;
|
|
4460
|
-
while (ts.isParenthesizedExpression(recv))
|
|
4461
|
-
recv = recv.expression;
|
|
4462
|
-
if (ts.isRegularExpressionLiteral(recv)) {
|
|
4463
|
-
const flags = recv.text.slice(recv.text.lastIndexOf("/") + 1);
|
|
4464
|
-
if (flags.includes("g") || flags.includes("y")) {
|
|
4465
|
-
lowerer.unsupported("SC1121", call);
|
|
4466
|
-
}
|
|
4467
|
-
}
|
|
4468
|
-
const re = lowerReceiver();
|
|
4469
|
-
const subject = lowerer.lowerExprExpecting(call.arguments[0], STRING);
|
|
4470
|
-
const resultT = { kind: "union", unionId: lowerer.unions.intern([arrayOf(STRING), { kind: "nullT" }]) };
|
|
4471
|
-
return { kind: "regexIntrinsic", method: "match", receiver: subject, args: [re], type: resultT, loc };
|
|
4472
|
-
}
|
|
4473
|
-
// `s.match(re)` for non-g/y regexes: Node's exec-shaped result reduced
|
|
4474
|
-
// to the honest slice — the `string[] | null` union holding
|
|
4475
|
-
// [whole match, ...captures] or the null arm. The g-flag match returns
|
|
4476
|
-
// EVERY match (a different shape) and /y is stateful — both fence at
|
|
4477
|
-
// compile time on literal arguments (values reaching the runtime with
|
|
4478
|
-
// those flags abort, the test() stance). `.index`/`.input` reads on
|
|
4479
|
-
// the result fence per member (array-typed value); `.groups` reads
|
|
4480
|
-
// desugar at their access sites when the regex is statically known
|
|
4481
|
-
// (lowerMatchGroupsRead).
|
|
4482
|
-
// `s.match(re)` also claims a NULLABLE string receiver (string + unit
|
|
4483
|
-
// arms — `process.versions.openssl.match(...)`, the Dict<string>
|
|
4484
|
-
// member the node suite's crypto helper reads): the checked
|
|
4485
|
-
// extraction narrows to the string arm and a unit value throws the
|
|
4486
|
-
// catchable TypeError at the read, where Node's own member read
|
|
4487
|
-
// throws — tsc only admits the spelling in JS sources.
|
|
4488
|
-
const nullableStringRecv = receiverKind === "union" &&
|
|
4489
|
-
name === "match" &&
|
|
4490
|
-
(() => {
|
|
4491
|
-
const t = lowerer.mapTypeOf(lowerer.typeOf(access.expression));
|
|
4492
|
-
if (t?.kind !== "union")
|
|
4493
|
-
return false;
|
|
4494
|
-
const arms = lowerer.unions.get(t.unionId)?.arms ?? [];
|
|
4495
|
-
return arms.some((a) => a.kind === "string") && arms.every((a) => a.kind === "string" || isUnitType(a));
|
|
4496
|
-
})();
|
|
4497
|
-
if ((receiverKind === "string" || nullableStringRecv) && name === "match") {
|
|
4498
|
-
const arg0 = call.arguments[0];
|
|
4499
|
-
if (!arg0 || lowerer.mapTypeOf(lowerer.typeOf(arg0))?.kind !== "regex")
|
|
4500
|
-
return null; // string-pattern match: the SC2020 fence
|
|
4501
|
-
if (call.arguments.length !== 1)
|
|
4502
|
-
return null;
|
|
4503
|
-
let reNode = arg0;
|
|
4504
|
-
while (ts.isParenthesizedExpression(reNode))
|
|
4505
|
-
reNode = reNode.expression;
|
|
4506
|
-
if (ts.isRegularExpressionLiteral(reNode)) {
|
|
4507
|
-
const flags = reNode.text.slice(reNode.text.lastIndexOf("/") + 1);
|
|
4508
|
-
if (flags.includes("g") || flags.includes("y")) {
|
|
4509
|
-
lowerer.unsupported("SC1120", call, "'.match()' with the 'g' or 'y' flag (an every-match array is a different shape — use replaceAll/split, or test() per position)");
|
|
4510
|
-
}
|
|
4511
|
-
}
|
|
4512
|
-
const receiver = nullableStringRecv
|
|
4513
|
-
? lowerMethodReceiver(lowerer, access.expression, STRING, access.name.text)
|
|
4514
|
-
: lowerReceiver();
|
|
4515
|
-
const re = lowerer.lowerExpr(arg0);
|
|
4516
|
-
// RegExpMatchArray | null maps to the string[] | null union by
|
|
4517
|
-
// itself; intern it directly when the checker's spelling doesn't —
|
|
4518
|
-
// or when it maps to something WIDER (an optional-chain call node
|
|
4519
|
-
// types `s?.match(re)` with the chain's `| undefined`; the intrinsic
|
|
4520
|
-
// itself answers string[] | null, and the chain wrapper widens).
|
|
4521
|
-
const exactT = { kind: "union", unionId: lowerer.unions.intern([arrayOf(STRING), { kind: "nullT" }]) };
|
|
4522
|
-
const mapped = lowerer.mapTypeOf(lowerer.typeOf(call));
|
|
4523
|
-
const resultT = mapped && typeEquals(mapped, exactT) ? mapped : exactT;
|
|
4524
|
-
return { kind: "regexIntrinsic", method: "match", receiver, args: [re], type: resultT, loc };
|
|
4525
|
-
}
|
|
4526
|
-
// `s.matchAll(re)` — the every-match iterator drained EAGERLY into a
|
|
4527
|
-
// string[][] (one honest match slice per row — match's rule). Lazy vs
|
|
4528
|
-
// eager is unobservable here: strings are immutable, and the spec
|
|
4529
|
-
// clones the regex at the call, so nothing can perturb the drain. The
|
|
4530
|
-
// eager array IS what the two lowered consumers see anyway (the
|
|
4531
|
-
// immediate [...spread] and the for-of walk); a stored iterator's
|
|
4532
|
-
// .next() fences as an array member like any other. Non-global
|
|
4533
|
-
// regexes throw Node's exact TypeError at runtime, catchably
|
|
4534
|
-
// (replaceAll's stance).
|
|
4535
|
-
if (receiverKind === "string" && name === "matchAll") {
|
|
4536
|
-
const arg0 = call.arguments[0];
|
|
4537
|
-
if (!arg0 || lowerer.mapTypeOf(lowerer.typeOf(arg0))?.kind !== "regex")
|
|
4538
|
-
return null; // string-pattern form: the SC2020 fence
|
|
4539
|
-
if (call.arguments.length !== 1)
|
|
4540
|
-
return null;
|
|
4541
|
-
const receiver = lowerReceiver();
|
|
4542
|
-
const re = lowerer.lowerExpr(arg0);
|
|
4543
|
-
return {
|
|
4544
|
-
kind: "regexIntrinsic",
|
|
4545
|
-
method: "matchAll",
|
|
4546
|
-
receiver,
|
|
4547
|
-
args: [re],
|
|
4548
|
-
type: arrayOf(arrayOf(STRING)),
|
|
4549
|
-
loc,
|
|
4550
|
-
};
|
|
4551
|
-
}
|
|
4552
|
-
// `s.search(re)` — the first match's UTF-16 index, or -1. No g/y fence
|
|
4553
|
-
// (unlike test/match): Symbol.search neither reads nor writes lastIndex
|
|
4554
|
-
// — a fresh exec from position 0, so /g is irrelevant and /y anchors at
|
|
4555
|
-
// 0, exactly Node. Never throws.
|
|
4556
|
-
if (receiverKind === "string" && name === "search") {
|
|
4557
|
-
const arg0 = call.arguments[0];
|
|
4558
|
-
if (!arg0 || lowerer.mapTypeOf(lowerer.typeOf(arg0))?.kind !== "regex")
|
|
4559
|
-
return null; // string-pattern form: the SC2020 fence
|
|
4560
|
-
if (call.arguments.length !== 1)
|
|
4561
|
-
return null;
|
|
4562
|
-
const receiver = lowerReceiver();
|
|
4563
|
-
const re = lowerer.lowerExpr(arg0);
|
|
4564
|
-
return { kind: "regexIntrinsic", method: "search", receiver, args: [re], type: F64, loc };
|
|
4565
|
-
}
|
|
4566
|
-
if (receiverKind === "string" &&
|
|
4567
|
-
(name === "replace" || name === "replaceAll" || name === "split")) {
|
|
4568
|
-
const arg0 = call.arguments[0];
|
|
4569
|
-
if (!arg0 || lowerer.mapTypeOf(lowerer.typeOf(arg0))?.kind !== "regex")
|
|
4570
|
-
return null;
|
|
4571
|
-
const receiver = lowerReceiver();
|
|
4572
|
-
// Split's omitted or undefined limit is 2^32-1. Complete it here so
|
|
4573
|
-
// both IR backends and the runtime have one required (regex, limit)
|
|
4574
|
-
// shape; an optional-number value selects the default at runtime.
|
|
4575
|
-
const args = name === "split"
|
|
4576
|
-
? [lowerer.lowerExpr(arg0), lowerSplitLimitArg(lowerer, call.arguments[1], loc)]
|
|
4577
|
-
: call.arguments.map((a) => lowerer.lowerExpr(a));
|
|
4578
|
-
if (name !== "split" && args[1]?.type.kind !== "string") {
|
|
4579
|
-
lowerer.unsupported("SC1120", call.arguments[1] ?? call, "function replacement values (replacements must be string templates)");
|
|
4580
|
-
}
|
|
4581
|
-
return {
|
|
4582
|
-
kind: "regexIntrinsic",
|
|
4583
|
-
method: name,
|
|
4584
|
-
receiver,
|
|
4585
|
-
args,
|
|
4586
|
-
type: name === "split" ? arrayOf(STRING) : STRING,
|
|
4587
|
-
loc,
|
|
4588
|
-
};
|
|
4589
|
-
}
|
|
4590
|
-
return null;
|
|
4591
|
-
}
|
|
4592
|
-
/** `s.slice(1, 4)` and friends → strIntrinsic. Null when this isn't an
|
|
4593
|
-
* ambient string method call (caller keeps its generic rejection). Missing
|
|
4594
|
-
* optional arguments are omitted from `args` — the backend fills the
|
|
4595
|
-
* documented defaults; the IR never encodes them (Infinity isn't
|
|
4596
|
-
* JSON-safe). tsc has already checked arity and argument types against
|
|
4597
|
-
* ambient/scriptc.d.ts. */
|
|
4598
|
-
export function lowerStringMethodCall(lowerer, call, access, dynReceiver) {
|
|
4599
|
-
if (lowerer.chainBlocked(access, call))
|
|
4600
|
-
return null;
|
|
4601
|
-
if (dynReceiver === undefined && access.name.text === "localeCompare")
|
|
4602
|
-
return lowerLocaleCompareCall(lowerer, call, access);
|
|
4603
|
-
const entry = own(STR_METHODS, access.name.text);
|
|
4604
|
-
if (!entry)
|
|
4605
|
-
return null;
|
|
4606
|
-
// A validated dyn receiver (`pkg.name.replace(...)` on a JSON.parse
|
|
4607
|
-
// value) arrives pre-extracted through `dynReceiver`; its checker type
|
|
4608
|
-
// is `any`, so the type/symbol gates don't apply — the dyn value's
|
|
4609
|
-
// methods can only BE the string intrinsics.
|
|
4610
|
-
if (dynReceiver === undefined) {
|
|
4611
|
-
const receiverIr = lowerer.mapTypeOf(lowerer.typeOf(access.expression));
|
|
4612
|
-
// `require.main?.filename.startsWith(...)`: the checker types the
|
|
4613
|
-
// receiver `string | undefined` (the chain's short-circuit arm), but
|
|
4614
|
-
// the entry-module fold lowers it to a compile-time STRING — the
|
|
4615
|
-
// suite harness's skip() shape. Everything else keeps the strict
|
|
4616
|
-
// string gate.
|
|
4617
|
-
if (receiverIr?.kind !== "string" && !isRequireMainFilename(lowerer, access.expression))
|
|
4618
|
-
return null;
|
|
4619
|
-
if (!lowerer.isStdlibMember(access))
|
|
4620
|
-
return null;
|
|
4621
|
-
}
|
|
4622
|
-
// The lib declares optional parameters beyond the lowered forms
|
|
4623
|
-
// (includes/startsWith/endsWith take a position); fence the unlowered
|
|
4624
|
-
// arities instead of passing arguments the runtime doesn't take.
|
|
4625
|
-
if (call.arguments.length < entry.minArgs || call.arguments.length > entry.maxArgs) {
|
|
4626
|
-
lowerer.noLowering(`.${access.name.text} with ${call.arguments.length} argument${call.arguments.length === 1 ? "" : "s"} on strings`, call);
|
|
4627
|
-
}
|
|
4628
|
-
const receiver = dynReceiver
|
|
4629
|
-
? dynReceiver()
|
|
4630
|
-
: lowerMethodReceiver(lowerer, access.expression, STRING, access.name.text);
|
|
4631
|
-
const args = entry.method === "split"
|
|
4632
|
-
? [lowerer.lowerExpr(call.arguments[0]), lowerSplitLimitArg(lowerer, call.arguments[1], locOf(call))]
|
|
4633
|
-
: call.arguments.map((a) => lowerer.lowerExpr(a));
|
|
4634
|
-
// split's separator must BE a string here (a regex argument was
|
|
4635
|
-
// claimed by lowerRegexMethodCall before this path) — the lib's
|
|
4636
|
-
// `string | RegExp` union has no lowering as a VALUE.
|
|
4637
|
-
if (entry.method === "split" && args[0].type.kind !== "string") {
|
|
4638
|
-
lowerer.unsupported("SC1090", call.arguments[0], `'.split()' on a '${lowerer.fmt(args[0].type)}' separator (pass a string, or a regex literal)`);
|
|
4639
|
-
}
|
|
4640
|
-
// padStart/padEnd with the fill omitted: Node pads with " " — the
|
|
4641
|
-
// same call with the default made explicit.
|
|
4642
|
-
if ((entry.method === "padStart" || entry.method === "padEnd") && args.length === 1) {
|
|
4643
|
-
args.push({ kind: "strLit", value: " ", type: STRING, loc: locOf(call) });
|
|
4644
|
-
}
|
|
4645
|
-
return {
|
|
4646
|
-
kind: "strIntrinsic",
|
|
4647
|
-
method: entry.method,
|
|
4648
|
-
receiver,
|
|
4649
|
-
args,
|
|
4650
|
-
type: entry.result,
|
|
4651
|
-
loc: locOf(call),
|
|
4652
|
-
};
|
|
4653
|
-
}
|
|
4654
|
-
/** `a.localeCompare(b)` — the one-argument form only (locales/options
|
|
4655
|
-
* select ICU collations that do not exist here). Lowers to an interned
|
|
4656
|
-
* synthetic function returning -1/0/1 by CODE-UNIT order — the same
|
|
4657
|
-
* ordering as the string relational operators — NOT Node's ICU default
|
|
4658
|
-
* collation: a documented divergence (SEMANTICS.md; e.g. Node says
|
|
4659
|
-
* "a" < "B" under ICU while code units say "B" < "a"). For same-case
|
|
4660
|
-
* ASCII the orders agree. Null when the receiver isn't a stdlib string
|
|
4661
|
-
* (caller keeps its generic rejection). */
|
|
4662
|
-
function lowerLocaleCompareCall(lowerer, call, access) {
|
|
4663
|
-
const receiverIr = lowerer.mapTypeOf(lowerer.typeOf(access.expression));
|
|
4664
|
-
if (receiverIr?.kind !== "string")
|
|
4665
|
-
return null;
|
|
4666
|
-
if (!lowerer.isStdlibMember(access))
|
|
4667
|
-
return null;
|
|
4668
|
-
const loc = locOf(call);
|
|
4669
|
-
if (call.arguments.length !== 1) {
|
|
4670
|
-
lowerer.noLowering(`.localeCompare with ${call.arguments.length} arguments`, call, "the locales/options parameters select ICU collations that have no lowering — " +
|
|
4671
|
-
"pass exactly the comparison string");
|
|
4672
|
-
}
|
|
4673
|
-
const receiver = lowerMethodReceiver(lowerer, access.expression, STRING, access.name.text);
|
|
4674
|
-
const arg = lowerer.lowerExpr(call.arguments[0]);
|
|
4675
|
-
if (arg.type.kind !== "string")
|
|
4676
|
-
lowerer.badType(call.arguments[0], lowerer.typeOf(call.arguments[0]));
|
|
4677
|
-
const key = "localeCompare";
|
|
4678
|
-
let helper = lowerer.arrHofHelpers.get(key);
|
|
4679
|
-
if (!helper) {
|
|
4680
|
-
helper = `%str.localeCompare`;
|
|
4681
|
-
lowerer.arrHofHelpers.set(key, helper);
|
|
4682
|
-
lowerer.liftedFns.push(buildLocaleCompareFn(helper, loc));
|
|
4683
|
-
}
|
|
4684
|
-
return { kind: "call", callee: helper, args: [receiver, arg], type: F64, loc };
|
|
4685
|
-
}
|
|
4686
|
-
/** `return a < b ? -1 : a > b ? 1 : 0` over the strCmp primitive (the
|
|
4687
|
-
* relational operators' exact machinery — one interned helper, no new IR
|
|
4688
|
-
* or runtime surface). */
|
|
4689
|
-
function buildLocaleCompareFn(name, loc) {
|
|
4690
|
-
const cmp = (op) => ({ kind: "strCmp", op, left: varRef("a.0", STRING, loc), right: varRef("b.0", STRING, loc), type: BOOL, loc });
|
|
4691
|
-
const body = [
|
|
4692
|
-
{
|
|
4693
|
-
kind: "return",
|
|
4694
|
-
value: {
|
|
4695
|
-
kind: "ternary",
|
|
4696
|
-
cond: cmp("<"),
|
|
4697
|
-
then: numLit(-1, loc),
|
|
4698
|
-
else_: { kind: "ternary", cond: cmp(">"), then: numLit(1, loc), else_: numLit(0, loc), type: F64, loc },
|
|
4699
|
-
type: F64,
|
|
4700
|
-
loc,
|
|
4701
|
-
},
|
|
4702
|
-
loc,
|
|
4703
|
-
},
|
|
4704
|
-
];
|
|
4705
|
-
return {
|
|
4706
|
-
name,
|
|
4707
|
-
params: [
|
|
4708
|
-
{ localId: "a.0", name: "a", type: STRING },
|
|
4709
|
-
{ localId: "b.0", name: "b", type: STRING },
|
|
4710
|
-
],
|
|
4711
|
-
returnType: F64,
|
|
4712
|
-
locals: [
|
|
4713
|
-
{ id: "a.0", name: "a", type: STRING, mutable: true },
|
|
4714
|
-
{ id: "b.0", name: "b", type: STRING, mutable: true },
|
|
4715
|
-
],
|
|
4716
|
-
body,
|
|
4717
|
-
loc,
|
|
4718
|
-
};
|
|
4719
|
-
}
|
|
4720
|
-
/* ── typed arrays / Buffer ─────────────────────────────────────────────── */
|
|
4721
|
-
/** The typed-array constructors with a runtime representation, by lib
|
|
4722
|
-
* interface name. The other TypedArray flavors (Int8Array, Float64Array,
|
|
4723
|
-
* DataView, ...) fall through to the generic stdlib-constructor fence. */
|
|
4724
|
-
const BYTES_CTORS = {
|
|
4725
|
-
Uint8Array: "u8",
|
|
4726
|
-
Uint32Array: "u32",
|
|
4727
|
-
Int32Array: "i32",
|
|
4728
|
-
Float32Array: "f32",
|
|
4729
|
-
};
|
|
4730
|
-
/** `new Uint8Array(...)` / `new Uint32Array(...)` / `new Float32Array(...)`
|
|
4731
|
-
* (stdlib provenance — a user's own class with the name resolves through
|
|
4732
|
-
* classBySymbol). Lowered argument shapes: none (empty), a length
|
|
4733
|
-
* (zero-filled; ToIndex at runtime — invalid lengths throw Node's
|
|
4734
|
-
* RangeError), a same-kind typed array or Buffer (an independent COPY —
|
|
4735
|
-
* the readFile chain's `new Uint8Array(await readFile(p))`), a number[]
|
|
4736
|
-
* literal (element-coerced; its contextual type is the lib's
|
|
4737
|
-
* ArrayLike/Iterable union, which cannot map — the Set-seed pattern), or
|
|
4738
|
-
* a number[]-typed value. ArrayBuffer/view forms are fenced: scriptc
|
|
4739
|
-
* typed arrays own their storage. Null when this isn't a stdlib
|
|
4740
|
-
* typed-array construction. */
|
|
4741
|
-
export function lowerBytesNew(lowerer, expr, symbol) {
|
|
4742
|
-
if (symbol && symbol.name === "DataView" && lowerer.isStdlibSymbol(symbol)) {
|
|
4743
|
-
return lowerDataViewNew(lowerer, expr);
|
|
4744
|
-
}
|
|
4745
|
-
const elem = symbol ? own(BYTES_CTORS, symbol.name) : undefined;
|
|
4746
|
-
if (!elem || !symbol || !lowerer.isStdlibSymbol(symbol))
|
|
4747
|
-
return null;
|
|
4748
|
-
const name = symbol.name;
|
|
4749
|
-
const type = bytesOf(elem);
|
|
4750
|
-
const loc = locOf(expr);
|
|
4751
|
-
const args = expr.arguments ?? [];
|
|
4752
|
-
if (args.length === 0)
|
|
4753
|
-
return { kind: "bytesNew", source: null, type, loc };
|
|
4754
|
-
if (args.length === 1 && !ts.isSpreadElement(args[0])) {
|
|
4755
|
-
const argNode = args[0];
|
|
4756
|
-
if (ts.isArrayLiteralExpression(argNode) && !argNode.elements.some(ts.isSpreadElement)) {
|
|
4757
|
-
const elems = argNode.elements.map((el) => lowerer.lowerExprExpecting(el, F64));
|
|
4758
|
-
const seed = { kind: "arrayLit", elems, type: arrayOf(F64), loc };
|
|
4759
|
-
return { kind: "bytesNew", source: seed, type, loc };
|
|
4760
|
-
}
|
|
4761
|
-
// The SYNTACTIC `new T(new ArrayBuffer(n))` and `new T(new
|
|
4762
|
-
// SharedArrayBuffer(n))` forms — fresh-buffer construction (the
|
|
4763
|
-
// shared spelling is the Atomics.wait sleep idiom's). The buffer
|
|
4764
|
-
// never exists as a value: n must be a byte-length LITERAL divisible
|
|
4765
|
-
// by the element size (tsc would admit any number; a bad one is
|
|
4766
|
-
// Node's RangeError — rejected at compile time instead of
|
|
4767
|
-
// half-lowering), and the whole expression is a zero-filled typed
|
|
4768
|
-
// array of n/elemSize elements. Erasing the buffer is exact: nothing
|
|
4769
|
-
// else can ever reference it, so neither sharing (scriptc has no
|
|
4770
|
-
// threads) nor aliasing is observable — SEMANTICS.md documents the
|
|
4771
|
-
// stance. The RESIZABLE form (a maxByteLength options bag) fences by
|
|
4772
|
-
// name: resize needs the buffer to exist as a value, and none does.
|
|
4773
|
-
if (ts.isNewExpression(argNode) &&
|
|
4774
|
-
ts.isIdentifier(argNode.expression) &&
|
|
4775
|
-
(argNode.expression.text === "ArrayBuffer" ||
|
|
4776
|
-
argNode.expression.text === "SharedArrayBuffer") &&
|
|
4777
|
-
lowerer.isStdlibSymbol(lowerer.resolveValueSymbol(argNode.expression) ?? undefined)) {
|
|
4778
|
-
const bufCtor = argNode.expression.text;
|
|
4779
|
-
if ((argNode.arguments?.length ?? 0) > 1) {
|
|
4780
|
-
lowerer.noLowering(`new ${name} over a resizable ${bufCtor}`, argNode, "a maxByteLength options bag makes the buffer resizable, and resizing needs the buffer " +
|
|
4781
|
-
"to exist as a runtime value — no free-standing ArrayBuffer value exists (the buffer here " +
|
|
4782
|
-
"erases into the view): drop the options bag");
|
|
4783
|
-
}
|
|
4784
|
-
const elemSize = elem === "u8" ? 1 : 4;
|
|
4785
|
-
const lenArg = argNode.arguments?.length === 1 ? argNode.arguments[0] : undefined;
|
|
4786
|
-
const lenT = lenArg ? lowerer.typeOf(lenArg) : null;
|
|
4787
|
-
const byteLen = lenT?.isNumberLiteralType() ? lenT.value : null;
|
|
4788
|
-
if (byteLen === null || byteLen % elemSize !== 0 || byteLen < 0) {
|
|
4789
|
-
lowerer.noLowering(`new ${name} over this ${bufCtor}`, argNode, `the byte length must be a number literal divisible by ${elemSize} — new ${name}(new ${bufCtor}(${elemSize}))`);
|
|
4790
|
-
}
|
|
4791
|
-
const count = { kind: "numLit", value: byteLen / elemSize, type: F64, loc };
|
|
4792
|
-
return { kind: "bytesNew", source: count, type, loc };
|
|
4793
|
-
}
|
|
4794
|
-
const src = lowerer.lowerExpr(argNode);
|
|
4795
|
-
if (src.type.kind === "union" && lowerer.armTag(src.type.unionId, UNDEFINED_T) >= 0) {
|
|
4796
|
-
const present = lowerer.stripUndefinedArm(src.type);
|
|
4797
|
-
if (present.kind === "array" && present.elem.kind === "f64") {
|
|
4798
|
-
const undefTag = lowerer.armTag(src.type.unionId, UNDEFINED_T);
|
|
4799
|
-
const presentTag = lowerer.armTag(src.type.unionId, present);
|
|
4800
|
-
if (undefTag >= 0 && presentTag >= 0) {
|
|
4801
|
-
const empty = { kind: "arrayLit", elems: [], type: present, loc };
|
|
4802
|
-
const source = {
|
|
4803
|
-
kind: "ternary",
|
|
4804
|
-
cond: { kind: "unionIsTag", unionId: src.type.unionId, tag: undefTag, negated: false, value: src, type: BOOL, loc },
|
|
4805
|
-
then: empty,
|
|
4806
|
-
else_: { kind: "unionNarrow", unionId: src.type.unionId, tag: presentTag, value: src, type: present, loc },
|
|
4807
|
-
type: present,
|
|
4808
|
-
loc,
|
|
4809
|
-
};
|
|
4810
|
-
return { kind: "bytesNew", source, type, loc };
|
|
4811
|
-
}
|
|
4812
|
-
}
|
|
4813
|
-
}
|
|
4814
|
-
if (src.type.kind === "f64" ||
|
|
4815
|
-
typeEquals(src.type, type) ||
|
|
4816
|
-
(src.type.kind === "array" && src.type.elem.kind === "f64")) {
|
|
4817
|
-
return { kind: "bytesNew", source: src, type, loc };
|
|
4818
|
-
}
|
|
4819
|
-
if (src.type.kind === "bytes") {
|
|
4820
|
-
lowerer.noLowering(`new ${name} over a '${lowerer.fmt(src.type)}'`, argNode, "cross-kind typed-array conversion has no lowering — copy element by element");
|
|
4821
|
-
}
|
|
4822
|
-
lowerer.noLowering(`new ${name} over '${lowerer.fmt(src.type)}' values`, argNode, `supported: new ${name}(), (length), (typedArray) — always a copy — or (number[]); ` +
|
|
4823
|
-
"ArrayBuffers and views do not exist here (narrow unions first)");
|
|
4824
|
-
}
|
|
4825
|
-
lowerer.noLowering(`new ${name} with ${args.length} arguments`, expr, `supported: new ${name}(), (length), (typedArray), or (number[])`);
|
|
4826
|
-
}
|
|
4827
|
-
/** `new DataView(x.buffer, byteOffset?, byteLength?)` (stdlib provenance).
|
|
4828
|
-
* The first argument must be the SYNTACTIC `.buffer` of a typed-array/
|
|
4829
|
-
* Buffer value — `x.buffer` names x's own storage (scriptc typed arrays
|
|
4830
|
-
* always own it whole, byteOffset 0), so the view construction takes x
|
|
4831
|
-
* itself as the receiver and no ArrayBuffer value ever exists.
|
|
4832
|
-
* byteOffset/byteLength are optional f64s (omitted args OMITTED, like
|
|
4833
|
-
* slice); bad indices THROW Node's RangeErrors catchably. The result is
|
|
4834
|
-
* a true borrowed view: reads and writes through it alias x. */
|
|
4835
|
-
function lowerDataViewNew(lowerer, expr) {
|
|
4836
|
-
const loc = locOf(expr);
|
|
4837
|
-
const args = expr.arguments ?? [];
|
|
4838
|
-
const bufNode = args[0];
|
|
4839
|
-
if (!bufNode || args.length > 3 || args.some(ts.isSpreadElement)) {
|
|
4840
|
-
lowerer.noLowering(`new DataView with ${args.length} arguments`, expr, "supported: new DataView(x.buffer), (x.buffer, byteOffset), or (x.buffer, byteOffset, byteLength)");
|
|
4841
|
-
}
|
|
4842
|
-
// The FRESH-BUFFER form: `new DataView(new ArrayBuffer(n), ...)` — the
|
|
4843
|
-
// buffer erases into the view (nothing else can ever reference it, so
|
|
4844
|
-
// the aliasing a shared buffer would exhibit is unobservable): the
|
|
4845
|
-
// view's storage is a fresh zero-filled n-byte allocation. n must be a
|
|
4846
|
-
// non-negative integer LITERAL (tsc admits any number; a fractional
|
|
4847
|
-
// one is ToIndex truncation nothing here implements). byteOffset and
|
|
4848
|
-
// byteLength keep their runtime Node-RangeError story. The RESIZABLE
|
|
4849
|
-
// form (a maxByteLength options bag) fences by name.
|
|
4850
|
-
if (ts.isNewExpression(bufNode) &&
|
|
4851
|
-
ts.isIdentifier(bufNode.expression) &&
|
|
4852
|
-
bufNode.expression.text === "ArrayBuffer" &&
|
|
4853
|
-
lowerer.isStdlibSymbol(lowerer.resolveValueSymbol(bufNode.expression) ?? undefined)) {
|
|
4854
|
-
if ((bufNode.arguments?.length ?? 0) > 1) {
|
|
4855
|
-
lowerer.noLowering("new DataView over a resizable ArrayBuffer", bufNode, "a maxByteLength options bag makes the buffer resizable, and resizing needs the buffer " +
|
|
4856
|
-
"to exist as a runtime value — no free-standing ArrayBuffer value exists (the buffer here " +
|
|
4857
|
-
"erases into the view): drop the options bag");
|
|
4858
|
-
}
|
|
4859
|
-
const lenArg = bufNode.arguments?.length === 1 ? bufNode.arguments[0] : undefined;
|
|
4860
|
-
const lenT = lenArg ? lowerer.typeOf(lenArg) : null;
|
|
4861
|
-
const byteLen = lenT?.isNumberLiteralType() ? lenT.value : null;
|
|
4862
|
-
if (byteLen === null || !Number.isInteger(byteLen) || byteLen < 0) {
|
|
4863
|
-
lowerer.noLowering("new DataView over this ArrayBuffer", bufNode, "the byte length must be a non-negative integer literal — new DataView(new ArrayBuffer(8))");
|
|
4864
|
-
}
|
|
4865
|
-
const count = { kind: "numLit", value: byteLen, type: F64, loc };
|
|
4866
|
-
const receiver = { kind: "bytesNew", source: count, type: BYTES_U8, loc };
|
|
4867
|
-
const idxArgs = args.slice(1).map((a) => lowerer.lowerExprExpecting(a, F64));
|
|
4868
|
-
return { kind: "bytesIntrinsic", method: "dataViewNew", receiver, args: idxArgs, type: BYTES_U8, loc };
|
|
4869
|
-
}
|
|
4870
|
-
const hint = "views compile over a typed array's own storage — new DataView(x.buffer, byteOffset?, byteLength?) " +
|
|
4871
|
-
"where x is a Uint8Array/Uint32Array/Float32Array/Buffer value — or a fresh buffer erased into " +
|
|
4872
|
-
"the view: new DataView(new ArrayBuffer(n), ...); free-standing ArrayBuffers have no representation";
|
|
4873
|
-
if (!ts.isPropertyAccessExpression(bufNode) || bufNode.name.text !== "buffer" || bufNode.questionDotToken) {
|
|
4874
|
-
lowerer.noLowering("new DataView over this buffer expression", bufNode, hint);
|
|
4875
|
-
}
|
|
4876
|
-
const srcIr = lowerer.mapTypeOf(lowerer.typeOf(bufNode.expression));
|
|
4877
|
-
if (srcIr?.kind !== "bytes" || !lowerer.isStdlibMember(bufNode)) {
|
|
4878
|
-
lowerer.noLowering(`new DataView over '.buffer' of a '${lowerer.checker.typeToString(lowerer.typeOf(bufNode.expression))}'`, bufNode, hint);
|
|
4879
|
-
}
|
|
4880
|
-
const receiver = lowerer.lowerExpr(bufNode.expression);
|
|
4881
|
-
const idxArgs = args.slice(1).map((a) => lowerer.lowerExprExpecting(a, F64));
|
|
4882
|
-
return { kind: "bytesIntrinsic", method: "dataViewNew", receiver, args: idxArgs, type: BYTES_U8, loc };
|
|
4883
|
-
}
|
|
4884
|
-
/** Method calls on typed-array/Buffer receivers: slice and subarray (BOTH
|
|
4885
|
-
* copy — subarray's sharing is the documented divergence), set(src,
|
|
4886
|
-
* offset?), and the u8-only Buffer surface: toString(enc?) plus the
|
|
4887
|
-
* whole numeric read/write family (fixed widths BE/LE and the
|
|
4888
|
-
* variable-width read/writeUIntLE quartet — BUF_NUM_METHODS). Everything
|
|
4889
|
-
* else the lib declares (fill, indexOf, reverse, ...) falls through to
|
|
4890
|
-
* the SC2020 member fence. Null when this isn't a bytes method call. */
|
|
4891
|
-
export function lowerBytesMethodCall(lowerer, call, access) {
|
|
4892
|
-
if (lowerer.chainBlocked(access, call))
|
|
4893
|
-
return null;
|
|
4894
|
-
const name = access.name.text;
|
|
4895
|
-
const receiverIr = lowerer.mapTypeOf(lowerer.typeOf(access.expression));
|
|
4896
|
-
if (receiverIr?.kind !== "bytes")
|
|
4897
|
-
return null;
|
|
4898
|
-
if (!lowerer.isStdlibMember(access))
|
|
4899
|
-
return null;
|
|
4900
|
-
const loc = locOf(call);
|
|
4901
|
-
const nArgs = call.arguments.length;
|
|
4902
|
-
if (receiverIr.elem === "u8" && name === "toSorted") {
|
|
4903
|
-
return lowerBytesToSortedCall(lowerer, call, access, receiverIr);
|
|
4904
|
-
}
|
|
4905
|
-
if (receiverIr.elem === "u8" && name === "toReversed") {
|
|
4906
|
-
if (nArgs !== 0) {
|
|
4907
|
-
lowerer.noLowering(`.toReversed with ${nArgs} arguments on Uint8Array`, call);
|
|
4908
|
-
}
|
|
4909
|
-
return {
|
|
4910
|
-
kind: "bytesIntrinsic",
|
|
4911
|
-
method: "toReversed",
|
|
4912
|
-
receiver: lowerer.lowerExpr(access.expression),
|
|
4913
|
-
args: [],
|
|
4914
|
-
type: receiverIr,
|
|
4915
|
-
loc,
|
|
4916
|
-
};
|
|
4917
|
-
}
|
|
4918
|
-
if (receiverIr.elem === "u8" && name === "with") {
|
|
4919
|
-
if (nArgs !== 2 || call.arguments.some(ts.isSpreadElement)) {
|
|
4920
|
-
lowerer.noLowering(`.with with ${nArgs} arguments on Uint8Array`, call);
|
|
4921
|
-
}
|
|
4922
|
-
return {
|
|
4923
|
-
kind: "bytesIntrinsic",
|
|
4924
|
-
method: "with",
|
|
4925
|
-
receiver: lowerer.lowerExpr(access.expression),
|
|
4926
|
-
args: [
|
|
4927
|
-
lowerer.lowerExprExpecting(call.arguments[0], F64),
|
|
4928
|
-
lowerer.lowerExprExpecting(call.arguments[1], F64),
|
|
4929
|
-
],
|
|
4930
|
-
type: receiverIr,
|
|
4931
|
-
loc,
|
|
4932
|
-
};
|
|
4933
|
-
}
|
|
4934
|
-
if (receiverIr.elem === "u8" && name === "join") {
|
|
4935
|
-
if (nArgs > 1 || call.arguments.some(ts.isSpreadElement)) {
|
|
4936
|
-
lowerer.noLowering(`.join with ${nArgs} arguments on Uint8Array`, call);
|
|
4937
|
-
}
|
|
4938
|
-
const separatorDefault = {
|
|
4939
|
-
kind: "strLit",
|
|
4940
|
-
value: ",",
|
|
4941
|
-
type: STRING,
|
|
4942
|
-
loc,
|
|
4943
|
-
};
|
|
4944
|
-
const separator = call.arguments[0]
|
|
4945
|
-
? lowerOptionalDefaultArg(lowerer, call.arguments[0], STRING, separatorDefault)
|
|
4946
|
-
: separatorDefault;
|
|
4947
|
-
return {
|
|
4948
|
-
kind: "bytesIntrinsic",
|
|
4949
|
-
method: "join",
|
|
4950
|
-
receiver: lowerer.lowerExpr(access.expression),
|
|
4951
|
-
args: [separator],
|
|
4952
|
-
type: STRING,
|
|
4953
|
-
loc,
|
|
4954
|
-
};
|
|
4955
|
-
}
|
|
4956
|
-
if (name === "slice" || name === "subarray") {
|
|
4957
|
-
if (nArgs > 2) {
|
|
4958
|
-
lowerer.noLowering(`.${name} with ${nArgs} arguments on typed arrays`, call);
|
|
4959
|
-
}
|
|
4960
|
-
const receiver = lowerer.lowerExpr(access.expression);
|
|
4961
|
-
const args = call.arguments.map((a) => lowerer.lowerExprExpecting(a, F64));
|
|
4962
|
-
// subarray is a VIEW (TypedArray.prototype.subarray aliases), and
|
|
4963
|
-
// Buffer's slice() is subarray's deprecated Node alias — resolved by
|
|
4964
|
-
// where the member is declared, the toString discipline below. Only
|
|
4965
|
-
// the plain typed arrays' slice() copies (JS-exact).
|
|
4966
|
-
const declaredOnBuffer = name === "slice" &&
|
|
4967
|
-
(() => {
|
|
4968
|
-
const nameSym = lowerer.checker.getSymbolAtLocation(access.name);
|
|
4969
|
-
return nameSym !== undefined && lowerer.checker.declarationsOf(nameSym).some((d) => ts.isInterfaceDeclaration(d.parent) && d.parent.name.text === "Buffer");
|
|
4970
|
-
})();
|
|
4971
|
-
const method = name === "subarray" || declaredOnBuffer ? "subarray" : "slice";
|
|
4972
|
-
return { kind: "bytesIntrinsic", method, receiver, args, type: receiverIr, loc };
|
|
4973
|
-
}
|
|
4974
|
-
if (name === "fill" && receiverIr.elem !== "u8") {
|
|
4975
|
-
// TypedArray.prototype.fill on the non-u8 kinds: per-element value
|
|
4976
|
-
// coercion, slice-clamped relative indices, never throws. u8
|
|
4977
|
-
// receivers keep the Buffer fill family (string patterns, throwing
|
|
4978
|
-
// offset validation) — lowerBufferInstanceMethod's surface.
|
|
4979
|
-
if (nArgs < 1 || nArgs > 3) {
|
|
4980
|
-
lowerer.noLowering(`.fill with ${nArgs} arguments on typed arrays`, call);
|
|
4981
|
-
}
|
|
4982
|
-
const receiver = lowerer.lowerExpr(access.expression);
|
|
4983
|
-
const v = lowerer.lowerExprExpecting(call.arguments[0], F64);
|
|
4984
|
-
const idx = call.arguments.slice(1).map((a) => lowerer.lowerExprExpecting(a, F64));
|
|
4985
|
-
return { kind: "bytesIntrinsic", method: "fillElem", receiver, args: [v, ...idx], type: receiverIr, loc };
|
|
4986
|
-
}
|
|
4987
|
-
if (name === "set") {
|
|
4988
|
-
if (nArgs < 1 || nArgs > 2) {
|
|
4989
|
-
lowerer.noLowering(`.set with ${nArgs} arguments on typed arrays`, call);
|
|
4990
|
-
}
|
|
4991
|
-
const receiver = lowerer.lowerExpr(access.expression);
|
|
4992
|
-
const src = lowerer.lowerExpr(call.arguments[0]);
|
|
4993
|
-
if (!typeEquals(src.type, receiverIr)) {
|
|
4994
|
-
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)");
|
|
4995
|
-
}
|
|
4996
|
-
const args = [src];
|
|
4997
|
-
if (nArgs === 2)
|
|
4998
|
-
args.push(lowerer.lowerExprExpecting(call.arguments[1], F64));
|
|
4999
|
-
return { kind: "bytesIntrinsic", method: "setFrom", receiver, args, type: VOID, loc };
|
|
5000
|
-
}
|
|
5001
|
-
if (name === "toString") {
|
|
5002
|
-
// Buffer's toString(encoding?) — utf8 by default. A 0-arg toString
|
|
5003
|
-
// resolved against the plain Uint8Array interface is JS's
|
|
5004
|
-
// Array-toString (comma join), a different operation: fenced.
|
|
5005
|
-
const declaredOnBuffer = (() => {
|
|
5006
|
-
const nameSym = lowerer.checker.getSymbolAtLocation(access.name);
|
|
5007
|
-
return nameSym !== undefined && lowerer.checker.declarationsOf(nameSym).some((d) => ts.isInterfaceDeclaration(d.parent) && d.parent.name.text === "Buffer");
|
|
5008
|
-
})();
|
|
5009
|
-
if (receiverIr.elem !== "u8" || !declaredOnBuffer) {
|
|
5010
|
-
lowerer.noLowering("typed-array toString", call, 'Buffer.from(x).toString("utf8" | "hex" | "base64") is the lowered string conversion');
|
|
5011
|
-
}
|
|
5012
|
-
if (nArgs > 3)
|
|
5013
|
-
lowerer.noLowering(`.toString with ${nArgs} arguments on Buffers`, call);
|
|
5014
|
-
const receiver = lowerer.lowerExpr(access.expression);
|
|
5015
|
-
const encNode = call.arguments[0];
|
|
5016
|
-
let method = "toString";
|
|
5017
|
-
let enc = { kind: "strLit", value: "utf8", type: STRING, loc };
|
|
5018
|
-
if (encNode) {
|
|
5019
|
-
const encType = lowerer.typeOf(encNode);
|
|
5020
|
-
const encName = encType.isStringLiteralType()
|
|
5021
|
-
? knownBufEncoding(encType.value)
|
|
5022
|
-
: undefined;
|
|
5023
|
-
if (encName !== undefined) {
|
|
5024
|
-
// Keep literals on the canonical, non-throwing fast path.
|
|
5025
|
-
enc = { kind: "strLit", value: encName, type: STRING, loc };
|
|
5026
|
-
}
|
|
5027
|
-
else {
|
|
5028
|
-
const undefinedArg = lowerStaticallyUndefinedArg(lowerer, encNode);
|
|
5029
|
-
if (undefinedArg) {
|
|
5030
|
-
// An explicit undefined is the omitted-encoding default. Keep
|
|
5031
|
-
// the canonical, non-throwing path while preserving effects
|
|
5032
|
-
// from equivalent spellings such as `void sideEffect()`.
|
|
5033
|
-
enc = defaultAfterUndefined(undefinedArg, enc);
|
|
5034
|
-
}
|
|
5035
|
-
else {
|
|
5036
|
-
// A BufferEncoding-typed variable selects its decoder at
|
|
5037
|
-
// runtime. Optional variables default their undefined arm to
|
|
5038
|
-
// utf8; the checked intrinsic canonicalizes every present
|
|
5039
|
-
// alias/case and raises Node's ERR_UNKNOWN_ENCODING when a cast
|
|
5040
|
-
// lets a bad value through.
|
|
5041
|
-
enc = lowerOptionalDefaultArg(lowerer, encNode, STRING, enc);
|
|
5042
|
-
method = "toStringVar";
|
|
5043
|
-
}
|
|
5044
|
-
}
|
|
5045
|
-
}
|
|
5046
|
-
// The range form toString(enc, start[, end]) decodes the clamped
|
|
5047
|
-
// [start, end) byte window (Node's slice-then-decode). An omitted
|
|
5048
|
-
// end stays omitted (2 intrinsic args) — the emitter supplies the
|
|
5049
|
-
// receiver's length, because EXPLICIT negative ends clamp to empty
|
|
5050
|
-
// in Node and no in-band sentinel can represent "omitted" safely.
|
|
5051
|
-
if (nArgs > 1) {
|
|
5052
|
-
const start = lowerer.lowerExprExpecting(call.arguments[1], F64);
|
|
5053
|
-
if (nArgs === 3) {
|
|
5054
|
-
const end = lowerer.lowerExprExpecting(call.arguments[2], F64);
|
|
5055
|
-
return { kind: "bytesIntrinsic", method, receiver, args: [enc, start, end], type: STRING, loc };
|
|
5056
|
-
}
|
|
5057
|
-
return { kind: "bytesIntrinsic", method, receiver, args: [enc, start], type: STRING, loc };
|
|
5058
|
-
}
|
|
5059
|
-
return { kind: "bytesIntrinsic", method, receiver, args: [enc], type: STRING, loc };
|
|
5060
|
-
}
|
|
5061
|
-
// The Buffer-declared comparison/search/mutation surface. All of
|
|
5062
|
-
// these resolve against the Buffer interface (the checker keeps most
|
|
5063
|
-
// off plain typed arrays; where the lib DOES declare a same-named
|
|
5064
|
-
// TypedArray member — indexOf, includes, fill — the semantics differ,
|
|
5065
|
-
// so only Buffer-declared resolutions lower and u8 receivers gate the
|
|
5066
|
-
// rest).
|
|
5067
|
-
const declOnBuffer = (() => {
|
|
5068
|
-
const nameSym = lowerer.checker.getSymbolAtLocation(access.name);
|
|
5069
|
-
return nameSym !== undefined && lowerer.checker.declarationsOf(nameSym).some((d) => ts.isInterfaceDeclaration(d.parent) && d.parent.name.text === "Buffer");
|
|
5070
|
-
})();
|
|
5071
|
-
if (declOnBuffer && receiverIr.elem === "u8") {
|
|
5072
|
-
const bufMethod = lowerBufferInstanceMethod(lowerer, call, access, name, loc);
|
|
5073
|
-
if (bufMethod)
|
|
5074
|
-
return bufMethod;
|
|
5075
|
-
}
|
|
5076
|
-
// The Buffer numeric read/write families — every fixed-width kind in
|
|
5077
|
-
// both endiannesses ("Uint" and "UInt" alike: Node aliases both), plus
|
|
5078
|
-
// the variable-width read/writeUIntLE quartet. The kind token rides as
|
|
5079
|
-
// a strLit args[0]; omitted offsets complete to Node's default 0.
|
|
5080
|
-
const numKind = own(BUF_NUM_METHODS, name);
|
|
5081
|
-
if (numKind !== undefined) {
|
|
5082
|
-
if (receiverIr.elem !== "u8") {
|
|
5083
|
-
lowerer.noLowering(`.${name} on a '${lowerer.fmt(receiverIr)}'`, call, "the numeric families read/write Buffer bytes");
|
|
5084
|
-
}
|
|
5085
|
-
const write = name.startsWith("write");
|
|
5086
|
-
const required = write ? 1 : 0; // value; offset defaults to 0
|
|
5087
|
-
if (nArgs < required || nArgs > required + 1) {
|
|
5088
|
-
lowerer.noLowering(`.${name} with ${nArgs} arguments`, call);
|
|
5089
|
-
}
|
|
5090
|
-
const receiver = lowerer.lowerExpr(access.expression);
|
|
5091
|
-
const kind = { kind: "strLit", value: numKind, type: STRING, loc };
|
|
5092
|
-
const args = [kind, ...call.arguments.map((a) => lowerer.lowerExprExpecting(a, F64))];
|
|
5093
|
-
if (nArgs === required) {
|
|
5094
|
-
args.push({ kind: "numLit", value: 0, type: F64, loc }); // Node's offset default
|
|
5095
|
-
}
|
|
5096
|
-
return { kind: "bytesIntrinsic", method: write ? "writeNum" : "readNum", receiver, args, type: F64, loc };
|
|
5097
|
-
}
|
|
5098
|
-
const varKind = own(BUF_NUM_VAR_METHODS, name);
|
|
5099
|
-
if (varKind !== undefined) {
|
|
5100
|
-
if (receiverIr.elem !== "u8") {
|
|
5101
|
-
lowerer.noLowering(`.${name} on a '${lowerer.fmt(receiverIr)}'`, call, "the numeric families read/write Buffer bytes");
|
|
5102
|
-
}
|
|
5103
|
-
const write = name.startsWith("write");
|
|
5104
|
-
const required = write ? 3 : 2; // Node declares offset AND byteLength required
|
|
5105
|
-
if (nArgs !== required) {
|
|
5106
|
-
lowerer.noLowering(`.${name} with ${nArgs} arguments`, call);
|
|
5107
|
-
}
|
|
5108
|
-
const receiver = lowerer.lowerExpr(access.expression);
|
|
5109
|
-
const kind = { kind: "strLit", value: varKind, type: STRING, loc };
|
|
5110
|
-
const args = [kind, ...call.arguments.map((a) => lowerer.lowerExprExpecting(a, F64))];
|
|
5111
|
-
return { kind: "bytesIntrinsic", method: write ? "writeNumVar" : "readNumVar", receiver, args, type: F64, loc };
|
|
5112
|
-
}
|
|
5113
|
-
// DataView getters (DataView maps to bytes<u8>, so the receiver kind
|
|
5114
|
-
// and stdlib provenance land here; the checker keeps these names off
|
|
5115
|
-
// typed arrays and Buffers). The multi-byte kinds take the optional
|
|
5116
|
-
// littleEndian bool (omitted = big-endian, the JS default). All THROW
|
|
5117
|
-
// Node's RangeError on a bad offset (may-throw seeds).
|
|
5118
|
-
const dvGetter = own(DV_GETTERS, name);
|
|
5119
|
-
if (dvGetter !== undefined && receiverIr.elem === "u8") {
|
|
5120
|
-
if (dvGetter.method === "dvGetBigUint64Number" || dvGetter.method === "dvGetBigInt64Number") {
|
|
5121
|
-
// bigint values have no representation — only the COMPOSED
|
|
5122
|
-
// `Number(view.getBigUint64/getBigInt64(...))` compiles (the
|
|
5123
|
-
// randomBytesToString pattern): the 8-byte integer converts to
|
|
5124
|
-
// double exactly as Number(bigint) would, and the bigint never
|
|
5125
|
-
// exists as a value (the f64-typed intrinsic result IS what the
|
|
5126
|
-
// identity Number(f64) lowering hands back).
|
|
5127
|
-
const p = call.parent;
|
|
5128
|
-
const composed = ts.isCallExpression(p) &&
|
|
5129
|
-
p.arguments.length === 1 &&
|
|
5130
|
-
p.arguments[0] === call &&
|
|
5131
|
-
ts.isIdentifier(p.expression) &&
|
|
5132
|
-
p.expression.text === "Number" &&
|
|
5133
|
-
lowerer.isStdlibSymbol(lowerer.resolveValueSymbol(p.expression) ?? undefined);
|
|
5134
|
-
if (!composed) {
|
|
5135
|
-
lowerer.noLowering(`.${name} outside Number(...)`, call, `bigint values have no representation — Number(view.${name}(offset, littleEndian?)) is the lowered form`);
|
|
5136
|
-
}
|
|
5137
|
-
}
|
|
5138
|
-
const maxArgs = dvGetter.le ? 2 : 1;
|
|
5139
|
-
if (nArgs < 1 || nArgs > maxArgs) {
|
|
5140
|
-
lowerer.noLowering(`.${name} with ${nArgs} arguments`, call);
|
|
5141
|
-
}
|
|
5142
|
-
const receiver = lowerer.lowerExpr(access.expression);
|
|
5143
|
-
const args = [lowerer.lowerExprExpecting(call.arguments[0], F64)];
|
|
5144
|
-
if (nArgs === 2)
|
|
5145
|
-
args.push(lowerer.lowerExprExpecting(call.arguments[1], BOOL));
|
|
5146
|
-
return { kind: "bytesIntrinsic", method: dvGetter.method, receiver, args, type: F64, loc };
|
|
5147
|
-
}
|
|
5148
|
-
// DataView setters — the getters' mirror: (byteOffset, value) plus the
|
|
5149
|
-
// optional littleEndian bool on the multi-byte kinds. Void results;
|
|
5150
|
-
// the same constant Node RangeError on a bad offset (may-throw seeds).
|
|
5151
|
-
// setBigUint64/setBigInt64 never lower (bigint ARGUMENTS have no
|
|
5152
|
-
// representation and no composed form exists) — they keep the member
|
|
5153
|
-
// fence, as does setFloat16.
|
|
5154
|
-
const dvSetter = own(DV_SETTERS, name);
|
|
5155
|
-
if (dvSetter !== undefined && receiverIr.elem === "u8") {
|
|
5156
|
-
const maxArgs = dvSetter.le ? 3 : 2;
|
|
5157
|
-
if (nArgs < 2 || nArgs > maxArgs) {
|
|
5158
|
-
lowerer.noLowering(`.${name} with ${nArgs} arguments`, call);
|
|
5159
|
-
}
|
|
5160
|
-
const receiver = lowerer.lowerExpr(access.expression);
|
|
5161
|
-
const args = [
|
|
5162
|
-
lowerer.lowerExprExpecting(call.arguments[0], F64),
|
|
5163
|
-
lowerer.lowerExprExpecting(call.arguments[1], F64),
|
|
5164
|
-
];
|
|
5165
|
-
if (nArgs === 3)
|
|
5166
|
-
args.push(lowerer.lowerExprExpecting(call.arguments[2], BOOL));
|
|
5167
|
-
return { kind: "bytesIntrinsic", method: dvSetter.method, receiver, args, type: VOID, loc };
|
|
5168
|
-
}
|
|
5169
|
-
return null;
|
|
5170
|
-
}
|
|
5171
|
-
/** Node's Buffer encoding names → the runtime's canonical spelling (every
|
|
5172
|
-
* alias folds at compile time; the runtime sees only canonical names).
|
|
5173
|
-
* Shared by toString, Buffer.from, and Buffer.byteLength. */
|
|
5174
|
-
const BUF_ENCODINGS = {
|
|
5175
|
-
utf8: "utf8",
|
|
5176
|
-
"utf-8": "utf8",
|
|
5177
|
-
hex: "hex",
|
|
5178
|
-
base64: "base64",
|
|
5179
|
-
base64url: "base64url",
|
|
5180
|
-
latin1: "latin1",
|
|
5181
|
-
binary: "latin1",
|
|
5182
|
-
ascii: "ascii",
|
|
5183
|
-
utf16le: "utf16le",
|
|
5184
|
-
"utf-16le": "utf16le",
|
|
5185
|
-
ucs2: "utf16le",
|
|
5186
|
-
"ucs-2": "utf16le",
|
|
5187
|
-
};
|
|
5188
|
-
/** A literal encoding's canonical name, or undefined for a spelling Node
|
|
5189
|
-
* does not know — the ladder callers (stream options) turn unknown
|
|
5190
|
-
* literals into Node's runtime ERR_UNKNOWN_ENCODING throw. */
|
|
5191
|
-
export function knownBufEncoding(name) {
|
|
5192
|
-
return own(BUF_ENCODINGS, name);
|
|
5193
|
-
}
|
|
5194
|
-
/** Presence guard for a synthetic native-array loop. HOFs skip holes, while
|
|
5195
|
-
* the ordinary `arrayGet` remains a proven-presence operation. */
|
|
5196
|
-
function arrayHasExpr(arr, index, loc) {
|
|
5197
|
-
return {
|
|
5198
|
-
kind: "arrayHas",
|
|
5199
|
-
arr,
|
|
5200
|
-
index,
|
|
5201
|
-
type: BOOL,
|
|
5202
|
-
loc,
|
|
5203
|
-
};
|
|
5204
|
-
}
|
|
5205
|
-
function getHasExpr(arrT, loc) {
|
|
5206
|
-
return arrayHasExpr({ kind: "varRef", localId: "a.0", type: arrT, loc }, { kind: "varRef", localId: "i.0", type: F64, loc }, loc);
|
|
5207
|
-
}
|
|
5208
|
-
/** The literal encoding argument of a Buffer surface, normalized — or a
|
|
5209
|
-
* fence when it isn't a literal alias Node knows. */
|
|
5210
|
-
export function bufEncoding(lowerer, what, encNode) {
|
|
5211
|
-
const t = lowerer.typeOf(encNode);
|
|
5212
|
-
const v = t.isStringLiteralType() ? own(BUF_ENCODINGS, t.value) : undefined;
|
|
5213
|
-
if (v === undefined) {
|
|
5214
|
-
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');
|
|
5215
|
-
}
|
|
5216
|
-
return v;
|
|
5217
|
-
}
|
|
5218
|
-
/** The fixed-width Buffer numeric methods by source name → the readNum/
|
|
5219
|
-
* writeNum kind token. Node declares BOTH capitalizations ("UInt" is the
|
|
5220
|
-
* original, "Uint" the aliased spelling) — the tables carry both. */
|
|
5221
|
-
const BUF_NUM_METHODS = (() => {
|
|
5222
|
-
const out = {};
|
|
5223
|
-
for (const rw of ["read", "write"]) {
|
|
5224
|
-
for (const u of ["UInt", "Uint"]) {
|
|
5225
|
-
out[`${rw}${u}8`] = "u8";
|
|
5226
|
-
out[`${rw}${u}16BE`] = "u16be";
|
|
5227
|
-
out[`${rw}${u}16LE`] = "u16le";
|
|
5228
|
-
out[`${rw}${u}32BE`] = "u32be";
|
|
5229
|
-
out[`${rw}${u}32LE`] = "u32le";
|
|
5230
|
-
}
|
|
5231
|
-
out[`${rw}Int8`] = "i8";
|
|
5232
|
-
out[`${rw}Int16BE`] = "i16be";
|
|
5233
|
-
out[`${rw}Int16LE`] = "i16le";
|
|
5234
|
-
out[`${rw}Int32BE`] = "i32be";
|
|
5235
|
-
out[`${rw}Int32LE`] = "i32le";
|
|
5236
|
-
out[`${rw}FloatBE`] = "f32be";
|
|
5237
|
-
out[`${rw}FloatLE`] = "f32le";
|
|
5238
|
-
out[`${rw}DoubleBE`] = "f64be";
|
|
5239
|
-
out[`${rw}DoubleLE`] = "f64le";
|
|
5240
|
-
}
|
|
5241
|
-
return out;
|
|
5242
|
-
})();
|
|
5243
|
-
/** The variable-width quartet (offset + byteLength) → sign/endian token. */
|
|
5244
|
-
const BUF_NUM_VAR_METHODS = {
|
|
5245
|
-
readUIntBE: "ube",
|
|
5246
|
-
readUintBE: "ube",
|
|
5247
|
-
readUIntLE: "ule",
|
|
5248
|
-
readUintLE: "ule",
|
|
5249
|
-
readIntBE: "ibe",
|
|
5250
|
-
readIntLE: "ile",
|
|
5251
|
-
writeUIntBE: "ube",
|
|
5252
|
-
writeUintBE: "ube",
|
|
5253
|
-
writeUIntLE: "ule",
|
|
5254
|
-
writeUintLE: "ule",
|
|
5255
|
-
writeIntBE: "ibe",
|
|
5256
|
-
writeIntLE: "ile",
|
|
5257
|
-
};
|
|
5258
|
-
/** The Buffer-declared comparison/search/mutation methods on a u8
|
|
5259
|
-
* receiver: equals, compare, indexOf/lastIndexOf/includes (number,
|
|
5260
|
-
* string-with-encoding, and Buffer needles), fill, copy, swap16/32/64,
|
|
5261
|
-
* and write. Null when `name` isn't one of them (the numeric families
|
|
5262
|
-
* and the generic bytes surface try next). Encodings must be literals
|
|
5263
|
-
* (bufEncoding's fence); a trailing string-typed argument is the
|
|
5264
|
-
* encoding, exactly Node's overloads. */
|
|
5265
|
-
function lowerBufferInstanceMethod(lowerer, call, access, name, loc) {
|
|
5266
|
-
const args = call.arguments;
|
|
5267
|
-
const nArgs = args.length;
|
|
5268
|
-
if (args.some(ts.isSpreadElement))
|
|
5269
|
-
return null;
|
|
5270
|
-
const isStringArg = (i) => lowerer.mapTypeOf(lowerer.typeOf(args[i]))?.kind === "string";
|
|
5271
|
-
const u8Arg = (i) => {
|
|
5272
|
-
const v = lowerer.lowerExpr(args[i]);
|
|
5273
|
-
if (!(v.type.kind === "bytes" && v.type.elem === "u8")) {
|
|
5274
|
-
lowerer.noLowering(`.${name} of '${lowerer.fmt(v.type)}' values`, args[i], "a Buffer/Uint8Array argument is the lowered shape (narrow unions first)");
|
|
5275
|
-
}
|
|
5276
|
-
return v;
|
|
5277
|
-
};
|
|
5278
|
-
// The checked-dynamic crossing for a compare/equals argument slot: dyn
|
|
5279
|
-
// passes through, convertible statics (the invalid-input probes'
|
|
5280
|
-
// literals) wrap in dynFrom, `undefined` literals ride the same wrap.
|
|
5281
|
-
// Fences (never returns) when the value cannot cross — an island jsval.
|
|
5282
|
-
const chkArg = (i) => {
|
|
5283
|
-
// Object literals take the dyn literal path directly (method members
|
|
5284
|
-
// box as dyn functions — the typed record fence never applies).
|
|
5285
|
-
if (ts.isObjectLiteralExpression(args[i])) {
|
|
5286
|
-
return lowerDynObjectLiteral(lowerer, args[i]);
|
|
5287
|
-
}
|
|
5288
|
-
const v = lowerer.lowerExpr(args[i]);
|
|
5289
|
-
if (v.type.kind === "dyn")
|
|
5290
|
-
return v;
|
|
5291
|
-
if (v.kind === "unitLit" || lowerer.dynConvertible(v.type)) {
|
|
5292
|
-
return { kind: "dynFrom", value: v, type: DYN, loc: v.loc };
|
|
5293
|
-
}
|
|
5294
|
-
lowerer.noLowering(`.${name} of '${lowerer.fmt(v.type)}' values`, args[i], "a Buffer/Uint8Array argument is the lowered shape (narrow unions first)");
|
|
5295
|
-
};
|
|
5296
|
-
const argIrKind = (i) => lowerer.mapTypeOf(lowerer.typeOf(args[i]));
|
|
5297
|
-
if (name === "equals") {
|
|
5298
|
-
if (nArgs !== 1)
|
|
5299
|
-
lowerer.noLowering(`.equals with ${nArgs} arguments`, call);
|
|
5300
|
-
if (argIrKind(0)?.kind === "bytes") {
|
|
5301
|
-
const other = u8Arg(0);
|
|
5302
|
-
const receiver = lowerer.lowerExpr(access.expression);
|
|
5303
|
-
return { kind: "bytesIntrinsic", method: "equals", receiver, args: [other], type: BOOL, loc };
|
|
5304
|
-
}
|
|
5305
|
-
// Not statically bytes (the invalid-input probes, untyped JS
|
|
5306
|
-
// helpers): Node's "otherBuffer" argument ladder runs at runtime.
|
|
5307
|
-
const receiver = lowerer.lowerExpr(access.expression);
|
|
5308
|
-
return { kind: "libCall", fn: "bytes.equalsChk", args: [receiver, chkArg(0)], type: BOOL, loc };
|
|
5309
|
-
}
|
|
5310
|
-
if (name === "compare") {
|
|
5311
|
-
if (nArgs > 5)
|
|
5312
|
-
lowerer.noLowering(`.compare with ${nArgs} arguments`, call);
|
|
5313
|
-
const fast = nArgs >= 1 &&
|
|
5314
|
-
argIrKind(0)?.kind === "bytes" &&
|
|
5315
|
-
call.arguments.slice(1).every((a) => lowerer.mapTypeOf(lowerer.typeOf(a))?.kind === "f64");
|
|
5316
|
-
if (fast) {
|
|
5317
|
-
const target = u8Arg(0);
|
|
5318
|
-
const idx = call.arguments.slice(1).map((a) => lowerer.lowerExprExpecting(a, F64));
|
|
5319
|
-
const receiver = lowerer.lowerExpr(access.expression);
|
|
5320
|
-
return { kind: "bytesIntrinsic", method: "compareBuf", receiver, args: [target, ...idx], type: F64, loc };
|
|
5321
|
-
}
|
|
5322
|
-
// An absent/ill-typed target or offset (`a.compare()`, string
|
|
5323
|
-
// offsets, explicit undefined): Node's target/targetStart/targetEnd/
|
|
5324
|
-
// sourceStart/sourceEnd ladder runs at runtime; absent slots pass
|
|
5325
|
-
// the undefined dyn (Node defaults apply there).
|
|
5326
|
-
const receiver = lowerer.lowerExpr(access.expression);
|
|
5327
|
-
const slots = [];
|
|
5328
|
-
for (let i = 0; i < 5; i++)
|
|
5329
|
-
slots.push(i < nArgs ? chkArg(i) : dynUndefinedExpr(loc));
|
|
5330
|
-
return { kind: "libCall", fn: "bytes.compareChk", args: [receiver, ...slots], type: F64, loc };
|
|
5331
|
-
}
|
|
5332
|
-
if (name === "indexOf" || name === "lastIndexOf" || name === "includes") {
|
|
5333
|
-
if (nArgs < 1 || nArgs > 3)
|
|
5334
|
-
lowerer.noLowering(`.${name} with ${nArgs} arguments`, call);
|
|
5335
|
-
// The overloads: (v), (v, byteOffset), (v, encoding), (v, byteOffset,
|
|
5336
|
-
// encoding) — a trailing string-typed arg is the encoding.
|
|
5337
|
-
let encName = "utf8";
|
|
5338
|
-
let offNode;
|
|
5339
|
-
if (nArgs === 3) {
|
|
5340
|
-
offNode = args[1];
|
|
5341
|
-
encName = bufEncoding(lowerer, `.${name}`, args[2]);
|
|
5342
|
-
}
|
|
5343
|
-
else if (nArgs === 2) {
|
|
5344
|
-
if (isStringArg(1))
|
|
5345
|
-
encName = bufEncoding(lowerer, `.${name}`, args[1]);
|
|
5346
|
-
else
|
|
5347
|
-
offNode = args[1];
|
|
5348
|
-
}
|
|
5349
|
-
const resultT = name === "includes" ? BOOL : F64;
|
|
5350
|
-
const vT = lowerer.mapTypeOf(lowerer.typeOf(args[0]));
|
|
5351
|
-
const receiver = lowerer.lowerExpr(access.expression);
|
|
5352
|
-
if (vT?.kind === "f64") {
|
|
5353
|
-
// A number needle wraps & 0xFF at runtime (Buffer semantics; the
|
|
5354
|
-
// encoding is irrelevant, like Node).
|
|
5355
|
-
const v = lowerer.lowerExprExpecting(args[0], F64);
|
|
5356
|
-
const numArgs = [v, ...(offNode ? [lowerer.lowerExprExpecting(offNode, F64)] : [])];
|
|
5357
|
-
const method = name === "indexOf" ? "indexOfNum" : name === "lastIndexOf" ? "lastIndexOfNum" : "includesNum";
|
|
5358
|
-
return { kind: "bytesIntrinsic", method, receiver, args: numArgs, type: resultT, loc };
|
|
5359
|
-
}
|
|
5360
|
-
let needle;
|
|
5361
|
-
let align = 1;
|
|
5362
|
-
if (vT?.kind === "string") {
|
|
5363
|
-
const s = lowerer.lowerExprExpecting(args[0], STRING);
|
|
5364
|
-
const enc = { kind: "strLit", value: encName, type: STRING, loc };
|
|
5365
|
-
needle = { kind: "libCall", fn: "buffer.fromStr", args: [s, enc], type: BYTES_U8, loc };
|
|
5366
|
-
if (encName === "utf16le")
|
|
5367
|
-
align = 2;
|
|
5368
|
-
}
|
|
5369
|
-
else if (vT?.kind === "bytes" && vT.elem === "u8") {
|
|
5370
|
-
needle = lowerer.lowerExpr(args[0]);
|
|
5371
|
-
}
|
|
5372
|
-
else {
|
|
5373
|
-
lowerer.noLowering(`.${name} of '${lowerer.checker.typeToString(lowerer.typeOf(args[0]))}' values`, args[0], "string, number, and Buffer/Uint8Array needles search (narrow unions first)");
|
|
5374
|
-
}
|
|
5375
|
-
const alignLit = { kind: "numLit", value: align, type: F64, loc };
|
|
5376
|
-
const searchArgs = [needle, alignLit, ...(offNode ? [lowerer.lowerExprExpecting(offNode, F64)] : [])];
|
|
5377
|
-
return { kind: "bytesIntrinsic", method: name, receiver, args: searchArgs, type: resultT, loc };
|
|
5378
|
-
}
|
|
5379
|
-
if (name === "fill") {
|
|
5380
|
-
if (nArgs < 1 || nArgs > 4)
|
|
5381
|
-
lowerer.noLowering(`.fill with ${nArgs} arguments`, call);
|
|
5382
|
-
// A trailing string-typed arg past the value is the encoding.
|
|
5383
|
-
let encNode;
|
|
5384
|
-
let idxNodes = call.arguments.slice(1);
|
|
5385
|
-
if (idxNodes.length > 0 && isStringArg(nArgs - 1)) {
|
|
5386
|
-
encNode = idxNodes[idxNodes.length - 1];
|
|
5387
|
-
idxNodes = idxNodes.slice(0, -1);
|
|
5388
|
-
}
|
|
5389
|
-
if (idxNodes.length > 2)
|
|
5390
|
-
lowerer.noLowering(`.fill with ${nArgs} arguments`, call);
|
|
5391
|
-
const idx = idxNodes.map((a) => lowerer.lowerExprExpecting(a, F64));
|
|
5392
|
-
const receiverT = lowerer.mapTypeOf(lowerer.typeOf(access.expression));
|
|
5393
|
-
if (receiverT?.kind !== "bytes")
|
|
5394
|
-
lowerer.badType(access.expression, lowerer.typeOf(access.expression));
|
|
5395
|
-
const vT = lowerer.mapTypeOf(lowerer.typeOf(args[0]));
|
|
5396
|
-
const receiver = lowerer.lowerExpr(access.expression);
|
|
5397
|
-
if (vT?.kind === "string") {
|
|
5398
|
-
const encName = encNode ? bufEncoding(lowerer, ".fill", encNode) : "utf8";
|
|
5399
|
-
const s = lowerer.lowerExprExpecting(args[0], STRING);
|
|
5400
|
-
const enc = { kind: "strLit", value: encName, type: STRING, loc };
|
|
5401
|
-
return { kind: "bytesIntrinsic", method: "fillStr", receiver, args: [s, enc, ...idx], type: BYTES_U8, loc };
|
|
5402
|
-
}
|
|
5403
|
-
if (encNode)
|
|
5404
|
-
lowerer.noLowering(".fill with an encoding on a non-string value", encNode);
|
|
5405
|
-
if (vT?.kind === "f64") {
|
|
5406
|
-
const v = lowerer.lowerExprExpecting(args[0], F64);
|
|
5407
|
-
return { kind: "bytesIntrinsic", method: "fillNum", receiver, args: [v, ...idx], type: BYTES_U8, loc };
|
|
5408
|
-
}
|
|
5409
|
-
const pattern = u8Arg(0);
|
|
5410
|
-
return { kind: "bytesIntrinsic", method: "fill", receiver, args: [pattern, ...idx], type: BYTES_U8, loc };
|
|
5411
|
-
}
|
|
5412
|
-
if (name === "copy") {
|
|
5413
|
-
if (nArgs < 1 || nArgs > 4)
|
|
5414
|
-
lowerer.noLowering(`.copy with ${nArgs} arguments`, call);
|
|
5415
|
-
const target = u8Arg(0);
|
|
5416
|
-
const idx = call.arguments.slice(1).map((a) => lowerer.lowerExprExpecting(a, F64));
|
|
5417
|
-
const receiver = lowerer.lowerExpr(access.expression);
|
|
5418
|
-
return { kind: "bytesIntrinsic", method: "copy", receiver, args: [target, ...idx], type: F64, loc };
|
|
5419
|
-
}
|
|
5420
|
-
if (name === "swap16" || name === "swap32" || name === "swap64") {
|
|
5421
|
-
if (nArgs !== 0)
|
|
5422
|
-
lowerer.noLowering(`.${name} with ${nArgs} arguments`, call);
|
|
5423
|
-
const receiver = lowerer.lowerExpr(access.expression);
|
|
5424
|
-
return { kind: "bytesIntrinsic", method: name, receiver, args: [], type: BYTES_U8, loc };
|
|
5425
|
-
}
|
|
5426
|
-
if (name === "write") {
|
|
5427
|
-
if (nArgs < 1 || nArgs > 4)
|
|
5428
|
-
lowerer.noLowering(`.write with ${nArgs} arguments`, call);
|
|
5429
|
-
// (str), (str, enc), (str, offset), (str, offset, enc),
|
|
5430
|
-
// (str, offset, length), (str, offset, length, enc).
|
|
5431
|
-
let encNode;
|
|
5432
|
-
let idxNodes = call.arguments.slice(1);
|
|
5433
|
-
if (idxNodes.length > 0 && isStringArg(nArgs - 1)) {
|
|
5434
|
-
encNode = idxNodes[idxNodes.length - 1];
|
|
5435
|
-
idxNodes = idxNodes.slice(0, -1);
|
|
5436
|
-
}
|
|
5437
|
-
if (idxNodes.length > 2)
|
|
5438
|
-
lowerer.noLowering(`.write with ${nArgs} arguments`, call);
|
|
5439
|
-
const encName = encNode ? bufEncoding(lowerer, "Buffer.write", encNode) : "utf8";
|
|
5440
|
-
const s = lowerer.lowerExprExpecting(args[0], STRING);
|
|
5441
|
-
const enc = { kind: "strLit", value: encName, type: STRING, loc };
|
|
5442
|
-
const offset = idxNodes[0]
|
|
5443
|
-
? lowerer.lowerExprExpecting(idxNodes[0], F64)
|
|
5444
|
-
: { kind: "numLit", value: 0, type: F64, loc };
|
|
5445
|
-
const writeArgs = [s, enc, offset, ...(idxNodes[1] ? [lowerer.lowerExprExpecting(idxNodes[1], F64)] : [])];
|
|
5446
|
-
const receiver = lowerer.lowerExpr(access.expression);
|
|
5447
|
-
return { kind: "bytesIntrinsic", method: "writeStr", receiver, args: writeArgs, type: F64, loc };
|
|
5448
|
-
}
|
|
5449
|
-
return null;
|
|
5450
|
-
}
|
|
5451
|
-
/** The DataView getter surface by source name. `le` marks the multi-byte
|
|
5452
|
-
* kinds whose lib signature declares the optional littleEndian parameter
|
|
5453
|
-
* (the 8-bit getters take none). The Big pair only compiles composed
|
|
5454
|
-
* inside Number(...) — see the use site. */
|
|
5455
|
-
const DV_GETTERS = {
|
|
5456
|
-
getUint8: { method: "dvGetUint8", le: false },
|
|
5457
|
-
getInt8: { method: "dvGetInt8", le: false },
|
|
5458
|
-
getUint16: { method: "dvGetUint16", le: true },
|
|
5459
|
-
getInt16: { method: "dvGetInt16", le: true },
|
|
5460
|
-
getUint32: { method: "dvGetUint32", le: true },
|
|
5461
|
-
getInt32: { method: "dvGetInt32", le: true },
|
|
5462
|
-
getFloat32: { method: "dvGetFloat32", le: true },
|
|
5463
|
-
getFloat64: { method: "dvGetFloat64", le: true },
|
|
5464
|
-
getBigUint64: { method: "dvGetBigUint64Number", le: true },
|
|
5465
|
-
getBigInt64: { method: "dvGetBigInt64Number", le: true },
|
|
5466
|
-
};
|
|
5467
|
-
const DV_SETTERS = {
|
|
5468
|
-
setUint8: { method: "dvSetUint8", le: false },
|
|
5469
|
-
setInt8: { method: "dvSetInt8", le: false },
|
|
5470
|
-
setUint16: { method: "dvSetUint16", le: true },
|
|
5471
|
-
setInt16: { method: "dvSetInt16", le: true },
|
|
5472
|
-
setUint32: { method: "dvSetUint32", le: true },
|
|
5473
|
-
setInt32: { method: "dvSetInt32", le: true },
|
|
5474
|
-
setFloat32: { method: "dvSetFloat32", le: true },
|
|
5475
|
-
setFloat64: { method: "dvSetFloat64", le: true },
|
|
5476
|
-
};
|
|
5477
|
-
/** The Buffer statics — `Buffer.from(...)`, `Buffer.alloc(n)`,
|
|
5478
|
-
* `Buffer.concat(list)`, `Buffer.isBuffer(x)` — on THE stdlib Buffer
|
|
5479
|
-
* global (name + provenance; fallback and @types/node alike). Null when
|
|
5480
|
-
* the callee isn't a Buffer-static access. */
|
|
5481
|
-
export function lowerBufferStaticCall(lowerer, call, access) {
|
|
5482
|
-
if (call.questionDotToken || access.questionDotToken)
|
|
5483
|
-
return null;
|
|
5484
|
-
if (!lowerer.isStdlibGlobal(access.expression, "Buffer"))
|
|
5485
|
-
return null;
|
|
5486
|
-
const member = access.name.text;
|
|
5487
|
-
const loc = locOf(call);
|
|
5488
|
-
const args = call.arguments;
|
|
5489
|
-
if (member === "from") {
|
|
5490
|
-
// Buffer.from(x.buffer[, byteOffset[, length]]): a u8 VIEW sharing
|
|
5491
|
-
// x's storage (Node shares the ArrayBuffer; length is in BYTES).
|
|
5492
|
-
// The first argument must be the SYNTACTIC `.buffer` of a typed
|
|
5493
|
-
// array — the DataView peel — and the construction rides the same
|
|
5494
|
-
// dataViewNew intrinsic (offset/length validation throws Node's
|
|
5495
|
-
// DataView-shaped RangeErrors; valid indices behave exactly).
|
|
5496
|
-
if (args.length >= 1 && args.length <= 3 && !args.some(ts.isSpreadElement) &&
|
|
5497
|
-
ts.isPropertyAccessExpression(args[0]) && args[0].name.text === "buffer") {
|
|
5498
|
-
const srcNode = args[0].expression;
|
|
5499
|
-
const srcIr = lowerer.mapTypeOf(lowerer.typeOf(srcNode));
|
|
5500
|
-
if (srcIr?.kind === "bytes") {
|
|
5501
|
-
const receiver = lowerer.lowerExpr(srcNode);
|
|
5502
|
-
const idxArgs = args.slice(1).map((a) => lowerer.lowerExprExpecting(a, F64));
|
|
5503
|
-
return { kind: "bytesIntrinsic", method: "dataViewNew", receiver, args: idxArgs, type: BYTES_U8, loc };
|
|
5504
|
-
}
|
|
5505
|
-
}
|
|
5506
|
-
if (args.length >= 1 && args.length <= 2 && !ts.isSpreadElement(args[0])) {
|
|
5507
|
-
const argNode = args[0];
|
|
5508
|
-
if (ts.isArrayLiteralExpression(argNode) && !argNode.elements.some(ts.isSpreadElement)) {
|
|
5509
|
-
// A number[] literal (contextually typed by the lib's readonly
|
|
5510
|
-
// number[] slot — build it element-wise, the Set-seed pattern).
|
|
5511
|
-
if (args.length === 1) {
|
|
5512
|
-
const elems = argNode.elements.map((el) => lowerer.lowerExprExpecting(el, F64));
|
|
5513
|
-
const seed = { kind: "arrayLit", elems, type: arrayOf(F64), loc };
|
|
5514
|
-
return { kind: "bytesNew", source: seed, type: BYTES_U8, loc };
|
|
5515
|
-
}
|
|
5516
|
-
}
|
|
5517
|
-
else {
|
|
5518
|
-
const srcIr = lowerer.mapTypeOf(lowerer.typeOf(argNode));
|
|
5519
|
-
if (srcIr?.kind === "string") {
|
|
5520
|
-
const encNode = args[1];
|
|
5521
|
-
const encName = encNode ? bufEncoding(lowerer, "Buffer.from", encNode) : "utf8";
|
|
5522
|
-
const s = lowerer.lowerExprExpecting(argNode, STRING);
|
|
5523
|
-
const enc = { kind: "strLit", value: encName, type: STRING, loc };
|
|
5524
|
-
return { kind: "libCall", fn: "buffer.fromStr", args: [s, enc], type: BYTES_U8, loc };
|
|
5525
|
-
}
|
|
5526
|
-
if (args.length === 1 && srcIr?.kind === "bytes" && srcIr.elem === "u8") {
|
|
5527
|
-
return { kind: "bytesNew", source: lowerer.lowerExpr(argNode), type: BYTES_U8, loc };
|
|
5528
|
-
}
|
|
5529
|
-
if (args.length === 1 && srcIr?.kind === "array" && srcIr.elem.kind === "f64") {
|
|
5530
|
-
return { kind: "bytesNew", source: lowerer.lowerExpr(argNode), type: BYTES_U8, loc };
|
|
5531
|
-
}
|
|
5532
|
-
}
|
|
5533
|
-
}
|
|
5534
|
-
lowerer.noLowering("Buffer.from with this argument shape", call, "supported: Buffer.from(string, encoding?) with a literal encoding, Buffer.from(u8Array) — a copy — " +
|
|
5535
|
-
"Buffer.from(number[]), or Buffer.from(x.buffer, byteOffset?, length?) — a view sharing x's " +
|
|
5536
|
-
"storage (no free-standing ArrayBuffer value exists; narrow unions first)");
|
|
5537
|
-
}
|
|
5538
|
-
if (member === "alloc" || member === "allocUnsafe") {
|
|
5539
|
-
const maxArgs = member === "alloc" ? 3 : 1;
|
|
5540
|
-
if (args.length < 1 || args.length > maxArgs || args.some(ts.isSpreadElement)) {
|
|
5541
|
-
lowerer.noLowering(`Buffer.${member} with ${args.length} arguments`, call);
|
|
5542
|
-
}
|
|
5543
|
-
// allocUnsafe's contents are UNSPECIFIED in Node (uninitialized pool
|
|
5544
|
-
// memory); a zero-filled buffer is a valid instance of unspecified,
|
|
5545
|
-
// and the deterministic choice — a program observing the difference
|
|
5546
|
-
// is depending on garbage.
|
|
5547
|
-
const size = lowerer.lowerExprExpecting(args[0], F64);
|
|
5548
|
-
const fresh = { kind: "bytesNew", source: size, type: BYTES_U8, loc };
|
|
5549
|
-
if (args.length === 1)
|
|
5550
|
-
return fresh;
|
|
5551
|
-
// alloc(size, fill, encoding?): the fill semantics ARE fill()'s —
|
|
5552
|
-
// the fresh buffer is the receiver of a whole-range fill.
|
|
5553
|
-
const fillT = lowerer.mapTypeOf(lowerer.typeOf(args[1]));
|
|
5554
|
-
if (fillT?.kind === "string") {
|
|
5555
|
-
const encName = args[2] ? bufEncoding(lowerer, "Buffer.alloc", args[2]) : "utf8";
|
|
5556
|
-
const s = lowerer.lowerExprExpecting(args[1], STRING);
|
|
5557
|
-
const enc = { kind: "strLit", value: encName, type: STRING, loc };
|
|
5558
|
-
return { kind: "bytesIntrinsic", method: "fillStr", receiver: fresh, args: [s, enc], type: BYTES_U8, loc };
|
|
5559
|
-
}
|
|
5560
|
-
if (args.length > 2) {
|
|
5561
|
-
lowerer.noLowering("Buffer.alloc with an encoding on a non-string fill", args[2]);
|
|
5562
|
-
}
|
|
5563
|
-
if (fillT?.kind === "f64") {
|
|
5564
|
-
const v = lowerer.lowerExprExpecting(args[1], F64);
|
|
5565
|
-
return { kind: "bytesIntrinsic", method: "fillNum", receiver: fresh, args: [v], type: BYTES_U8, loc };
|
|
5566
|
-
}
|
|
5567
|
-
if (fillT?.kind === "bytes" && fillT.elem === "u8") {
|
|
5568
|
-
const pattern = lowerer.lowerExpr(args[1]);
|
|
5569
|
-
return { kind: "bytesIntrinsic", method: "fill", receiver: fresh, args: [pattern], type: BYTES_U8, loc };
|
|
5570
|
-
}
|
|
5571
|
-
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");
|
|
5572
|
-
}
|
|
5573
|
-
if (member === "concat") {
|
|
5574
|
-
if (args.length < 1 || args.length > 2 || args.some(ts.isSpreadElement)) {
|
|
5575
|
-
lowerer.noLowering(`Buffer.concat with ${args.length} arguments`, call);
|
|
5576
|
-
}
|
|
5577
|
-
const argNode = args[0];
|
|
5578
|
-
let list;
|
|
5579
|
-
if (ts.isArrayLiteralExpression(argNode) && !argNode.elements.some(ts.isSpreadElement)) {
|
|
5580
|
-
const elems = argNode.elements.map((el) => lowerer.lowerExprExpecting(el, BYTES_U8));
|
|
5581
|
-
list = { kind: "arrayLit", elems, type: arrayOf(BYTES_U8), loc };
|
|
5582
|
-
}
|
|
5583
|
-
else {
|
|
5584
|
-
list = lowerer.lowerExpr(argNode);
|
|
5585
|
-
if (!typeEquals(list.type, arrayOf(BYTES_U8))) {
|
|
5586
|
-
lowerer.noLowering(`Buffer.concat of '${lowerer.fmt(list.type)}' values`, argNode, "one Uint8Array[]/Buffer[] value (or literal) is the lowered list shape");
|
|
5587
|
-
}
|
|
5588
|
-
}
|
|
5589
|
-
if (args.length === 2) {
|
|
5590
|
-
// The totalLength form truncates or zero-pads (and THROWS Node's
|
|
5591
|
-
// 'length' RangeError on bad totals).
|
|
5592
|
-
const total = lowerer.lowerExprExpecting(args[1], F64);
|
|
5593
|
-
return { kind: "libCall", fn: "buffer.concatLen", args: [list, total], type: BYTES_U8, loc };
|
|
5594
|
-
}
|
|
5595
|
-
return { kind: "libCall", fn: "buffer.concat", args: [list], type: BYTES_U8, loc };
|
|
5596
|
-
}
|
|
5597
|
-
if (member === "compare") {
|
|
5598
|
-
// The static form: Buffer.compare(a, b) IS a.compare(b).
|
|
5599
|
-
if (args.length !== 2 || args.some(ts.isSpreadElement)) {
|
|
5600
|
-
lowerer.noLowering(`Buffer.compare with ${args.length} arguments`, call);
|
|
5601
|
-
}
|
|
5602
|
-
const sides = args.map((a) => lowerer.lowerExpr(a));
|
|
5603
|
-
if (sides.every((v) => v.type.kind === "bytes" && v.type.elem === "u8")) {
|
|
5604
|
-
return { kind: "bytesIntrinsic", method: "compareBuf", receiver: sides[0], args: [sides[1]], type: F64, loc };
|
|
5605
|
-
}
|
|
5606
|
-
// A side that is not statically bytes (the invalid-input probes,
|
|
5607
|
-
// untyped JS helpers): Node's "buf1"/"buf2" argument ladder runs
|
|
5608
|
-
// at runtime — a well-typed dyn still compares.
|
|
5609
|
-
const dyns = sides.map((v, i) => {
|
|
5610
|
-
if (v.type.kind === "dyn")
|
|
5611
|
-
return v;
|
|
5612
|
-
if (v.kind === "unitLit" || lowerer.dynConvertible(v.type)) {
|
|
5613
|
-
return { kind: "dynFrom", value: v, type: DYN, loc: v.loc };
|
|
5614
|
-
}
|
|
5615
|
-
lowerer.noLowering(`Buffer.compare of '${lowerer.fmt(v.type)}' values`, args[i], "Buffer/Uint8Array values compare (narrow unions first)");
|
|
5616
|
-
});
|
|
5617
|
-
return { kind: "libCall", fn: "buffer.compareChk", args: dyns, type: F64, loc };
|
|
5618
|
-
}
|
|
5619
|
-
if (member === "isBuffer") {
|
|
5620
|
-
// The type-predicate narrowing test. Lowered where it DECIDES
|
|
5621
|
-
// something: a union-typed argument with a Buffer arm becomes a
|
|
5622
|
-
// runtime tag test (tsc's narrowing then types the branches, the
|
|
5623
|
-
// discriminated-union machinery). Statically-decided arguments are
|
|
5624
|
-
// fenced — the answer is a constant, and the operand's evaluation
|
|
5625
|
-
// would have to be discarded.
|
|
5626
|
-
if (args.length === 1 && !ts.isSpreadElement(args[0])) {
|
|
5627
|
-
const v = lowerer.lowerExpr(args[0]);
|
|
5628
|
-
if (v.type.kind === "union") {
|
|
5629
|
-
const def = lowerer.unions.get(v.type.unionId);
|
|
5630
|
-
const tag = def ? def.arms.findIndex((a) => a.kind === "bytes" && a.elem === "u8") : -1;
|
|
5631
|
-
if (tag >= 0) {
|
|
5632
|
-
return { kind: "unionIsTag", unionId: v.type.unionId, tag, negated: false, value: v, type: BOOL, loc };
|
|
5633
|
-
}
|
|
5634
|
-
}
|
|
5635
|
-
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");
|
|
5636
|
-
}
|
|
5637
|
-
lowerer.noLowering("Buffer.isBuffer with this argument shape", call);
|
|
5638
|
-
}
|
|
5639
|
-
if (member === "byteLength") {
|
|
5640
|
-
// Buffer.byteLength(string, enc?) — the UTF-16-aware per-encoding
|
|
5641
|
-
// count — or of a typed array/Buffer (its byte length, a property
|
|
5642
|
-
// read at heart).
|
|
5643
|
-
if (args.length >= 1 && args.length <= 2 && !ts.isSpreadElement(args[0])) {
|
|
5644
|
-
const srcIr = lowerer.mapTypeOf(lowerer.typeOf(args[0]));
|
|
5645
|
-
if (srcIr?.kind === "string") {
|
|
5646
|
-
const encName = args[1] ? bufEncoding(lowerer, "Buffer.byteLength", args[1]) : "utf8";
|
|
5647
|
-
const s = lowerer.lowerExprExpecting(args[0], STRING);
|
|
5648
|
-
const enc = { kind: "strLit", value: encName, type: STRING, loc };
|
|
5649
|
-
return { kind: "libCall", fn: "buffer.byteLenStr", args: [s, enc], type: F64, loc };
|
|
5650
|
-
}
|
|
5651
|
-
if (srcIr?.kind === "bytes" && args.length === 1) {
|
|
5652
|
-
const receiver = lowerer.lowerExpr(args[0]);
|
|
5653
|
-
return { kind: "bytesIntrinsic", method: "byteLength", receiver, args: [], type: F64, loc };
|
|
5654
|
-
}
|
|
5655
|
-
}
|
|
5656
|
-
lowerer.noLowering("Buffer.byteLength with this argument shape", call, "supported: Buffer.byteLength(string, literalEncoding?) or Buffer.byteLength(typedArray)");
|
|
5657
|
-
}
|
|
5658
|
-
if (member === "isEncoding") {
|
|
5659
|
-
// The runtime alias-set test — a plain bool over any string value
|
|
5660
|
-
// (Node's case-insensitive normalizeEncoding check).
|
|
5661
|
-
if (args.length === 1 && !ts.isSpreadElement(args[0]) && lowerer.mapTypeOf(lowerer.typeOf(args[0]))?.kind === "string") {
|
|
5662
|
-
const s = lowerer.lowerExprExpecting(args[0], STRING);
|
|
5663
|
-
return { kind: "libCall", fn: "buffer.isEncoding", args: [s], type: BOOL, loc };
|
|
5664
|
-
}
|
|
5665
|
-
lowerer.noLowering("Buffer.isEncoding with this argument shape", call, "one string-typed argument is the lowered form");
|
|
5666
|
-
}
|
|
5667
|
-
return null; // of, copyBytesFrom, ... → the SC2020 member fence
|
|
5668
|
-
}
|
|
5669
4341
|
/** `Object.keys/values/entries` over an INDEX-SIGNATURE (overflow-carrying)
|
|
5670
4342
|
* record shape: declared fields answer first from the compile-time field
|
|
5671
4343
|
* list (declaration order, undefined-valued fields skipped — exactly the
|