@scriptc/compiler 0.0.22 → 0.0.24
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 +139 -14
- package/dist/backend/cc.d.ts +13 -0
- package/dist/backend/cc.js +11 -0
- package/dist/backend/cc.js.map +1 -1
- package/dist/backend/emission/emit-async.d.ts +4 -0
- package/dist/backend/emission/emit-async.js +34 -2
- package/dist/backend/emission/emit-async.js.map +1 -1
- package/dist/backend/emission/emit-exprs.d.ts +10 -0
- package/dist/backend/emission/emit-exprs.js +272 -22
- package/dist/backend/emission/emit-exprs.js.map +1 -1
- package/dist/backend/emission/emit-shapes.js +1 -0
- package/dist/backend/emission/emit-shapes.js.map +1 -1
- package/dist/backend/emission/emit-stmts.js +36 -9
- package/dist/backend/emission/emit-stmts.js.map +1 -1
- package/dist/backend/emission/emit-types.js +14 -1
- package/dist/backend/emission/emit-types.js.map +1 -1
- package/dist/backend/emission/emitter.d.ts +19 -1
- package/dist/backend/emission/emitter.js +81 -2
- package/dist/backend/emission/emitter.js.map +1 -1
- package/dist/backend/llvm/emitter.js +578 -36
- package/dist/backend/llvm/emitter.js.map +1 -1
- package/dist/backend/llvm/shapes.js +12 -3
- package/dist/backend/llvm/shapes.js.map +1 -1
- package/dist/backend/mangle.d.ts +3 -0
- package/dist/backend/mangle.js +5 -0
- package/dist/backend/mangle.js.map +1 -1
- package/dist/compat/fetch-profile.d.ts +10 -0
- package/dist/compat/fetch-profile.js +55 -6
- package/dist/compat/fetch-profile.js.map +1 -1
- package/dist/coverage/surface-manifest.js +16 -1
- package/dist/coverage/surface-manifest.js.map +1 -1
- package/dist/diagnostics/diagnostic.d.ts +10 -0
- package/dist/diagnostics/diagnostic.js +27 -1
- package/dist/diagnostics/diagnostic.js.map +1 -1
- package/dist/frontend/lowering/lower-builtins.d.ts +17 -16
- package/dist/frontend/lowering/lower-builtins.js +817 -117
- package/dist/frontend/lowering/lower-builtins.js.map +1 -1
- package/dist/frontend/lowering/lower-calls.js +3 -1
- package/dist/frontend/lowering/lower-calls.js.map +1 -1
- package/dist/frontend/lowering/lower-classes.js +28 -8
- package/dist/frontend/lowering/lower-classes.js.map +1 -1
- package/dist/frontend/lowering/lower-containers.d.ts +2 -1
- package/dist/frontend/lowering/lower-containers.js +99 -32
- package/dist/frontend/lowering/lower-containers.js.map +1 -1
- package/dist/frontend/lowering/lower-exprs.d.ts +1 -1
- package/dist/frontend/lowering/lower-exprs.js +78 -6
- package/dist/frontend/lowering/lower-exprs.js.map +1 -1
- package/dist/frontend/lowering/lower-generators.js +7 -1
- package/dist/frontend/lowering/lower-generators.js.map +1 -1
- package/dist/frontend/lowering/lower-inspect.js +59 -1
- package/dist/frontend/lowering/lower-inspect.js.map +1 -1
- package/dist/frontend/lowering/lower-island.d.ts +21 -0
- package/dist/frontend/lowering/lower-island.js +284 -10
- package/dist/frontend/lowering/lower-island.js.map +1 -1
- package/dist/frontend/lowering/lower-server.d.ts +5 -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 +6 -2
- package/dist/frontend/lowering/lower-stmts.js +53 -14
- package/dist/frontend/lowering/lower-stmts.js.map +1 -1
- package/dist/frontend/lowering/lowerer.d.ts +3 -2
- package/dist/frontend/lowering/lowerer.js +17 -8
- package/dist/frontend/lowering/lowerer.js.map +1 -1
- package/dist/frontend/lowering/surfaces.js +108 -25
- package/dist/frontend/lowering/surfaces.js.map +1 -1
- package/dist/frontend/program.d.ts +4 -4
- package/dist/frontend/program.js +264 -46
- package/dist/frontend/program.js.map +1 -1
- package/dist/frontend/resolve.d.ts +10 -0
- package/dist/frontend/resolve.js +37 -0
- package/dist/frontend/resolve.js.map +1 -1
- package/dist/frontend/ts7/enums.d.ts +9 -0
- package/dist/frontend/ts7/enums.js +8 -6
- package/dist/frontend/ts7/enums.js.map +1 -1
- package/dist/frontend/ts7/program.js +4 -1
- package/dist/frontend/ts7/program.js.map +1 -1
- package/dist/frontend/types.d.ts +7 -4
- package/dist/frontend/types.js +103 -8
- package/dist/frontend/types.js.map +1 -1
- package/dist/index.js +8 -1
- package/dist/index.js.map +1 -1
- package/dist/ir/integer-loops.d.ts +23 -0
- package/dist/ir/integer-loops.js +82 -0
- package/dist/ir/integer-loops.js.map +1 -0
- package/dist/ir/nodes.d.ts +104 -28
- package/dist/ir/nodes.js +89 -1
- package/dist/ir/nodes.js.map +1 -1
- package/dist/ir/validate.js +136 -25
- package/dist/ir/validate.js.map +1 -1
- package/dist/library/fence-eval.js +4 -3
- package/dist/library/fence-eval.js.map +1 -1
- package/package.json +2 -2
- package/surface-manifest.json +186 -29
|
@@ -13,12 +13,72 @@ import { probeNodeRequireRefusal } from "../npm.js";
|
|
|
13
13
|
import { isNpmStaticPackage } from "../npm-static.js";
|
|
14
14
|
import { invalidJsonModuleDiag, requiresDynamicImportDiag } from "../../diagnostics/diagnostic.js";
|
|
15
15
|
import { builtinModuleFnOf, FS_READDIR_DOCUMENTED_OPTIONS, FS_WATCH_DOCUMENTED_OPTIONS, FS_WRITE_FILE_DOCUMENTED_OPTIONS, QS_PARSE_DOCUMENTED_OPTIONS, QS_STRINGIFY_DOCUMENTED_OPTIONS, READLINE_DOCUMENTED_OPTIONS, builtinConstLit, fenceOrDropOptionKey, isChildSurfaceMember, } from "./surfaces.js";
|
|
16
|
-
import { conditionalSpreadOf, lowerDynObjectLiteral } from "./lower-exprs.js";
|
|
16
|
+
import { conditionalSpreadOf, droppableStatic, lowerDynObjectLiteral } from "./lower-exprs.js";
|
|
17
17
|
import { HTTP2_CONSTANTS } from "./http2-constants.js";
|
|
18
18
|
import { CRYPTO_CIPHERS, CRYPTO_CONSTANTS, CRYPTO_CURVES, CRYPTO_HASHES } from "./crypto-tables.js";
|
|
19
19
|
import { timerStyleCallback } from "./lower-calls.js";
|
|
20
|
-
import { registerHttpClientFnBinding } from "./lower-server.js";
|
|
21
|
-
import { BOOL, BYTES_U8, CHILD_T, CHILDSTREAM_T, DYN, F64, FSWATCHER_T, PROCSTREAM_T, JSVAL, NULL_T, SEARCH_PARAMS_T, SPAWNRES_T, STRING, UNDEFINED_T, VOID, arrayOf, canBoxFuncIntoDyn, canConvertToDyn, funcOf, isUnitType, typeEquals, typeKey } from "../../ir/nodes.js";
|
|
20
|
+
import { registerHttpClientFnBinding, voidizedCallback } from "./lower-server.js";
|
|
21
|
+
import { BOOL, BYTES_U8, CHILD_T, CHILDSTREAM_T, DYN, F64, FILEHANDLE_T, FSWATCHER_T, PROCSTREAM_T, JSVAL, NULL_T, SEARCH_PARAMS_T, SPAWNRES_T, STRING, UNDEFINED_T, VOID, arrayOf, canBoxFuncIntoDyn, canConvertToDyn, funcOf, isUnitType, typeEquals, typeKey } from "../../ir/nodes.js";
|
|
22
|
+
/** Lower an optional builtin argument whose checker type is statically
|
|
23
|
+
* undefined/void. The returned expression exists only to preserve effects;
|
|
24
|
+
* callers replace its value with the API default. */
|
|
25
|
+
function lowerStaticallyUndefinedBuiltinArg(L, node) {
|
|
26
|
+
const peel = (value) => {
|
|
27
|
+
let expr = value;
|
|
28
|
+
while (ts.isParenthesizedExpression(expr) ||
|
|
29
|
+
ts.isAsExpression(expr) ||
|
|
30
|
+
ts.isTypeAssertion(expr) ||
|
|
31
|
+
ts.isSatisfiesExpression(expr)) {
|
|
32
|
+
expr = expr.expression;
|
|
33
|
+
}
|
|
34
|
+
return expr;
|
|
35
|
+
};
|
|
36
|
+
let expr = node;
|
|
37
|
+
while (ts.isParenthesizedExpression(expr))
|
|
38
|
+
expr = expr.expression;
|
|
39
|
+
if ((L.typeOf(expr).flags & (ts.TypeFlags.Undefined | ts.TypeFlags.Void)) === 0)
|
|
40
|
+
return null;
|
|
41
|
+
expr = peel(expr);
|
|
42
|
+
let sawVoid = false;
|
|
43
|
+
while (ts.isVoidExpression(expr)) {
|
|
44
|
+
sawVoid = true;
|
|
45
|
+
expr = peel(expr.expression);
|
|
46
|
+
}
|
|
47
|
+
return L.lowerExpr(sawVoid ? expr : node);
|
|
48
|
+
}
|
|
49
|
+
function defaultAfterUndefined(value, dflt) {
|
|
50
|
+
if (droppableStatic(value))
|
|
51
|
+
return dflt;
|
|
52
|
+
return {
|
|
53
|
+
kind: "seqExpr",
|
|
54
|
+
stmts: [{ kind: "exprStmt", expr: value, loc: value.loc }],
|
|
55
|
+
result: dflt,
|
|
56
|
+
type: dflt.type,
|
|
57
|
+
loc: value.loc,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
/** Complete an optional builtin argument. Statically undefined spellings
|
|
61
|
+
* preserve their effects and select the default; a runtime unit-armed union
|
|
62
|
+
* uses nullish selection when every non-unit arm is the expected type. */
|
|
63
|
+
function lowerBuiltinOptionalDefault(L, node, expected, dflt, nullIsDefault = false) {
|
|
64
|
+
const undefinedArg = lowerStaticallyUndefinedBuiltinArg(L, node);
|
|
65
|
+
if (undefinedArg)
|
|
66
|
+
return defaultAfterUndefined(undefinedArg, dflt);
|
|
67
|
+
if (nullIsDefault && (L.typeOf(node).flags & ts.TypeFlags.Null) !== 0) {
|
|
68
|
+
return defaultAfterUndefined(L.lowerExpr(node), dflt);
|
|
69
|
+
}
|
|
70
|
+
const value = L.lowerExpr(node);
|
|
71
|
+
if (value.type.kind === "union") {
|
|
72
|
+
const def = L.unions.get(value.type.unionId);
|
|
73
|
+
const allowed = def?.arms.every((arm) => typeEquals(arm, expected) ||
|
|
74
|
+
arm.kind === "undefinedT" ||
|
|
75
|
+
(nullIsDefault && arm.kind === "nullT"));
|
|
76
|
+
if (allowed && def.arms.some((arm) => isUnitType(arm))) {
|
|
77
|
+
return { kind: "nullish", left: value, right: dflt, type: expected, loc: value.loc };
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return L.coerceInto(node, value, expected);
|
|
81
|
+
}
|
|
22
82
|
/** Resolves an identifier to a supported builtin-module IMPORT BINDING:
|
|
23
83
|
* a named import (through its alias, so `import { join as j }` matches)
|
|
24
84
|
* whose declaration is an ImportSpecifier under a supported builtin
|
|
@@ -785,6 +845,191 @@ export function lowerBuiltinModuleCall(L, expr, bi, fn, loc) {
|
|
|
785
845
|
if (bi.module === "fs" && bi.member === "watch") {
|
|
786
846
|
return lowerFsWatchCall(L, expr, loc);
|
|
787
847
|
}
|
|
848
|
+
// fs/promises.open(path[, flags[, mode]]) — string flags and numeric
|
|
849
|
+
// creation mode, with Node's "r"/0o666 defaults. The runtime wraps
|
|
850
|
+
// the descriptor in a shared FileHandle and settles/rejects exactly
|
|
851
|
+
// like the existing fs/promises operations.
|
|
852
|
+
if (bi.module === "fs/promises" && bi.member === "open") {
|
|
853
|
+
if (expr.arguments.length < 1 || expr.arguments.length > 3 || expr.arguments.some(ts.isSpreadElement)) {
|
|
854
|
+
L.noLowering(`fs.promises.open with ${expr.arguments.length} arguments`, expr, "use open(path[, stringFlags[, numericMode]])");
|
|
855
|
+
}
|
|
856
|
+
const path = L.lowerExprExpecting(expr.arguments[0], STRING);
|
|
857
|
+
const defaultFlags = { kind: "strLit", value: "r", type: STRING, loc };
|
|
858
|
+
const defaultMode = { kind: "numLit", value: 0o666, type: F64, loc };
|
|
859
|
+
const flags = expr.arguments[1]
|
|
860
|
+
? lowerBuiltinOptionalDefault(L, expr.arguments[1], STRING, defaultFlags)
|
|
861
|
+
: defaultFlags;
|
|
862
|
+
const mode = expr.arguments[2]
|
|
863
|
+
? lowerBuiltinOptionalDefault(L, expr.arguments[2], F64, defaultMode)
|
|
864
|
+
: defaultMode;
|
|
865
|
+
return {
|
|
866
|
+
kind: "libCall",
|
|
867
|
+
fn: "fsp.open",
|
|
868
|
+
args: [path, flags, mode],
|
|
869
|
+
type: { kind: "promise", inner: FILEHANDLE_T },
|
|
870
|
+
loc,
|
|
871
|
+
};
|
|
872
|
+
}
|
|
873
|
+
// fs.rename(oldPath, newPath, callback): the callback is a
|
|
874
|
+
// program-shaped closure (zero parameters are valid; the ordinary
|
|
875
|
+
// form receives NodeJS.ErrnoException | null). Keep it typed here so
|
|
876
|
+
// the backends can emit the same union-building adapter used by
|
|
877
|
+
// dns.lookup instead of losing the Error arm through a dyn boundary.
|
|
878
|
+
if (bi.module === "fs" && bi.member === "rename") {
|
|
879
|
+
if (expr.arguments.length !== 3 || expr.arguments.some(ts.isSpreadElement)) {
|
|
880
|
+
L.noLowering(`rename with ${expr.arguments.length} argument${expr.arguments.length === 1 ? "" : "s"}`, expr, "the supported form is rename(oldPath, newPath, callback)");
|
|
881
|
+
}
|
|
882
|
+
const oldPath = L.lowerExprExpecting(expr.arguments[0], STRING);
|
|
883
|
+
const newPath = L.lowerExprExpecting(expr.arguments[1], STRING);
|
|
884
|
+
let callback = L.lowerExpr(expr.arguments[2]);
|
|
885
|
+
// JS/checkJs callback values may arrive as checked-dynamic callables.
|
|
886
|
+
// Adapt them to the one error-first slot; the runtime passes a dyn
|
|
887
|
+
// Error on failure and null on success through the emitted thunk.
|
|
888
|
+
if (callback.type.kind === "dyn") {
|
|
889
|
+
callback = {
|
|
890
|
+
kind: "dynCheck",
|
|
891
|
+
value: callback,
|
|
892
|
+
type: funcOf([DYN], VOID),
|
|
893
|
+
loc: locOf(expr.arguments[2]),
|
|
894
|
+
};
|
|
895
|
+
}
|
|
896
|
+
let callbackOk = callback.type.kind === "func" && callback.type.params.length <= 1;
|
|
897
|
+
if (callbackOk && callback.type.kind === "func" && callback.type.params.length === 1) {
|
|
898
|
+
const param = callback.type.params[0];
|
|
899
|
+
if (param.kind === "dyn") {
|
|
900
|
+
callbackOk = true;
|
|
901
|
+
}
|
|
902
|
+
else if (param.kind === "union") {
|
|
903
|
+
const def = L.unions.get(param.unionId);
|
|
904
|
+
callbackOk = !!def &&
|
|
905
|
+
def.arms.some((a) => a.kind === "nullT") &&
|
|
906
|
+
def.arms.some((a) => a.kind === "object" && a.className === "%Error") &&
|
|
907
|
+
def.arms.every((a) => a.kind === "nullT" || a.kind === "undefinedT" ||
|
|
908
|
+
(a.kind === "object" && a.className === "%Error"));
|
|
909
|
+
}
|
|
910
|
+
else {
|
|
911
|
+
callbackOk = false;
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
if (!callbackOk) {
|
|
915
|
+
L.unsupported("SC1090", expr.arguments[2], "fs.rename callbacks must accept at most one Error | null parameter");
|
|
916
|
+
}
|
|
917
|
+
// TypeScript deliberately permits a value-returning function where a
|
|
918
|
+
// void callback is expected; Node ignores that value. Normalize the
|
|
919
|
+
// closure to the runtime's void callback ABI after validating its
|
|
920
|
+
// error-first parameter shape.
|
|
921
|
+
callback = voidizedCallback(L, callback, locOf(expr.arguments[2]));
|
|
922
|
+
return { kind: "libCall", fn: "fs.renameCb", args: [oldPath, newPath, callback], type: VOID, loc };
|
|
923
|
+
}
|
|
924
|
+
// fs.readSync(fd, buffer, offset, length[, position]) — normalize the
|
|
925
|
+
// current-offset forms to Node/libuv's -1 sentinel so the IR and native
|
|
926
|
+
// ABI stay fixed-width. A real numeric position is a positioned read
|
|
927
|
+
// and therefore must not advance the descriptor. Literal null is the
|
|
928
|
+
// documented current-offset spelling; richer nullable expressions must
|
|
929
|
+
// narrow first so no effectful evaluation is silently discarded.
|
|
930
|
+
if (bi.module === "fs" && bi.member === "readSync") {
|
|
931
|
+
const supported = "use readSync(fd, buffer, offset, length[, position]) with a numeric position or literal null; options objects and bigint positions have no lowering";
|
|
932
|
+
if ((expr.arguments.length !== 4 && expr.arguments.length !== 5) ||
|
|
933
|
+
expr.arguments.some(ts.isSpreadElement)) {
|
|
934
|
+
L.noLowering(`readSync with ${expr.arguments.length} argument${expr.arguments.length === 1 ? "" : "s"}`, expr, supported);
|
|
935
|
+
}
|
|
936
|
+
const args = [
|
|
937
|
+
L.lowerExprExpecting(expr.arguments[0], F64),
|
|
938
|
+
L.lowerExprExpecting(expr.arguments[1], BYTES_U8),
|
|
939
|
+
L.lowerExprExpecting(expr.arguments[2], F64),
|
|
940
|
+
L.lowerExprExpecting(expr.arguments[3], F64),
|
|
941
|
+
];
|
|
942
|
+
const positionNode = expr.arguments[4];
|
|
943
|
+
let positionValueNode = positionNode;
|
|
944
|
+
while (positionValueNode && ts.isParenthesizedExpression(positionValueNode)) {
|
|
945
|
+
positionValueNode = positionValueNode.expression;
|
|
946
|
+
}
|
|
947
|
+
if (positionNode === undefined || positionValueNode.kind === ts.SyntaxKind.NullKeyword) {
|
|
948
|
+
args.push({ kind: "numLit", value: -1, type: F64, loc });
|
|
949
|
+
}
|
|
950
|
+
else {
|
|
951
|
+
const positionType = L.mapTypeOf(L.typeOf(positionNode));
|
|
952
|
+
if (positionType?.kind !== "f64") {
|
|
953
|
+
L.noLowering(`readSync with a '${positionType ? L.fmt(positionType) : L.checker.typeToString(L.typeOf(positionNode))}' position`, positionNode, supported);
|
|
954
|
+
}
|
|
955
|
+
args.push(L.lowerExprExpecting(positionNode, F64));
|
|
956
|
+
}
|
|
957
|
+
return { kind: "libCall", fn: "fs.readSync", args, type: F64, loc };
|
|
958
|
+
}
|
|
959
|
+
// fs.writeSync has two static families. Buffer writes use the classic
|
|
960
|
+
// (fd, buffer, offset, length[, position]) shape; string writes accept
|
|
961
|
+
// (fd, string[, position[, "utf8"]]). The runtime uses -1 for the
|
|
962
|
+
// current-offset forms. Node treats a negative, fractional, non-finite,
|
|
963
|
+
// or over-MAX_SAFE numeric write position like null rather than throwing;
|
|
964
|
+
// preserve the value here so the runtime can make that dispatch.
|
|
965
|
+
if (bi.module === "fs" && bi.member === "writeSync") {
|
|
966
|
+
const supported = 'use writeSync(fd, buffer, offset, length[, position]) or writeSync(fd, string[, position[, "utf8"]]); options objects, bigint positions, and other encodings have no lowering';
|
|
967
|
+
if (expr.arguments.some(ts.isSpreadElement) || expr.arguments.length < 2) {
|
|
968
|
+
L.noLowering(`writeSync with ${expr.arguments.length} argument${expr.arguments.length === 1 ? "" : "s"}`, expr, supported);
|
|
969
|
+
}
|
|
970
|
+
const dataNode = expr.arguments[1];
|
|
971
|
+
const dataType = L.mapTypeOf(L.typeOf(dataNode));
|
|
972
|
+
const fd = L.lowerExprExpecting(expr.arguments[0], F64);
|
|
973
|
+
const position = (node) => {
|
|
974
|
+
let valueNode = node;
|
|
975
|
+
while (valueNode && ts.isParenthesizedExpression(valueNode))
|
|
976
|
+
valueNode = valueNode.expression;
|
|
977
|
+
if (node === undefined || valueNode.kind === ts.SyntaxKind.NullKeyword) {
|
|
978
|
+
return { kind: "numLit", value: -1, type: F64, loc };
|
|
979
|
+
}
|
|
980
|
+
const t = L.mapTypeOf(L.typeOf(node));
|
|
981
|
+
if (t?.kind !== "f64") {
|
|
982
|
+
L.noLowering(`writeSync with a '${t ? L.fmt(t) : L.checker.typeToString(L.typeOf(node))}' position`, node, supported);
|
|
983
|
+
}
|
|
984
|
+
return L.lowerExprExpecting(node, F64);
|
|
985
|
+
};
|
|
986
|
+
if (dataType?.kind === "bytes") {
|
|
987
|
+
if (dataType.elem !== "u8") {
|
|
988
|
+
L.noLowering(`writeSync of '${L.fmt(dataType)}' data`, dataNode, "byte writes take Uint8Array/Buffer data");
|
|
989
|
+
}
|
|
990
|
+
if (expr.arguments.length !== 4 && expr.arguments.length !== 5) {
|
|
991
|
+
L.noLowering(`writeSync of Buffer data with ${expr.arguments.length} arguments`, expr, supported);
|
|
992
|
+
}
|
|
993
|
+
return {
|
|
994
|
+
kind: "libCall",
|
|
995
|
+
fn: "fs.writeSync",
|
|
996
|
+
args: [
|
|
997
|
+
fd,
|
|
998
|
+
L.lowerExprExpecting(dataNode, BYTES_U8),
|
|
999
|
+
L.lowerExprExpecting(expr.arguments[2], F64),
|
|
1000
|
+
L.lowerExprExpecting(expr.arguments[3], F64),
|
|
1001
|
+
position(expr.arguments[4]),
|
|
1002
|
+
],
|
|
1003
|
+
type: F64,
|
|
1004
|
+
loc,
|
|
1005
|
+
};
|
|
1006
|
+
}
|
|
1007
|
+
if (dataType?.kind === "string") {
|
|
1008
|
+
if (expr.arguments.length > 4) {
|
|
1009
|
+
L.noLowering(`writeSync of string data with ${expr.arguments.length} arguments`, expr, supported);
|
|
1010
|
+
}
|
|
1011
|
+
const encNode = expr.arguments[3];
|
|
1012
|
+
let enc = { kind: "strLit", value: "utf8", type: STRING, loc };
|
|
1013
|
+
if (encNode !== undefined) {
|
|
1014
|
+
const encType = L.typeOf(encNode);
|
|
1015
|
+
if (!encType.isStringLiteralType() || (encType.value !== "utf8" && encType.value !== "utf-8")) {
|
|
1016
|
+
L.noLowering("writeSync with a non-utf8 encoding", encNode, supported);
|
|
1017
|
+
}
|
|
1018
|
+
// Even though utf8 is the runtime's only encoding, the argument
|
|
1019
|
+
// remains an ordinary JS argument: evaluate it in source order so
|
|
1020
|
+
// a call/getter whose type is the accepted literal cannot vanish.
|
|
1021
|
+
enc = L.lowerExprExpecting(encNode, STRING);
|
|
1022
|
+
}
|
|
1023
|
+
return {
|
|
1024
|
+
kind: "libCall",
|
|
1025
|
+
fn: "fs.writeStrSync",
|
|
1026
|
+
args: [fd, L.lowerExprExpecting(dataNode, STRING), position(expr.arguments[2]), enc],
|
|
1027
|
+
type: F64,
|
|
1028
|
+
loc,
|
|
1029
|
+
};
|
|
1030
|
+
}
|
|
1031
|
+
L.noLowering(`writeSync of '${dataType ? L.fmt(dataType) : L.checker.typeToString(L.typeOf(dataNode))}' data`, dataNode, supported);
|
|
1032
|
+
}
|
|
788
1033
|
if (bi.module === "child_process" &&
|
|
789
1034
|
(bi.member === "execFileSync" || bi.member === "execSync")) {
|
|
790
1035
|
return L.lowerExecSyncCall(expr, bi.member === "execSync", loc);
|
|
@@ -1151,14 +1396,54 @@ export function lowerBuiltinModuleCall(L, expr, bi, fn, loc) {
|
|
|
1151
1396
|
return { kind: "libCall", fn: "fs.writeFileSyncBytes", args: [path, data], type: VOID, loc };
|
|
1152
1397
|
}
|
|
1153
1398
|
}
|
|
1154
|
-
// writeFileSync(p, data, options)
|
|
1399
|
+
// writeFileSync(p, data, options) / fs.promises.writeFile(p, data,
|
|
1400
|
+
// options): the lowered options are a literal
|
|
1155
1401
|
// `{ mode?: <number>, encoding?: "utf8" }` — the mode is open(2)'s
|
|
1156
1402
|
// O_CREAT argument (creation only; an existing file keeps its
|
|
1157
1403
|
// permissions, exactly Node), and the encoding may only spell the
|
|
1158
|
-
// utf8 the runtime writes anyway. String data only —
|
|
1159
|
-
//
|
|
1160
|
-
|
|
1404
|
+
// utf8 the runtime writes anyway. String data only — Buffer options
|
|
1405
|
+
// remain outside the static surface.
|
|
1406
|
+
const syncWriteOptions = bi.module === "fs" && bi.member === "writeFileSync";
|
|
1407
|
+
const promiseWriteOptions = bi.module === "fs/promises" && bi.member === "writeFile";
|
|
1408
|
+
if ((syncWriteOptions || promiseWriteOptions) && expr.arguments.length === 3) {
|
|
1161
1409
|
const optsNode = expr.arguments[2];
|
|
1410
|
+
const operation = promiseWriteOptions ? "fs.promises.writeFile" : "writeFileSync";
|
|
1411
|
+
const plainFn = promiseWriteOptions ? "fsp.writeFile" : "fs.writeFileSync";
|
|
1412
|
+
const modeFn = promiseWriteOptions ? "fsp.writeFileMode" : "fs.writeFileModeSync";
|
|
1413
|
+
const resultType = promiseWriteOptions ? { kind: "promise", inner: VOID } : VOID;
|
|
1414
|
+
// The runtime needs only `mode`, but every source option value is an
|
|
1415
|
+
// ordinary JS expression. Stage path/data and then evaluate the option
|
|
1416
|
+
// values in object-literal order before issuing the write; otherwise a
|
|
1417
|
+
// call/getter statically typed as the accepted utf8 literal can vanish.
|
|
1418
|
+
const finishWrite = (optionValues) => {
|
|
1419
|
+
const path = L.lowerExprExpecting(expr.arguments[0], STRING);
|
|
1420
|
+
const data = L.lowerExprExpecting(expr.arguments[1], STRING);
|
|
1421
|
+
const pathLocal = L.declareHiddenLocal("%writePath", STRING);
|
|
1422
|
+
const dataLocal = L.declareHiddenLocal("%writeData", STRING);
|
|
1423
|
+
const ref = (local) => ({ kind: "varRef", localId: local.id, type: local.type, loc });
|
|
1424
|
+
const stmts = [
|
|
1425
|
+
{ kind: "varDecl", localId: pathLocal.id, init: path, loc: path.loc },
|
|
1426
|
+
{ kind: "varDecl", localId: dataLocal.id, init: data, loc: data.loc },
|
|
1427
|
+
];
|
|
1428
|
+
let mode = null;
|
|
1429
|
+
for (const option of optionValues) {
|
|
1430
|
+
if (option.kind === "effect") {
|
|
1431
|
+
stmts.push({ kind: "exprStmt", expr: option.value, loc: option.value.loc });
|
|
1432
|
+
continue;
|
|
1433
|
+
}
|
|
1434
|
+
const modeLocal = L.declareHiddenLocal("%writeMode", F64);
|
|
1435
|
+
stmts.push({ kind: "varDecl", localId: modeLocal.id, init: option.value, loc: option.value.loc });
|
|
1436
|
+
mode = ref(modeLocal);
|
|
1437
|
+
}
|
|
1438
|
+
const result = {
|
|
1439
|
+
kind: "libCall",
|
|
1440
|
+
fn: mode ? modeFn : plainFn,
|
|
1441
|
+
args: mode ? [ref(pathLocal), ref(dataLocal), mode] : [ref(pathLocal), ref(dataLocal)],
|
|
1442
|
+
type: resultType,
|
|
1443
|
+
loc,
|
|
1444
|
+
};
|
|
1445
|
+
return { kind: "seqExpr", stmts, result, type: resultType, loc };
|
|
1446
|
+
};
|
|
1162
1447
|
// The bare-encoding spelling — writeFileSync(p, data, "utf-8") — is
|
|
1163
1448
|
// the options record's encoding key alone: utf8 is what the runtime
|
|
1164
1449
|
// writes anyway, so string data takes the plain write. Any OTHER
|
|
@@ -1167,12 +1452,10 @@ export function lowerBuiltinModuleCall(L, expr, bi, fn, loc) {
|
|
|
1167
1452
|
const t = L.typeOf(optsNode);
|
|
1168
1453
|
if (t.isStringLiteralType() && (t.value === "utf8" || t.value === "utf-8") &&
|
|
1169
1454
|
L.mapTypeOf(L.typeOf(expr.arguments[1]))?.kind === "string") {
|
|
1170
|
-
|
|
1171
|
-
const data = L.lowerExprExpecting(expr.arguments[1], STRING);
|
|
1172
|
-
return { kind: "libCall", fn: "fs.writeFileSync", args: [path, data], type: VOID, loc };
|
|
1455
|
+
return finishWrite([{ kind: "effect", value: L.lowerExprExpecting(optsNode, STRING) }]);
|
|
1173
1456
|
}
|
|
1174
1457
|
}
|
|
1175
|
-
|
|
1458
|
+
const optionValues = [];
|
|
1176
1459
|
let ok = ts.isObjectLiteralExpression(optsNode);
|
|
1177
1460
|
if (ok) {
|
|
1178
1461
|
for (const p of optsNode.properties) {
|
|
@@ -1182,7 +1465,7 @@ export function lowerBuiltinModuleCall(L, expr, bi, fn, loc) {
|
|
|
1182
1465
|
break;
|
|
1183
1466
|
}
|
|
1184
1467
|
if (m.name === "mode") {
|
|
1185
|
-
|
|
1468
|
+
optionValues.push({ kind: "mode", value: L.lowerExprExpecting(m.value, F64) });
|
|
1186
1469
|
}
|
|
1187
1470
|
else if (m.name === "encoding") {
|
|
1188
1471
|
const t = L.typeOf(m.value);
|
|
@@ -1190,30 +1473,24 @@ export function lowerBuiltinModuleCall(L, expr, bi, fn, loc) {
|
|
|
1190
1473
|
ok = false;
|
|
1191
1474
|
break;
|
|
1192
1475
|
}
|
|
1476
|
+
optionValues.push({ kind: "effect", value: L.lowerExprExpecting(m.value, STRING) });
|
|
1193
1477
|
}
|
|
1194
1478
|
else if (m.name === "flag") {
|
|
1195
1479
|
// Documented, behavior-changing (open(2)'s disposition — 'a'
|
|
1196
1480
|
// IS appendFileSync), no lowering: fence by name.
|
|
1197
|
-
L.noLowering(
|
|
1481
|
+
L.noLowering(`${operation} with the flag option`, p, "the write truncates-or-creates (Node's default 'w'); other flags have no lowering");
|
|
1198
1482
|
}
|
|
1199
1483
|
else {
|
|
1200
1484
|
// The options-record stance: documented keys with no lowering
|
|
1201
1485
|
// fence by name; undocumented keys drop like Node.
|
|
1202
|
-
fenceOrDropOptionKey(L, p, m.name,
|
|
1486
|
+
fenceOrDropOptionKey(L, p, m.name, operation, FS_WRITE_FILE_DOCUMENTED_OPTIONS, 'the supported options are { mode: <number>, encoding: "utf8" }');
|
|
1203
1487
|
}
|
|
1204
1488
|
}
|
|
1205
1489
|
}
|
|
1206
1490
|
if (!ok || L.mapTypeOf(L.typeOf(expr.arguments[1]))?.kind !== "string") {
|
|
1207
|
-
L.noLowering(
|
|
1208
|
-
}
|
|
1209
|
-
const path = L.lowerExprExpecting(expr.arguments[0], STRING);
|
|
1210
|
-
const data = L.lowerExprExpecting(expr.arguments[1], STRING);
|
|
1211
|
-
if (modeNode === null) {
|
|
1212
|
-
// encoding-only options change nothing: the plain write.
|
|
1213
|
-
return { kind: "libCall", fn: "fs.writeFileSync", args: [path, data], type: VOID, loc };
|
|
1491
|
+
L.noLowering(`${operation} with 3 arguments`, optsNode, 'the supported options are { mode: <number>, encoding: "utf8" } over string data');
|
|
1214
1492
|
}
|
|
1215
|
-
|
|
1216
|
-
return { kind: "libCall", fn: "fs.writeFileModeSync", args: [path, data, mode], type: VOID, loc };
|
|
1493
|
+
return finishWrite(optionValues);
|
|
1217
1494
|
}
|
|
1218
1495
|
// zlib takes Buffers; a string argument (the lib admits it) gets the
|
|
1219
1496
|
// wrap-it-first hint instead of a generic type mismatch.
|
|
@@ -3672,6 +3949,211 @@ function buildSpForEachFn(name, arity, fnRet, loc) {
|
|
|
3672
3949
|
loc,
|
|
3673
3950
|
};
|
|
3674
3951
|
}
|
|
3952
|
+
/** Calls on an fs/promises FileHandle. The promise-returning methods run
|
|
3953
|
+
* through the same synchronous descriptor primitives as fs.readSync /
|
|
3954
|
+
* writeSync, then settle so failures reject at await. read/write retain
|
|
3955
|
+
* the caller's buffer in the Node-shaped result record. */
|
|
3956
|
+
export function lowerFileHandleMethodCall(L, call, access) {
|
|
3957
|
+
if (call.questionDotToken || access.questionDotToken)
|
|
3958
|
+
return null;
|
|
3959
|
+
if (L.mapTypeOf(L.typeOf(access.expression))?.kind !== "fileHandle")
|
|
3960
|
+
return null;
|
|
3961
|
+
if (!L.isStdlibMember(access))
|
|
3962
|
+
return null;
|
|
3963
|
+
const name = access.name.text;
|
|
3964
|
+
const loc = locOf(call);
|
|
3965
|
+
const receiver = () => L.lowerExprExpecting(access.expression, FILEHANDLE_T);
|
|
3966
|
+
const promise = (inner) => ({ kind: "promise", inner });
|
|
3967
|
+
const bool = (value) => ({ kind: "boolLit", value, type: BOOL, loc });
|
|
3968
|
+
const num = (node, dflt) => {
|
|
3969
|
+
const defaultValue = { kind: "numLit", value: dflt, type: F64, loc };
|
|
3970
|
+
if (!node)
|
|
3971
|
+
return { value: defaultValue, defaulted: bool(true) };
|
|
3972
|
+
const undefinedArg = lowerStaticallyUndefinedBuiltinArg(L, node);
|
|
3973
|
+
if (undefinedArg) {
|
|
3974
|
+
return { value: defaultAfterUndefined(undefinedArg, defaultValue), defaulted: bool(true) };
|
|
3975
|
+
}
|
|
3976
|
+
if ((L.typeOf(node).flags & ts.TypeFlags.Null) !== 0) {
|
|
3977
|
+
return { value: defaultAfterUndefined(L.lowerExpr(node), defaultValue), defaulted: bool(true) };
|
|
3978
|
+
}
|
|
3979
|
+
const value = L.lowerExpr(node);
|
|
3980
|
+
if (value.type.kind === "union") {
|
|
3981
|
+
const def = L.unions.get(value.type.unionId);
|
|
3982
|
+
const units = def?.arms.filter(isUnitType) ?? [];
|
|
3983
|
+
if (units.length > 0 &&
|
|
3984
|
+
def?.arms.every((arm) => typeEquals(arm, F64) || isUnitType(arm))) {
|
|
3985
|
+
// The normalized number and the separate "length was omitted"
|
|
3986
|
+
// bit must observe one evaluation of a runtime optional union.
|
|
3987
|
+
// Bind it in the numeric argument; the later bool argument reads
|
|
3988
|
+
// the same hidden local (libCall arguments evaluate left-to-right).
|
|
3989
|
+
const saved = L.declareHiddenLocal("%fhOptNum", value.type);
|
|
3990
|
+
const savedRef = () => ({
|
|
3991
|
+
kind: "varRef", localId: saved.id, type: value.type, loc: value.loc,
|
|
3992
|
+
});
|
|
3993
|
+
let defaulted = {
|
|
3994
|
+
kind: "unionIsTag", unionId: value.type.unionId,
|
|
3995
|
+
tag: L.armTag(value.type.unionId, units[0]), negated: false,
|
|
3996
|
+
value: savedRef(), type: BOOL, loc: value.loc,
|
|
3997
|
+
};
|
|
3998
|
+
for (const unit of units.slice(1)) {
|
|
3999
|
+
defaulted = {
|
|
4000
|
+
kind: "logical", op: "||", left: defaulted,
|
|
4001
|
+
right: {
|
|
4002
|
+
kind: "unionIsTag", unionId: value.type.unionId,
|
|
4003
|
+
tag: L.armTag(value.type.unionId, unit), negated: false,
|
|
4004
|
+
value: savedRef(), type: BOOL, loc: value.loc,
|
|
4005
|
+
},
|
|
4006
|
+
type: BOOL,
|
|
4007
|
+
loc: value.loc,
|
|
4008
|
+
};
|
|
4009
|
+
}
|
|
4010
|
+
return {
|
|
4011
|
+
value: {
|
|
4012
|
+
kind: "seqExpr",
|
|
4013
|
+
stmts: [{ kind: "varDecl", localId: saved.id, init: value, loc: value.loc }],
|
|
4014
|
+
result: {
|
|
4015
|
+
kind: "nullish", left: savedRef(), right: defaultValue,
|
|
4016
|
+
type: F64, loc: value.loc,
|
|
4017
|
+
},
|
|
4018
|
+
type: F64,
|
|
4019
|
+
loc: value.loc,
|
|
4020
|
+
},
|
|
4021
|
+
defaulted,
|
|
4022
|
+
};
|
|
4023
|
+
}
|
|
4024
|
+
}
|
|
4025
|
+
return { value: L.coerceInto(node, value, F64), defaulted: bool(false) };
|
|
4026
|
+
};
|
|
4027
|
+
const utf8 = (node) => {
|
|
4028
|
+
const dflt = { kind: "strLit", value: "utf8", type: STRING, loc };
|
|
4029
|
+
if (!node)
|
|
4030
|
+
return dflt;
|
|
4031
|
+
const nodeType = L.typeOf(node);
|
|
4032
|
+
const parts = nodeType.isUnionType() ? nodeType.getTypes() : [nodeType];
|
|
4033
|
+
const supported = parts.every((t) => (t.flags & (ts.TypeFlags.Undefined | ts.TypeFlags.Void | ts.TypeFlags.Null)) !== 0 ||
|
|
4034
|
+
(t.isStringLiteralType() && (t.value === "utf8" || t.value === "utf-8")));
|
|
4035
|
+
if (!supported) {
|
|
4036
|
+
L.noLowering(`FileHandle.${name} with a non-utf8 encoding`, node, "only utf8 data is supported");
|
|
4037
|
+
}
|
|
4038
|
+
return lowerBuiltinOptionalDefault(L, node, STRING, dflt, true);
|
|
4039
|
+
};
|
|
4040
|
+
if (name === "close" || name === "stat") {
|
|
4041
|
+
if (call.arguments.length !== 0)
|
|
4042
|
+
L.noLowering(`FileHandle.${name} with arguments`, call);
|
|
4043
|
+
return {
|
|
4044
|
+
kind: "libCall",
|
|
4045
|
+
fn: name === "close" ? "fileHandle.close" : "fileHandle.stat",
|
|
4046
|
+
args: [receiver()],
|
|
4047
|
+
type: promise(name === "close" ? VOID : { kind: "stats" }),
|
|
4048
|
+
loc,
|
|
4049
|
+
};
|
|
4050
|
+
}
|
|
4051
|
+
if (name === "readFile") {
|
|
4052
|
+
if (call.arguments.length > 1) {
|
|
4053
|
+
L.noLowering(`FileHandle.readFile with ${call.arguments.length} arguments`, call);
|
|
4054
|
+
}
|
|
4055
|
+
const type = L.mapTypeOf(L.typeOf(call));
|
|
4056
|
+
if (type?.kind !== "promise")
|
|
4057
|
+
L.badType(call, L.typeOf(call));
|
|
4058
|
+
const encoding = utf8(call.arguments[0]);
|
|
4059
|
+
if (typeEquals(type.inner, BYTES_U8)) {
|
|
4060
|
+
return {
|
|
4061
|
+
kind: "libCall", fn: "fileHandle.readFileBytes", args: [receiver(), encoding],
|
|
4062
|
+
type, loc,
|
|
4063
|
+
};
|
|
4064
|
+
}
|
|
4065
|
+
if (!typeEquals(type.inner, STRING))
|
|
4066
|
+
L.badType(call, L.typeOf(call));
|
|
4067
|
+
return {
|
|
4068
|
+
kind: "libCall", fn: "fileHandle.readFile", args: [receiver(), encoding],
|
|
4069
|
+
type, loc,
|
|
4070
|
+
};
|
|
4071
|
+
}
|
|
4072
|
+
if (name === "writeFile" || name === "appendFile") {
|
|
4073
|
+
if (call.arguments.length < 1 || call.arguments.length > 2) {
|
|
4074
|
+
L.noLowering(`FileHandle.${name} with ${call.arguments.length} arguments`, call);
|
|
4075
|
+
}
|
|
4076
|
+
const dataNode = call.arguments[0];
|
|
4077
|
+
const dataT = L.mapTypeOf(L.typeOf(dataNode));
|
|
4078
|
+
// Evaluate a supplied utf8 encoding even for Buffer data, matching
|
|
4079
|
+
// Node's argument order; it does not affect the bytes.
|
|
4080
|
+
const encoding = utf8(call.arguments[1]);
|
|
4081
|
+
if (dataT?.kind === "string") {
|
|
4082
|
+
return {
|
|
4083
|
+
kind: "libCall", fn: "fileHandle.writeFile",
|
|
4084
|
+
args: [receiver(), L.lowerExprExpecting(dataNode, STRING), encoding],
|
|
4085
|
+
type: promise(VOID), loc,
|
|
4086
|
+
};
|
|
4087
|
+
}
|
|
4088
|
+
if (dataT?.kind === "bytes" && dataT.elem === "u8") {
|
|
4089
|
+
return {
|
|
4090
|
+
kind: "libCall", fn: "fileHandle.writeFileBytes",
|
|
4091
|
+
args: [receiver(), L.lowerExprExpecting(dataNode, BYTES_U8), encoding],
|
|
4092
|
+
type: promise(VOID), loc,
|
|
4093
|
+
};
|
|
4094
|
+
}
|
|
4095
|
+
L.noLowering(`FileHandle.${name} of '${dataT ? L.fmt(dataT) : L.checker.typeToString(L.typeOf(dataNode))}' data`, dataNode, "string and Uint8Array data are supported");
|
|
4096
|
+
}
|
|
4097
|
+
if (name === "read") {
|
|
4098
|
+
if (call.arguments.length < 1 || call.arguments.length > 4) {
|
|
4099
|
+
L.noLowering(`FileHandle.read with ${call.arguments.length} arguments`, call, "use read(buffer[, offset[, length[, position]]])");
|
|
4100
|
+
}
|
|
4101
|
+
const buffer = L.lowerExprExpecting(call.arguments[0], BYTES_U8);
|
|
4102
|
+
const type = L.mapTypeOf(L.typeOf(call));
|
|
4103
|
+
if (type?.kind !== "promise" || type.inner.kind !== "record") {
|
|
4104
|
+
L.badType(call, L.typeOf(call));
|
|
4105
|
+
}
|
|
4106
|
+
const offset = num(call.arguments[1], 0);
|
|
4107
|
+
const length = num(call.arguments[2], -1);
|
|
4108
|
+
const position = num(call.arguments[3], -1);
|
|
4109
|
+
return {
|
|
4110
|
+
kind: "libCall", fn: "fileHandle.read",
|
|
4111
|
+
args: [
|
|
4112
|
+
receiver(), buffer, offset.value, length.value,
|
|
4113
|
+
position.value, length.defaulted,
|
|
4114
|
+
],
|
|
4115
|
+
type, loc,
|
|
4116
|
+
};
|
|
4117
|
+
}
|
|
4118
|
+
if (name === "write") {
|
|
4119
|
+
if (call.arguments.length < 1 || call.arguments.length > 4) {
|
|
4120
|
+
L.noLowering(`FileHandle.write with ${call.arguments.length} arguments`, call);
|
|
4121
|
+
}
|
|
4122
|
+
const dataNode = call.arguments[0];
|
|
4123
|
+
const dataT = L.mapTypeOf(L.typeOf(dataNode));
|
|
4124
|
+
const type = L.mapTypeOf(L.typeOf(call));
|
|
4125
|
+
if (type?.kind !== "promise" || type.inner.kind !== "record") {
|
|
4126
|
+
L.badType(call, L.typeOf(call));
|
|
4127
|
+
}
|
|
4128
|
+
if (dataT?.kind === "bytes" && dataT.elem === "u8") {
|
|
4129
|
+
const offset = num(call.arguments[1], 0);
|
|
4130
|
+
const length = num(call.arguments[2], -1);
|
|
4131
|
+
const position = num(call.arguments[3], -1);
|
|
4132
|
+
return {
|
|
4133
|
+
kind: "libCall", fn: "fileHandle.writeBytes",
|
|
4134
|
+
args: [
|
|
4135
|
+
receiver(), L.lowerExprExpecting(dataNode, BYTES_U8),
|
|
4136
|
+
offset.value, length.value, position.value,
|
|
4137
|
+
length.defaulted,
|
|
4138
|
+
],
|
|
4139
|
+
type, loc,
|
|
4140
|
+
};
|
|
4141
|
+
}
|
|
4142
|
+
if (dataT?.kind === "string") {
|
|
4143
|
+
if (call.arguments.length > 3) {
|
|
4144
|
+
L.noLowering(`FileHandle.write(string) with ${call.arguments.length} arguments`, call, 'use write(string[, position[, "utf8"]])');
|
|
4145
|
+
}
|
|
4146
|
+
const position = num(call.arguments[1], -1);
|
|
4147
|
+
return {
|
|
4148
|
+
kind: "libCall", fn: "fileHandle.writeStr",
|
|
4149
|
+
args: [receiver(), L.lowerExprExpecting(dataNode, STRING), position.value, utf8(call.arguments[2])],
|
|
4150
|
+
type, loc,
|
|
4151
|
+
};
|
|
4152
|
+
}
|
|
4153
|
+
L.noLowering(`FileHandle.write of '${dataT ? L.fmt(dataT) : L.checker.typeToString(L.typeOf(dataNode))}' data`, dataNode, "string and Uint8Array data are supported");
|
|
4154
|
+
}
|
|
4155
|
+
L.noLowering(`FileHandle.${name}`, call, "fd, close(), read(), write(), readFile(), writeFile(), appendFile(), and stat() are the supported FileHandle members", L.checker.getSymbolAtLocation(access.name));
|
|
4156
|
+
}
|
|
3675
4157
|
/** Method calls on Stats-typed receivers: isFile()/isDirectory()/
|
|
3676
4158
|
* isSymbolicLink() are pure reads on the stat snapshot (a followed
|
|
3677
4159
|
* statSync snapshot never answers true to isSymbolicLink — take
|
|
@@ -3694,7 +4176,7 @@ export function lowerStatsMethodCall(L, call, access) {
|
|
|
3694
4176
|
: "stats.isSymbolicLink";
|
|
3695
4177
|
return { kind: "libCall", fn, args: [receiver], type: BOOL, loc: locOf(call) };
|
|
3696
4178
|
}
|
|
3697
|
-
L.noLowering(`Stats.${name}`, call, "isFile(), isDirectory(), isSymbolicLink(), size, and mtimeMs are the supported Stats members", L.checker.getSymbolAtLocation(access.name));
|
|
4179
|
+
L.noLowering(`Stats.${name}`, call, "isFile(), isDirectory(), isSymbolicLink(), size, blocks, nlink, atimeMs, and mtimeMs are the supported Stats members", L.checker.getSymbolAtLocation(access.name));
|
|
3698
4180
|
}
|
|
3699
4181
|
/** `Atomics.wait(int32Array, idx, expected, timeoutMs)` — the
|
|
3700
4182
|
* synchronous-sleep idiom (RouteStore's
|
|
@@ -3733,12 +4215,11 @@ export function lowerAtomicsCall(L, call, access) {
|
|
|
3733
4215
|
}
|
|
3734
4216
|
/** The property-read extensions this spoke owns, tried BEFORE the
|
|
3735
4217
|
* lower-exprs intrinsic-property fallback (the lowerer's wrapper chains
|
|
3736
|
-
* them):
|
|
3737
|
-
*
|
|
3738
|
-
*
|
|
3739
|
-
*
|
|
3740
|
-
*
|
|
3741
|
-
* its fences) keeps going. */
|
|
4218
|
+
* them): the widened numeric Stats snapshot and SpawnSyncReturns.signal
|
|
4219
|
+
* (the termination signal's name as the call site's `Signals | null` union
|
|
4220
|
+
* — null for a normal exit or spawn failure; a timeout kill reports its
|
|
4221
|
+
* killSignal, Node's shape). Null for everything else, so the ordinary
|
|
4222
|
+
* chain (and its fences) keeps going. */
|
|
3742
4223
|
export function lowerBuiltinExtraProperty(L, expr) {
|
|
3743
4224
|
if (expr.questionDotToken && !L.chainHandled.has(expr))
|
|
3744
4225
|
return null;
|
|
@@ -3752,12 +4233,23 @@ export function lowerBuiltinExtraProperty(L, expr) {
|
|
|
3752
4233
|
return { kind: "recordGet", obj: receiver, shapeId: receiver.type.shapeId, field: "%enc", type: STRING, loc: locOf(expr) };
|
|
3753
4234
|
}
|
|
3754
4235
|
const kind = L.mapTypeOf(L.typeOf(expr.expression))?.kind;
|
|
3755
|
-
if (kind !== "stats" && kind !== "spawnRes" && kind !== "child")
|
|
4236
|
+
if (kind !== "stats" && kind !== "fileHandle" && kind !== "spawnRes" && kind !== "child")
|
|
3756
4237
|
return null;
|
|
3757
4238
|
if (kind === "child" ? !isChildSurfaceMember(L, expr) : !L.isStdlibMember(expr))
|
|
3758
4239
|
return null;
|
|
3759
4240
|
const name = expr.name.text;
|
|
3760
4241
|
const loc = locOf(expr);
|
|
4242
|
+
if (kind === "fileHandle") {
|
|
4243
|
+
if (name === "fd") {
|
|
4244
|
+
const receiver = L.lowerExprExpecting(expr.expression, FILEHANDLE_T);
|
|
4245
|
+
return { kind: "libCall", fn: "fileHandle.fd", args: [receiver], type: F64, loc };
|
|
4246
|
+
}
|
|
4247
|
+
const methods = new Set(["close", "read", "write", "readFile", "writeFile", "appendFile", "stat"]);
|
|
4248
|
+
if (methods.has(name)) {
|
|
4249
|
+
L.unsupported("SC1090", expr, `FileHandle methods as values (call '${name}' directly)`);
|
|
4250
|
+
}
|
|
4251
|
+
L.noLowering(`FileHandle.${name}`, expr, "fd, close(), read(), write(), readFile(), writeFile(), appendFile(), and stat() are the supported FileHandle members", L.checker.getSymbolAtLocation(expr.name));
|
|
4252
|
+
}
|
|
3761
4253
|
// child.stdout / child.stderr — the piped-output streams: the
|
|
3762
4254
|
// checker's `Readable | null` (null exactly when the slot was not
|
|
3763
4255
|
// piped), constructed type-directedly in the backend over the
|
|
@@ -3779,9 +4271,11 @@ export function lowerBuiltinExtraProperty(L, expr) {
|
|
|
3779
4271
|
}
|
|
3780
4272
|
if (kind === "child")
|
|
3781
4273
|
return null; // pid/exitCode/killed live in lowerIntrinsicProperty
|
|
3782
|
-
if (kind === "stats" &&
|
|
4274
|
+
if (kind === "stats" &&
|
|
4275
|
+
(name === "blocks" || name === "nlink" || name === "atimeMs" || name === "mtimeMs")) {
|
|
3783
4276
|
const receiver = L.lowerExpr(expr.expression);
|
|
3784
|
-
|
|
4277
|
+
const fn = `stats.${name}`;
|
|
4278
|
+
return { kind: "libCall", fn, args: [receiver], type: F64, loc };
|
|
3785
4279
|
}
|
|
3786
4280
|
if (kind === "spawnRes" && name === "signal") {
|
|
3787
4281
|
const receiver = L.lowerExpr(expr.expression);
|
|
@@ -4756,12 +5250,13 @@ export function lowerProcessEnvGet(L, expr) {
|
|
|
4756
5250
|
export function lowerProcessMethodCall(L, call, access) {
|
|
4757
5251
|
if (call.questionDotToken)
|
|
4758
5252
|
return null;
|
|
4759
|
-
// process.stdout.write(s)
|
|
4760
|
-
//
|
|
5253
|
+
// process.stdout.write(s[, encoding][, callback]) and stderr's twin:
|
|
5254
|
+
// raw bytes, no newline or formatting. stdout shares console.log's
|
|
4761
5255
|
// promptly-submitted stream, preserving source order. Node's boolean
|
|
4762
5256
|
// is a backpressure signal; this synchronous write is constantly true.
|
|
4763
|
-
//
|
|
4764
|
-
//
|
|
5257
|
+
// String encodings are compile-time-known BufferEncoding spellings;
|
|
5258
|
+
// byte chunks evaluate but ignore the encoding like Node. Completion
|
|
5259
|
+
// callbacks ride the next-tick queue and receive the success `null`.
|
|
4765
5260
|
// process.stdin.destroy(): a deliberate no-op — no stream machinery
|
|
4766
5261
|
// exists to tear down, and no other stdin surface observes the
|
|
4767
5262
|
// destroyed state (SEMANTICS.md documents it).
|
|
@@ -4845,10 +5340,64 @@ export function lowerProcessMethodCall(L, call, access) {
|
|
|
4845
5340
|
const stream = L.stdlibGlobalMember(access.expression, "process");
|
|
4846
5341
|
if (stream === "stdout" || stream === "stderr") {
|
|
4847
5342
|
const loc = locOf(call);
|
|
4848
|
-
|
|
4849
|
-
|
|
5343
|
+
const args = call.arguments;
|
|
5344
|
+
if (args.length < 1 || args.length > 3 || args.some(ts.isSpreadElement)) {
|
|
5345
|
+
L.noLowering(`process.${stream}.write with ${args.length} arguments`, call, "the supported forms are write(data[, encoding][, callback]) with a static BufferEncoding and completion callback");
|
|
4850
5346
|
}
|
|
4851
|
-
|
|
5347
|
+
const secondNode = args[1];
|
|
5348
|
+
const thirdNode = args[2];
|
|
5349
|
+
const secondUndefined = secondNode
|
|
5350
|
+
? lowerStaticallyUndefinedBuiltinArg(L, secondNode)
|
|
5351
|
+
: null;
|
|
5352
|
+
const thirdUndefined = thirdNode
|
|
5353
|
+
? lowerStaticallyUndefinedBuiltinArg(L, thirdNode)
|
|
5354
|
+
: null;
|
|
5355
|
+
const secondT = secondNode && !secondUndefined ? L.mapTypeOf(L.typeOf(secondNode)) : undefined;
|
|
5356
|
+
const callbackNode = args.length === 3 && !thirdUndefined
|
|
5357
|
+
? thirdNode
|
|
5358
|
+
: args.length === 2 && secondT?.kind === "func"
|
|
5359
|
+
? secondNode
|
|
5360
|
+
: undefined;
|
|
5361
|
+
const encodingNode = (args.length === 3 || (args.length === 2 && callbackNode === undefined)) && !secondUndefined
|
|
5362
|
+
? secondNode
|
|
5363
|
+
: undefined;
|
|
5364
|
+
// Node's BufferEncoding aliases normalize before bytes are made.
|
|
5365
|
+
// Preserve an effectful literal-typed expression even when its
|
|
5366
|
+
// spelling folds (for example a function returning `"binary"`).
|
|
5367
|
+
const encoding = (() => {
|
|
5368
|
+
const defaultEncoding = { kind: "strLit", value: "utf8", type: STRING, loc };
|
|
5369
|
+
if (secondUndefined) {
|
|
5370
|
+
return defaultAfterUndefined(secondUndefined, defaultEncoding);
|
|
5371
|
+
}
|
|
5372
|
+
if (!encodingNode) {
|
|
5373
|
+
return defaultEncoding;
|
|
5374
|
+
}
|
|
5375
|
+
const aliases = {
|
|
5376
|
+
utf8: "utf8", "utf-8": "utf8", hex: "hex", base64: "base64",
|
|
5377
|
+
base64url: "base64url", latin1: "latin1", binary: "latin1", ascii: "ascii",
|
|
5378
|
+
utf16le: "utf16le", "utf-16le": "utf16le", ucs2: "utf16le", "ucs-2": "utf16le",
|
|
5379
|
+
};
|
|
5380
|
+
const t = L.typeOf(encodingNode);
|
|
5381
|
+
const raw = t.isStringLiteralType() ? t.value : undefined;
|
|
5382
|
+
const canonical = raw !== undefined ? own(aliases, raw) : undefined;
|
|
5383
|
+
if (canonical === undefined) {
|
|
5384
|
+
L.noLowering(`process.${stream}.write with this encoding`, encodingNode, 'use a literal "utf8", "hex", "base64", "base64url", "latin1", "binary", "ascii", "utf16le", or "ucs2" encoding');
|
|
5385
|
+
}
|
|
5386
|
+
const evaluated = L.lowerExprExpecting(encodingNode, STRING);
|
|
5387
|
+
if (canonical === raw)
|
|
5388
|
+
return evaluated;
|
|
5389
|
+
const normalized = { kind: "strLit", value: canonical, type: STRING, loc: locOf(encodingNode) };
|
|
5390
|
+
return droppableStatic(evaluated)
|
|
5391
|
+
? normalized
|
|
5392
|
+
: {
|
|
5393
|
+
kind: "seqExpr",
|
|
5394
|
+
stmts: [{ kind: "exprStmt", expr: evaluated, loc: evaluated.loc }],
|
|
5395
|
+
result: normalized,
|
|
5396
|
+
type: STRING,
|
|
5397
|
+
loc: evaluated.loc,
|
|
5398
|
+
};
|
|
5399
|
+
})();
|
|
5400
|
+
let data = L.lowerExpr(args[0]);
|
|
4852
5401
|
// A checked-dynamic argument in a JS file takes the validated
|
|
4853
5402
|
// string exit (the trust-but-verify boundary: commander's
|
|
4854
5403
|
// `writeOut: (str) => process.stdout.write(str)` — str untyped):
|
|
@@ -4857,20 +5406,81 @@ export function lowerProcessMethodCall(L, call, access) {
|
|
|
4857
5406
|
if (data.type.kind === "dyn" && isJsSourceFile(call.getSourceFile())) {
|
|
4858
5407
|
data = { kind: "dynCheck", value: data, type: STRING, loc };
|
|
4859
5408
|
}
|
|
4860
|
-
|
|
4861
|
-
|
|
4862
|
-
|
|
5409
|
+
const isBytes = data.type.kind === "bytes" && data.type.elem === "u8";
|
|
5410
|
+
if (!isBytes && data.type.kind !== "string") {
|
|
5411
|
+
L.noLowering(`process.${stream}.write of non-string data`, args[0], "strings and Buffer/Uint8Array values write; narrow unions first");
|
|
5412
|
+
}
|
|
5413
|
+
let callback;
|
|
5414
|
+
if (callbackNode) {
|
|
5415
|
+
callback = L.lowerExpr(callbackNode);
|
|
5416
|
+
if (callback.type.kind === "dyn" && isJsSourceFile(call.getSourceFile())) {
|
|
5417
|
+
callback = {
|
|
5418
|
+
kind: "dynCheck",
|
|
5419
|
+
value: callback,
|
|
5420
|
+
type: funcOf([DYN], VOID),
|
|
5421
|
+
loc: locOf(callbackNode),
|
|
5422
|
+
};
|
|
5423
|
+
}
|
|
5424
|
+
let callbackOk = callback.type.kind === "func" && callback.type.params.length <= 1;
|
|
5425
|
+
if (callbackOk && callback.type.kind === "func" && callback.type.params.length === 1) {
|
|
5426
|
+
const param = callback.type.params[0];
|
|
5427
|
+
if (param.kind !== "dyn") {
|
|
5428
|
+
const def = param.kind === "union" ? L.unions.get(param.unionId) : undefined;
|
|
5429
|
+
callbackOk = !!def &&
|
|
5430
|
+
def.arms.some((a) => a.kind === "nullT") &&
|
|
5431
|
+
def.arms.some((a) => a.kind === "object" && a.className === "%Error") &&
|
|
5432
|
+
def.arms.every((a) => a.kind === "nullT" || a.kind === "undefinedT" ||
|
|
5433
|
+
(a.kind === "object" && a.className === "%Error"));
|
|
5434
|
+
}
|
|
5435
|
+
}
|
|
5436
|
+
if (!callbackOk) {
|
|
5437
|
+
L.unsupported("SC1090", callbackNode, "process output completion callbacks must accept at most one Error | null parameter");
|
|
5438
|
+
}
|
|
5439
|
+
callback = voidizedCallback(L, callback, locOf(callbackNode));
|
|
5440
|
+
}
|
|
5441
|
+
// Encoding- or callback-bearing writes use one fixed byte ABI. For
|
|
5442
|
+
// strings, Buffer.from's encoder runs after data/encoding evaluation
|
|
5443
|
+
// and before the callback expression; only its pure allocation moves
|
|
5444
|
+
// earlier than Node's internal write conversion.
|
|
5445
|
+
if (callback || args.length > 1 || isBytes) {
|
|
5446
|
+
const bytes = isBytes
|
|
5447
|
+
? data
|
|
5448
|
+
: { kind: "libCall", fn: "buffer.fromStr", args: [data, encoding], type: BYTES_U8, loc };
|
|
5449
|
+
const defaultRuntimeEncoding = { kind: "strLit", value: "utf8", type: STRING, loc };
|
|
5450
|
+
let runtimeEncoding = isBytes ? encoding : defaultRuntimeEncoding;
|
|
5451
|
+
// An explicitly-undefined third argument is still evaluated after
|
|
5452
|
+
// data and encoding, even though it schedules no callback. Strings
|
|
5453
|
+
// already evaluate encoding while producing `bytes`; byte chunks
|
|
5454
|
+
// fold both ignored argument effects into this final ABI slot.
|
|
5455
|
+
if (thirdUndefined && !droppableStatic(thirdUndefined)) {
|
|
5456
|
+
const effects = isBytes
|
|
5457
|
+
? [encoding, thirdUndefined].filter((effect) => !droppableStatic(effect))
|
|
5458
|
+
: [thirdUndefined];
|
|
5459
|
+
runtimeEncoding = {
|
|
5460
|
+
kind: "seqExpr",
|
|
5461
|
+
stmts: effects.map((effect) => ({ kind: "exprStmt", expr: effect, loc: effect.loc })),
|
|
5462
|
+
result: defaultRuntimeEncoding,
|
|
5463
|
+
type: STRING,
|
|
5464
|
+
loc: thirdUndefined.loc,
|
|
5465
|
+
};
|
|
5466
|
+
}
|
|
5467
|
+
if (callback) {
|
|
5468
|
+
return {
|
|
5469
|
+
kind: "libCall",
|
|
5470
|
+
fn: stream === "stdout" ? "process.stdoutWriteBytesCb" : "process.stderrWriteBytesCb",
|
|
5471
|
+
args: [bytes, runtimeEncoding, callback],
|
|
5472
|
+
type: BOOL,
|
|
5473
|
+
loc,
|
|
5474
|
+
};
|
|
5475
|
+
}
|
|
4863
5476
|
return {
|
|
4864
5477
|
kind: "libCall",
|
|
4865
5478
|
fn: stream === "stdout" ? "process.stdoutWriteBytes" : "process.stderrWriteBytes",
|
|
4866
|
-
args: [
|
|
5479
|
+
args: [bytes, runtimeEncoding],
|
|
4867
5480
|
type: BOOL,
|
|
4868
5481
|
loc,
|
|
4869
5482
|
};
|
|
4870
5483
|
}
|
|
4871
|
-
if (data.type.kind !== "string") {
|
|
4872
|
-
L.noLowering(`process.${stream}.write of non-string data`, call.arguments[0], "strings and Buffer/Uint8Array values write; narrow unions first");
|
|
4873
|
-
}
|
|
4874
5484
|
return {
|
|
4875
5485
|
kind: "libCall",
|
|
4876
5486
|
fn: stream === "stdout" ? "process.stdoutWrite" : "process.stderrWrite",
|
|
@@ -5326,13 +5936,29 @@ export function lowerNumberStaticCall(L, call, access) {
|
|
|
5326
5936
|
}
|
|
5327
5937
|
return null; // other Number statics land on the member fence
|
|
5328
5938
|
}
|
|
5329
|
-
|
|
5330
|
-
|
|
5331
|
-
|
|
5332
|
-
|
|
5333
|
-
|
|
5334
|
-
|
|
5335
|
-
|
|
5939
|
+
const DATE_GETTER_FNS = {
|
|
5940
|
+
getFullYear: "date.getFullYear",
|
|
5941
|
+
getUTCFullYear: "date.getUTCFullYear",
|
|
5942
|
+
getMonth: "date.getMonth",
|
|
5943
|
+
getUTCMonth: "date.getUTCMonth",
|
|
5944
|
+
getDate: "date.getDate",
|
|
5945
|
+
getUTCDate: "date.getUTCDate",
|
|
5946
|
+
getDay: "date.getDay",
|
|
5947
|
+
getUTCDay: "date.getUTCDay",
|
|
5948
|
+
getHours: "date.getHours",
|
|
5949
|
+
getUTCHours: "date.getUTCHours",
|
|
5950
|
+
getMinutes: "date.getMinutes",
|
|
5951
|
+
getUTCMinutes: "date.getUTCMinutes",
|
|
5952
|
+
getSeconds: "date.getSeconds",
|
|
5953
|
+
getUTCSeconds: "date.getUTCSeconds",
|
|
5954
|
+
getMilliseconds: "date.getMilliseconds",
|
|
5955
|
+
getUTCMilliseconds: "date.getUTCMilliseconds",
|
|
5956
|
+
getTimezoneOffset: "date.getTimezoneOffset",
|
|
5957
|
+
};
|
|
5958
|
+
const DATE_METHOD_HINT = "getTime(), valueOf(), toISOString(), the local/UTC calendar getters, and getTimezoneOffset() are supported; Date setters and locale/string formatters have no lowering";
|
|
5959
|
+
/** The Date slice: statics plus read-only Date values backed by one
|
|
5960
|
+
* TimeClip'd millisecond scalar. Construction lives in lowerNew;
|
|
5961
|
+
* identity and mutating methods remain fenced. */
|
|
5336
5962
|
export function lowerDateCall(L, call, access) {
|
|
5337
5963
|
if (call.questionDotToken || access.questionDotToken)
|
|
5338
5964
|
return null;
|
|
@@ -5363,60 +5989,107 @@ export function lowerDateCall(L, call, access) {
|
|
|
5363
5989
|
}
|
|
5364
5990
|
return { kind: "libCall", fn: "date.utc", args, type: F64, loc };
|
|
5365
5991
|
}
|
|
5366
|
-
if (
|
|
5992
|
+
if (L.mapTypeOf(L.typeOf(access.expression))?.kind !== "date")
|
|
5367
5993
|
return null;
|
|
5368
|
-
|
|
5369
|
-
|
|
5370
|
-
|
|
5371
|
-
const sym = L.resolveValueSymbol(recv.expression);
|
|
5372
|
-
if (!sym || sym.name !== "Date" || !L.isStdlibSymbol(sym))
|
|
5373
|
-
return null;
|
|
5374
|
-
if (access.name.text === "getTime") {
|
|
5375
|
-
// The composed `new Date(x).getTime()` read — the Date value
|
|
5376
|
-
// between the two never materializes (the toISOString precedent).
|
|
5377
|
-
// new Date().getTime() IS Date.now(); the STRING form parses the
|
|
5378
|
-
// bounded date-string grammar (the X509 validity shape portless's
|
|
5379
|
-
// cert-expiry check reads, plus ECMA's own date-time format) and
|
|
5380
|
-
// answers NaN elsewhere — a documented divergence from V8's wider
|
|
5381
|
-
// parser. The ms form is pointless composition; the fence points at
|
|
5382
|
-
// the value.
|
|
5383
|
-
if (call.arguments.length !== 0) {
|
|
5384
|
-
L.noLowering("getTime with arguments", call);
|
|
5385
|
-
}
|
|
5386
|
-
const ctorArgs = recv.arguments ?? [];
|
|
5387
|
-
if (ctorArgs.length === 0) {
|
|
5388
|
-
return { kind: "libCall", fn: "date.now", args: [], type: F64, loc };
|
|
5389
|
-
}
|
|
5390
|
-
if (ctorArgs.length !== 1) {
|
|
5391
|
-
L.noLowering("new Date with year/month/day arguments", recv, "the field constructor is local-time; only new Date() and new Date(dateString) compose with .getTime()");
|
|
5392
|
-
}
|
|
5393
|
-
const arg = L.lowerExpr(ctorArgs[0]);
|
|
5394
|
-
if (arg.type.kind !== "string") {
|
|
5395
|
-
L.noLowering(`new Date of '${L.fmt(arg.type)}' values composed with .getTime()`, ctorArgs[0], arg.type.kind === "f64"
|
|
5396
|
-
? "new Date(ms).getTime() is the ms value — use it directly (or Date.now())"
|
|
5397
|
-
: "the composed form parses date STRINGS — narrow unions first");
|
|
5398
|
-
}
|
|
5399
|
-
return { kind: "libCall", fn: "date.parseGetTime", args: [arg], type: F64, loc };
|
|
5400
|
-
}
|
|
5994
|
+
if (!L.isStdlibMember(access))
|
|
5995
|
+
return null;
|
|
5996
|
+
const name = access.name.text;
|
|
5401
5997
|
if (call.arguments.length !== 0) {
|
|
5402
|
-
L.noLowering(
|
|
5998
|
+
L.noLowering(`Date.prototype.${name} with arguments`, call, DATE_METHOD_HINT);
|
|
5403
5999
|
}
|
|
5404
|
-
const
|
|
5405
|
-
|
|
5406
|
-
|
|
5407
|
-
|
|
6000
|
+
const receiver = L.lowerExpr(access.expression);
|
|
6001
|
+
if (receiver.type.kind !== "date")
|
|
6002
|
+
L.badType(access.expression, L.typeOf(access.expression));
|
|
6003
|
+
if (name === "getTime" || name === "valueOf") {
|
|
6004
|
+
return {
|
|
6005
|
+
kind: "libCall",
|
|
6006
|
+
fn: name === "getTime" ? "date.getTime" : "date.valueOf",
|
|
6007
|
+
args: [receiver],
|
|
6008
|
+
type: F64,
|
|
6009
|
+
loc,
|
|
6010
|
+
};
|
|
5408
6011
|
}
|
|
5409
|
-
|
|
5410
|
-
|
|
5411
|
-
if (arg.type.kind !== "f64") {
|
|
5412
|
-
L.noLowering(`new Date of '${L.fmt(arg.type)}' values`, ctorArgs[0], "only the milliseconds form composes — new Date(ms).toISOString(); date-string parsing has no lowering");
|
|
5413
|
-
}
|
|
5414
|
-
ms = arg;
|
|
6012
|
+
if (name === "toISOString") {
|
|
6013
|
+
return { kind: "libCall", fn: "date.toISOStringValue", args: [receiver], type: STRING, loc };
|
|
5415
6014
|
}
|
|
5416
|
-
|
|
5417
|
-
|
|
6015
|
+
const fn = DATE_GETTER_FNS[name];
|
|
6016
|
+
if (fn !== undefined) {
|
|
6017
|
+
return { kind: "libCall", fn, args: [receiver], type: F64, loc };
|
|
5418
6018
|
}
|
|
5419
|
-
|
|
6019
|
+
L.noLowering(`Date.prototype.${name}`, call, DATE_METHOD_HINT, L.checker.getSymbolAtLocation(access.name));
|
|
6020
|
+
}
|
|
6021
|
+
/* WHATWG label registry → native decoder id. The first 27 ids stay in the
|
|
6022
|
+
* same order as scripts/gen-text-decoder-tables.mjs; the remaining ids match
|
|
6023
|
+
* scr_bytes.c's compact enum. ISO-8859-8-I shares ISO-8859-8's byte table,
|
|
6024
|
+
* and GBK/gb18030 share one decoder (their decoders are identical). */
|
|
6025
|
+
const TEXT_DECODER_SINGLE_BYTE_NAMES = [
|
|
6026
|
+
"ibm866", "iso-8859-2", "iso-8859-3", "iso-8859-4", "iso-8859-5",
|
|
6027
|
+
"iso-8859-6", "iso-8859-7", "iso-8859-8", "iso-8859-10", "iso-8859-13",
|
|
6028
|
+
"iso-8859-14", "iso-8859-15", "iso-8859-16", "koi8-r", "koi8-u",
|
|
6029
|
+
"macintosh", "windows-874", "windows-1250", "windows-1251", "windows-1252",
|
|
6030
|
+
"windows-1253", "windows-1254", "windows-1255", "windows-1256", "windows-1257",
|
|
6031
|
+
"windows-1258", "x-mac-cyrillic",
|
|
6032
|
+
];
|
|
6033
|
+
const TEXT_DECODER_UTF8_LABELS = new Set([
|
|
6034
|
+
"unicode-1-1-utf-8", "unicode11utf8", "unicode20utf8", "utf-8", "utf8", "x-unicode20utf8",
|
|
6035
|
+
]);
|
|
6036
|
+
const TEXT_DECODER_LEGACY_LABELS = (() => {
|
|
6037
|
+
const labels = Object.create(null);
|
|
6038
|
+
const add = (encoding, names) => {
|
|
6039
|
+
for (const name of names)
|
|
6040
|
+
labels[name] = encoding;
|
|
6041
|
+
};
|
|
6042
|
+
const single = (name, aliases) => {
|
|
6043
|
+
add(TEXT_DECODER_SINGLE_BYTE_NAMES.indexOf(name), aliases);
|
|
6044
|
+
};
|
|
6045
|
+
single("ibm866", ["866", "cp866", "csibm866", "ibm866"]);
|
|
6046
|
+
single("iso-8859-2", ["csisolatin2", "iso-8859-2", "iso-ir-101", "iso8859-2", "iso88592", "iso_8859-2", "iso_8859-2:1987", "l2", "latin2"]);
|
|
6047
|
+
single("iso-8859-3", ["csisolatin3", "iso-8859-3", "iso-ir-109", "iso8859-3", "iso88593", "iso_8859-3", "iso_8859-3:1988", "l3", "latin3"]);
|
|
6048
|
+
single("iso-8859-4", ["csisolatin4", "iso-8859-4", "iso-ir-110", "iso8859-4", "iso88594", "iso_8859-4", "iso_8859-4:1988", "l4", "latin4"]);
|
|
6049
|
+
single("iso-8859-5", ["csisolatincyrillic", "cyrillic", "iso-8859-5", "iso-ir-144", "iso8859-5", "iso88595", "iso_8859-5", "iso_8859-5:1988"]);
|
|
6050
|
+
single("iso-8859-6", ["arabic", "asmo-708", "csiso88596e", "csiso88596i", "csisolatinarabic", "ecma-114", "iso-8859-6", "iso-8859-6-e", "iso-8859-6-i", "iso-ir-127", "iso8859-6", "iso88596", "iso_8859-6", "iso_8859-6:1987"]);
|
|
6051
|
+
single("iso-8859-7", ["csisolatingreek", "ecma-118", "elot_928", "greek", "greek8", "iso-8859-7", "iso-ir-126", "iso8859-7", "iso88597", "iso_8859-7", "iso_8859-7:1987", "sun_eu_greek"]);
|
|
6052
|
+
single("iso-8859-8", ["csiso88598e", "csiso88598i", "csisolatinhebrew", "hebrew", "iso-8859-8", "iso-8859-8-e", "iso-8859-8-i", "iso-ir-138", "iso8859-8", "iso88598", "iso_8859-8", "iso_8859-8:1988", "logical", "visual"]);
|
|
6053
|
+
single("iso-8859-10", ["csisolatin6", "iso-8859-10", "iso-ir-157", "iso8859-10", "iso885910", "l6", "latin6"]);
|
|
6054
|
+
single("iso-8859-13", ["iso-8859-13", "iso8859-13", "iso885913"]);
|
|
6055
|
+
single("iso-8859-14", ["iso-8859-14", "iso8859-14", "iso885914"]);
|
|
6056
|
+
single("iso-8859-15", ["csisolatin9", "iso-8859-15", "iso8859-15", "iso885915", "iso_8859-15", "l9"]);
|
|
6057
|
+
single("iso-8859-16", ["iso-8859-16"]);
|
|
6058
|
+
single("koi8-r", ["cskoi8r", "koi", "koi8", "koi8-r", "koi8_r"]);
|
|
6059
|
+
single("koi8-u", ["koi8-ru", "koi8-u"]);
|
|
6060
|
+
single("macintosh", ["csmacintosh", "mac", "macintosh", "x-mac-roman"]);
|
|
6061
|
+
single("windows-874", ["dos-874", "iso-8859-11", "iso8859-11", "iso885911", "tis-620", "windows-874"]);
|
|
6062
|
+
single("windows-1250", ["cp1250", "windows-1250", "x-cp1250"]);
|
|
6063
|
+
single("windows-1251", ["cp1251", "windows-1251", "x-cp1251"]);
|
|
6064
|
+
single("windows-1252", ["ansi_x3.4-1968", "ascii", "cp1252", "cp819", "csisolatin1", "ibm819", "iso-8859-1", "iso-ir-100", "iso8859-1", "iso88591", "iso_8859-1", "iso_8859-1:1987", "l1", "latin1", "us-ascii", "windows-1252", "x-cp1252"]);
|
|
6065
|
+
single("windows-1253", ["cp1253", "windows-1253", "x-cp1253"]);
|
|
6066
|
+
single("windows-1254", ["cp1254", "csisolatin5", "iso-8859-9", "iso-ir-148", "iso8859-9", "iso88599", "iso_8859-9", "iso_8859-9:1989", "l5", "latin5", "windows-1254", "x-cp1254"]);
|
|
6067
|
+
single("windows-1255", ["cp1255", "windows-1255", "x-cp1255"]);
|
|
6068
|
+
single("windows-1256", ["cp1256", "windows-1256", "x-cp1256"]);
|
|
6069
|
+
single("windows-1257", ["cp1257", "windows-1257", "x-cp1257"]);
|
|
6070
|
+
single("windows-1258", ["cp1258", "windows-1258", "x-cp1258"]);
|
|
6071
|
+
single("x-mac-cyrillic", ["x-mac-cyrillic", "x-mac-ukrainian"]);
|
|
6072
|
+
add(27, ["x-user-defined"]);
|
|
6073
|
+
add(28, ["csunicode", "iso-10646-ucs-2", "ucs-2", "unicode", "unicodefeff", "utf-16", "utf-16le"]);
|
|
6074
|
+
add(29, ["unicodefffe", "utf-16be"]);
|
|
6075
|
+
add(30, ["chinese", "csgb2312", "csiso58gb231280", "gb18030", "gb2312", "gb_2312", "gb_2312-80", "gbk", "iso-ir-58", "x-gbk"]);
|
|
6076
|
+
add(31, ["big5", "big5-hkscs", "cn-big5", "csbig5", "x-x-big5"]);
|
|
6077
|
+
add(32, ["cseucpkdfmtjapanese", "euc-jp", "x-euc-jp"]);
|
|
6078
|
+
add(33, ["csiso2022jp", "iso-2022-jp"]);
|
|
6079
|
+
add(34, ["csshiftjis", "ms932", "ms_kanji", "shift-jis", "shift_jis", "sjis", "windows-31j", "x-sjis"]);
|
|
6080
|
+
add(35, ["cseuckr", "csksc56011987", "euc-kr", "iso-ir-149", "korean", "ks_c_5601-1987", "ks_c_5601-1989", "ksc5601", "ksc_5601", "windows-949"]);
|
|
6081
|
+
return labels;
|
|
6082
|
+
})();
|
|
6083
|
+
/** TextDecoder's get-an-encoding normalization: trim ASCII whitespace and
|
|
6084
|
+
* fold ASCII case only (Unicode case folding must not manufacture a label). */
|
|
6085
|
+
function staticTextDecoderEncoding(label) {
|
|
6086
|
+
const normalized = label
|
|
6087
|
+
.replace(/^[\u0009\u000a\u000c\u000d\u0020]+|[\u0009\u000a\u000c\u000d\u0020]+$/g, "")
|
|
6088
|
+
.replace(/[A-Z]/g, (char) => char.toLowerCase());
|
|
6089
|
+
if (TEXT_DECODER_UTF8_LABELS.has(normalized))
|
|
6090
|
+
return { kind: "utf8" };
|
|
6091
|
+
const id = own(TEXT_DECODER_LEGACY_LABELS, normalized);
|
|
6092
|
+
return id === undefined ? null : { kind: "legacy", id };
|
|
5420
6093
|
}
|
|
5421
6094
|
/** A direct construction of THE stdlib TextEncoder/TextDecoder, through
|
|
5422
6095
|
* type-only wrappers. Name alone is never enough: a user class with the
|
|
@@ -5440,8 +6113,9 @@ function directTextCodecCtorOf(L, expr) {
|
|
|
5440
6113
|
/** True when construction itself is effect-free and inside the already
|
|
5441
6114
|
* lowered codec slice, so a const binding can be erased and calls can
|
|
5442
6115
|
* resolve back to the initializer. TextDecoder's explicit label must be
|
|
5443
|
-
* an actual literal here: accepting an arbitrary expression
|
|
5444
|
-
* as
|
|
6116
|
+
* an actual recognized literal here: accepting an arbitrary expression
|
|
6117
|
+
* merely typed as a label would move or repeat its effects when the
|
|
6118
|
+
* binding is erased. */
|
|
5445
6119
|
function erasableTextCodecCtor(info) {
|
|
5446
6120
|
const args = info.ctor.arguments ?? [];
|
|
5447
6121
|
if (info.cls === "TextEncoder")
|
|
@@ -5451,9 +6125,9 @@ function erasableTextCodecCtor(info) {
|
|
|
5451
6125
|
if (args.length !== 1)
|
|
5452
6126
|
return false;
|
|
5453
6127
|
const label = stripTypeCasts(args[0]);
|
|
5454
|
-
return ts.isStringLiteralLike(label) && (label.text
|
|
6128
|
+
return ts.isStringLiteralLike(label) && staticTextDecoderEncoding(label.text) !== null;
|
|
5455
6129
|
}
|
|
5456
|
-
/** `const encoder = new TextEncoder()` /
|
|
6130
|
+
/** `const encoder = new TextEncoder()` / a statically-labelled TextDecoder twin:
|
|
5457
6131
|
* compile-time alias plumbing with no runtime object. Calls through the
|
|
5458
6132
|
* stable binding are recognized by textCodecReceiverOf below; any other
|
|
5459
6133
|
* reached value use keeps the ordinary SC2020 representation fence. Both
|
|
@@ -5548,8 +6222,9 @@ function textCodecReceiverOf(L, expr) {
|
|
|
5548
6222
|
* the idiomatic store-then-call equivalents. The codec object never
|
|
5549
6223
|
* exists (the crypto/Date precedent); bare construction and value uses
|
|
5550
6224
|
* are fenced with the composed hint. decode is the runtime's WHATWG
|
|
5551
|
-
*
|
|
5552
|
-
* is "" like the spec's.
|
|
6225
|
+
* decode for every recognized static label (with BOM handling for the
|
|
6226
|
+
* Unicode encodings); a zero-argument decode() is "" like the spec's.
|
|
6227
|
+
* encode IS Buffer.from(s, "utf8") — ScrStr
|
|
5553
6228
|
* storage is well-formed UTF-8, so the bytes are identical (lone
|
|
5554
6229
|
* surrogates became U+FFFD at string construction, exactly what the
|
|
5555
6230
|
* spec's encoder emits). Null when this is neither supported form. */
|
|
@@ -5569,17 +6244,33 @@ export function lowerTextCodecCall(L, call, access) {
|
|
|
5569
6244
|
const loc = locOf(call);
|
|
5570
6245
|
const ctorArgs = recv.arguments ?? [];
|
|
5571
6246
|
if (cls === "TextDecoder") {
|
|
5572
|
-
// The
|
|
5573
|
-
//
|
|
5574
|
-
//
|
|
6247
|
+
// The label must be statically known. A literal-typed effectful inline
|
|
6248
|
+
// expression is accepted, but its evaluation is sequenced before the
|
|
6249
|
+
// decode below; stored decoder aliases admit only actual literals.
|
|
5575
6250
|
const labelT = ctorArgs.length >= 1 ? L.typeOf(ctorArgs[0]) : null;
|
|
5576
|
-
const
|
|
5577
|
-
|
|
5578
|
-
|
|
5579
|
-
|
|
6251
|
+
const encoding = ctorArgs.length === 0
|
|
6252
|
+
? { kind: "utf8" }
|
|
6253
|
+
: labelT !== null && labelT.isStringLiteralType()
|
|
6254
|
+
? staticTextDecoderEncoding(labelT.value)
|
|
6255
|
+
: null;
|
|
6256
|
+
if (ctorArgs.length > 1 || encoding === null) {
|
|
6257
|
+
L.noLowering("new TextDecoder with runtime-valued options or an unknown label", recv, "a recognized literal WHATWG label with default options compiles");
|
|
6258
|
+
}
|
|
6259
|
+
const labelEffect = ctorArgs.length === 1
|
|
6260
|
+
? L.lowerExprExpecting(ctorArgs[0], STRING)
|
|
6261
|
+
: null;
|
|
6262
|
+
const afterLabel = (result) => labelEffect === null || labelEffect.kind === "strLit"
|
|
6263
|
+
? result
|
|
6264
|
+
: {
|
|
6265
|
+
kind: "seqExpr",
|
|
6266
|
+
stmts: [{ kind: "exprStmt", expr: labelEffect, loc: labelEffect.loc }],
|
|
6267
|
+
result,
|
|
6268
|
+
type: result.type,
|
|
6269
|
+
loc,
|
|
6270
|
+
};
|
|
5580
6271
|
if (call.arguments.length === 0) {
|
|
5581
6272
|
// decode() with no input is "" per spec — nothing to evaluate.
|
|
5582
|
-
return { kind: "strLit", value: "", type: STRING, loc };
|
|
6273
|
+
return afterLabel({ kind: "strLit", value: "", type: STRING, loc });
|
|
5583
6274
|
}
|
|
5584
6275
|
if (call.arguments.length !== 1) {
|
|
5585
6276
|
L.noLowering("decode with a stream option", call, "streaming decode has no lowering — decode whole buffers");
|
|
@@ -5589,7 +6280,16 @@ export function lowerTextCodecCall(L, call, access) {
|
|
|
5589
6280
|
if (!(arg.type.kind === "bytes" && arg.type.elem === "u8")) {
|
|
5590
6281
|
L.noLowering(`TextDecoder.decode of '${L.fmt(arg.type)}' values`, argNode, "Uint8Array/Buffer input decodes (ArrayBuffer values have no representation)");
|
|
5591
6282
|
}
|
|
5592
|
-
|
|
6283
|
+
const decoded = encoding.kind === "utf8"
|
|
6284
|
+
? { kind: "libCall", fn: "text.decode", args: [arg], type: STRING, loc }
|
|
6285
|
+
: {
|
|
6286
|
+
kind: "libCall",
|
|
6287
|
+
fn: "text.decodeLegacy",
|
|
6288
|
+
args: [arg, { kind: "numLit", value: encoding.id, type: F64, loc }],
|
|
6289
|
+
type: STRING,
|
|
6290
|
+
loc,
|
|
6291
|
+
};
|
|
6292
|
+
return afterLabel(decoded);
|
|
5593
6293
|
}
|
|
5594
6294
|
if (ctorArgs.length > 0) {
|
|
5595
6295
|
L.noLowering("new TextEncoder with arguments", recv);
|