@scriptc/compiler 0.0.23 → 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 +100 -11
- 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 +31 -1
- package/dist/backend/emission/emit-async.js.map +1 -1
- package/dist/backend/emission/emit-exprs.js +147 -14
- 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 +24 -5
- package/dist/backend/emission/emit-stmts.js.map +1 -1
- package/dist/backend/emission/emit-types.js +10 -0
- package/dist/backend/emission/emit-types.js.map +1 -1
- package/dist/backend/emission/emitter.d.ts +10 -2
- package/dist/backend/emission/emitter.js +49 -30
- package/dist/backend/emission/emitter.js.map +1 -1
- package/dist/backend/llvm/emitter.js +284 -25
- package/dist/backend/llvm/emitter.js.map +1 -1
- package/dist/backend/llvm/shapes.js +9 -1
- 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/frontend/lowering/lower-builtins.d.ts +14 -9
- package/dist/frontend/lowering/lower-builtins.js +737 -62
- package/dist/frontend/lowering/lower-builtins.js.map +1 -1
- package/dist/frontend/lowering/lower-calls.js +2 -1
- package/dist/frontend/lowering/lower-calls.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.js +53 -2
- package/dist/frontend/lowering/lower-exprs.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-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 +45 -10
- package/dist/frontend/lowering/lower-stmts.js.map +1 -1
- package/dist/frontend/lowering/lowerer.d.ts +2 -2
- package/dist/frontend/lowering/lowerer.js +11 -6
- package/dist/frontend/lowering/lowerer.js.map +1 -1
- package/dist/frontend/lowering/surfaces.js +65 -13
- package/dist/frontend/lowering/surfaces.js.map +1 -1
- package/dist/frontend/types.d.ts +3 -0
- package/dist/frontend/types.js +78 -0
- 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 +84 -21
- package/dist/ir/nodes.js +84 -1
- package/dist/ir/nodes.js.map +1 -1
- package/dist/ir/validate.js +92 -18
- 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 +50 -2
|
@@ -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,82 @@ 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
|
+
}
|
|
788
924
|
// fs.readSync(fd, buffer, offset, length[, position]) — normalize the
|
|
789
925
|
// current-offset forms to Node/libuv's -1 sentinel so the IR and native
|
|
790
926
|
// ABI stay fixed-width. A real numeric position is a positioned read
|
|
@@ -820,6 +956,80 @@ export function lowerBuiltinModuleCall(L, expr, bi, fn, loc) {
|
|
|
820
956
|
}
|
|
821
957
|
return { kind: "libCall", fn: "fs.readSync", args, type: F64, loc };
|
|
822
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
|
+
}
|
|
823
1033
|
if (bi.module === "child_process" &&
|
|
824
1034
|
(bi.member === "execFileSync" || bi.member === "execSync")) {
|
|
825
1035
|
return L.lowerExecSyncCall(expr, bi.member === "execSync", loc);
|
|
@@ -1186,14 +1396,54 @@ export function lowerBuiltinModuleCall(L, expr, bi, fn, loc) {
|
|
|
1186
1396
|
return { kind: "libCall", fn: "fs.writeFileSyncBytes", args: [path, data], type: VOID, loc };
|
|
1187
1397
|
}
|
|
1188
1398
|
}
|
|
1189
|
-
// writeFileSync(p, data, options)
|
|
1399
|
+
// writeFileSync(p, data, options) / fs.promises.writeFile(p, data,
|
|
1400
|
+
// options): the lowered options are a literal
|
|
1190
1401
|
// `{ mode?: <number>, encoding?: "utf8" }` — the mode is open(2)'s
|
|
1191
1402
|
// O_CREAT argument (creation only; an existing file keeps its
|
|
1192
1403
|
// permissions, exactly Node), and the encoding may only spell the
|
|
1193
|
-
// utf8 the runtime writes anyway. String data only —
|
|
1194
|
-
//
|
|
1195
|
-
|
|
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) {
|
|
1196
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
|
+
};
|
|
1197
1447
|
// The bare-encoding spelling — writeFileSync(p, data, "utf-8") — is
|
|
1198
1448
|
// the options record's encoding key alone: utf8 is what the runtime
|
|
1199
1449
|
// writes anyway, so string data takes the plain write. Any OTHER
|
|
@@ -1202,12 +1452,10 @@ export function lowerBuiltinModuleCall(L, expr, bi, fn, loc) {
|
|
|
1202
1452
|
const t = L.typeOf(optsNode);
|
|
1203
1453
|
if (t.isStringLiteralType() && (t.value === "utf8" || t.value === "utf-8") &&
|
|
1204
1454
|
L.mapTypeOf(L.typeOf(expr.arguments[1]))?.kind === "string") {
|
|
1205
|
-
|
|
1206
|
-
const data = L.lowerExprExpecting(expr.arguments[1], STRING);
|
|
1207
|
-
return { kind: "libCall", fn: "fs.writeFileSync", args: [path, data], type: VOID, loc };
|
|
1455
|
+
return finishWrite([{ kind: "effect", value: L.lowerExprExpecting(optsNode, STRING) }]);
|
|
1208
1456
|
}
|
|
1209
1457
|
}
|
|
1210
|
-
|
|
1458
|
+
const optionValues = [];
|
|
1211
1459
|
let ok = ts.isObjectLiteralExpression(optsNode);
|
|
1212
1460
|
if (ok) {
|
|
1213
1461
|
for (const p of optsNode.properties) {
|
|
@@ -1217,7 +1465,7 @@ export function lowerBuiltinModuleCall(L, expr, bi, fn, loc) {
|
|
|
1217
1465
|
break;
|
|
1218
1466
|
}
|
|
1219
1467
|
if (m.name === "mode") {
|
|
1220
|
-
|
|
1468
|
+
optionValues.push({ kind: "mode", value: L.lowerExprExpecting(m.value, F64) });
|
|
1221
1469
|
}
|
|
1222
1470
|
else if (m.name === "encoding") {
|
|
1223
1471
|
const t = L.typeOf(m.value);
|
|
@@ -1225,30 +1473,24 @@ export function lowerBuiltinModuleCall(L, expr, bi, fn, loc) {
|
|
|
1225
1473
|
ok = false;
|
|
1226
1474
|
break;
|
|
1227
1475
|
}
|
|
1476
|
+
optionValues.push({ kind: "effect", value: L.lowerExprExpecting(m.value, STRING) });
|
|
1228
1477
|
}
|
|
1229
1478
|
else if (m.name === "flag") {
|
|
1230
1479
|
// Documented, behavior-changing (open(2)'s disposition — 'a'
|
|
1231
1480
|
// IS appendFileSync), no lowering: fence by name.
|
|
1232
|
-
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");
|
|
1233
1482
|
}
|
|
1234
1483
|
else {
|
|
1235
1484
|
// The options-record stance: documented keys with no lowering
|
|
1236
1485
|
// fence by name; undocumented keys drop like Node.
|
|
1237
|
-
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" }');
|
|
1238
1487
|
}
|
|
1239
1488
|
}
|
|
1240
1489
|
}
|
|
1241
1490
|
if (!ok || L.mapTypeOf(L.typeOf(expr.arguments[1]))?.kind !== "string") {
|
|
1242
|
-
L.noLowering(
|
|
1243
|
-
}
|
|
1244
|
-
const path = L.lowerExprExpecting(expr.arguments[0], STRING);
|
|
1245
|
-
const data = L.lowerExprExpecting(expr.arguments[1], STRING);
|
|
1246
|
-
if (modeNode === null) {
|
|
1247
|
-
// encoding-only options change nothing: the plain write.
|
|
1248
|
-
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');
|
|
1249
1492
|
}
|
|
1250
|
-
|
|
1251
|
-
return { kind: "libCall", fn: "fs.writeFileModeSync", args: [path, data, mode], type: VOID, loc };
|
|
1493
|
+
return finishWrite(optionValues);
|
|
1252
1494
|
}
|
|
1253
1495
|
// zlib takes Buffers; a string argument (the lib admits it) gets the
|
|
1254
1496
|
// wrap-it-first hint instead of a generic type mismatch.
|
|
@@ -3707,6 +3949,211 @@ function buildSpForEachFn(name, arity, fnRet, loc) {
|
|
|
3707
3949
|
loc,
|
|
3708
3950
|
};
|
|
3709
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
|
+
}
|
|
3710
4157
|
/** Method calls on Stats-typed receivers: isFile()/isDirectory()/
|
|
3711
4158
|
* isSymbolicLink() are pure reads on the stat snapshot (a followed
|
|
3712
4159
|
* statSync snapshot never answers true to isSymbolicLink — take
|
|
@@ -3729,7 +4176,7 @@ export function lowerStatsMethodCall(L, call, access) {
|
|
|
3729
4176
|
: "stats.isSymbolicLink";
|
|
3730
4177
|
return { kind: "libCall", fn, args: [receiver], type: BOOL, loc: locOf(call) };
|
|
3731
4178
|
}
|
|
3732
|
-
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));
|
|
3733
4180
|
}
|
|
3734
4181
|
/** `Atomics.wait(int32Array, idx, expected, timeoutMs)` — the
|
|
3735
4182
|
* synchronous-sleep idiom (RouteStore's
|
|
@@ -3768,12 +4215,11 @@ export function lowerAtomicsCall(L, call, access) {
|
|
|
3768
4215
|
}
|
|
3769
4216
|
/** The property-read extensions this spoke owns, tried BEFORE the
|
|
3770
4217
|
* lower-exprs intrinsic-property fallback (the lowerer's wrapper chains
|
|
3771
|
-
* them):
|
|
3772
|
-
*
|
|
3773
|
-
*
|
|
3774
|
-
*
|
|
3775
|
-
*
|
|
3776
|
-
* 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. */
|
|
3777
4223
|
export function lowerBuiltinExtraProperty(L, expr) {
|
|
3778
4224
|
if (expr.questionDotToken && !L.chainHandled.has(expr))
|
|
3779
4225
|
return null;
|
|
@@ -3787,12 +4233,23 @@ export function lowerBuiltinExtraProperty(L, expr) {
|
|
|
3787
4233
|
return { kind: "recordGet", obj: receiver, shapeId: receiver.type.shapeId, field: "%enc", type: STRING, loc: locOf(expr) };
|
|
3788
4234
|
}
|
|
3789
4235
|
const kind = L.mapTypeOf(L.typeOf(expr.expression))?.kind;
|
|
3790
|
-
if (kind !== "stats" && kind !== "spawnRes" && kind !== "child")
|
|
4236
|
+
if (kind !== "stats" && kind !== "fileHandle" && kind !== "spawnRes" && kind !== "child")
|
|
3791
4237
|
return null;
|
|
3792
4238
|
if (kind === "child" ? !isChildSurfaceMember(L, expr) : !L.isStdlibMember(expr))
|
|
3793
4239
|
return null;
|
|
3794
4240
|
const name = expr.name.text;
|
|
3795
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
|
+
}
|
|
3796
4253
|
// child.stdout / child.stderr — the piped-output streams: the
|
|
3797
4254
|
// checker's `Readable | null` (null exactly when the slot was not
|
|
3798
4255
|
// piped), constructed type-directedly in the backend over the
|
|
@@ -3814,9 +4271,11 @@ export function lowerBuiltinExtraProperty(L, expr) {
|
|
|
3814
4271
|
}
|
|
3815
4272
|
if (kind === "child")
|
|
3816
4273
|
return null; // pid/exitCode/killed live in lowerIntrinsicProperty
|
|
3817
|
-
if (kind === "stats" &&
|
|
4274
|
+
if (kind === "stats" &&
|
|
4275
|
+
(name === "blocks" || name === "nlink" || name === "atimeMs" || name === "mtimeMs")) {
|
|
3818
4276
|
const receiver = L.lowerExpr(expr.expression);
|
|
3819
|
-
|
|
4277
|
+
const fn = `stats.${name}`;
|
|
4278
|
+
return { kind: "libCall", fn, args: [receiver], type: F64, loc };
|
|
3820
4279
|
}
|
|
3821
4280
|
if (kind === "spawnRes" && name === "signal") {
|
|
3822
4281
|
const receiver = L.lowerExpr(expr.expression);
|
|
@@ -4791,12 +5250,13 @@ export function lowerProcessEnvGet(L, expr) {
|
|
|
4791
5250
|
export function lowerProcessMethodCall(L, call, access) {
|
|
4792
5251
|
if (call.questionDotToken)
|
|
4793
5252
|
return null;
|
|
4794
|
-
// process.stdout.write(s)
|
|
4795
|
-
//
|
|
5253
|
+
// process.stdout.write(s[, encoding][, callback]) and stderr's twin:
|
|
5254
|
+
// raw bytes, no newline or formatting. stdout shares console.log's
|
|
4796
5255
|
// promptly-submitted stream, preserving source order. Node's boolean
|
|
4797
5256
|
// is a backpressure signal; this synchronous write is constantly true.
|
|
4798
|
-
//
|
|
4799
|
-
//
|
|
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`.
|
|
4800
5260
|
// process.stdin.destroy(): a deliberate no-op — no stream machinery
|
|
4801
5261
|
// exists to tear down, and no other stdin surface observes the
|
|
4802
5262
|
// destroyed state (SEMANTICS.md documents it).
|
|
@@ -4880,10 +5340,64 @@ export function lowerProcessMethodCall(L, call, access) {
|
|
|
4880
5340
|
const stream = L.stdlibGlobalMember(access.expression, "process");
|
|
4881
5341
|
if (stream === "stdout" || stream === "stderr") {
|
|
4882
5342
|
const loc = locOf(call);
|
|
4883
|
-
|
|
4884
|
-
|
|
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");
|
|
4885
5346
|
}
|
|
4886
|
-
|
|
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]);
|
|
4887
5401
|
// A checked-dynamic argument in a JS file takes the validated
|
|
4888
5402
|
// string exit (the trust-but-verify boundary: commander's
|
|
4889
5403
|
// `writeOut: (str) => process.stdout.write(str)` — str untyped):
|
|
@@ -4892,20 +5406,81 @@ export function lowerProcessMethodCall(L, call, access) {
|
|
|
4892
5406
|
if (data.type.kind === "dyn" && isJsSourceFile(call.getSourceFile())) {
|
|
4893
5407
|
data = { kind: "dynCheck", value: data, type: STRING, loc };
|
|
4894
5408
|
}
|
|
4895
|
-
|
|
4896
|
-
|
|
4897
|
-
|
|
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
|
+
}
|
|
4898
5476
|
return {
|
|
4899
5477
|
kind: "libCall",
|
|
4900
5478
|
fn: stream === "stdout" ? "process.stdoutWriteBytes" : "process.stderrWriteBytes",
|
|
4901
|
-
args: [
|
|
5479
|
+
args: [bytes, runtimeEncoding],
|
|
4902
5480
|
type: BOOL,
|
|
4903
5481
|
loc,
|
|
4904
5482
|
};
|
|
4905
5483
|
}
|
|
4906
|
-
if (data.type.kind !== "string") {
|
|
4907
|
-
L.noLowering(`process.${stream}.write of non-string data`, call.arguments[0], "strings and Buffer/Uint8Array values write; narrow unions first");
|
|
4908
|
-
}
|
|
4909
5484
|
return {
|
|
4910
5485
|
kind: "libCall",
|
|
4911
5486
|
fn: stream === "stdout" ? "process.stdoutWrite" : "process.stderrWrite",
|
|
@@ -5443,6 +6018,79 @@ export function lowerDateCall(L, call, access) {
|
|
|
5443
6018
|
}
|
|
5444
6019
|
L.noLowering(`Date.prototype.${name}`, call, DATE_METHOD_HINT, L.checker.getSymbolAtLocation(access.name));
|
|
5445
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 };
|
|
6093
|
+
}
|
|
5446
6094
|
/** A direct construction of THE stdlib TextEncoder/TextDecoder, through
|
|
5447
6095
|
* type-only wrappers. Name alone is never enough: a user class with the
|
|
5448
6096
|
* same spelling keeps the ordinary class lowering. */
|
|
@@ -5465,8 +6113,9 @@ function directTextCodecCtorOf(L, expr) {
|
|
|
5465
6113
|
/** True when construction itself is effect-free and inside the already
|
|
5466
6114
|
* lowered codec slice, so a const binding can be erased and calls can
|
|
5467
6115
|
* resolve back to the initializer. TextDecoder's explicit label must be
|
|
5468
|
-
* an actual literal here: accepting an arbitrary expression
|
|
5469
|
-
* 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. */
|
|
5470
6119
|
function erasableTextCodecCtor(info) {
|
|
5471
6120
|
const args = info.ctor.arguments ?? [];
|
|
5472
6121
|
if (info.cls === "TextEncoder")
|
|
@@ -5476,9 +6125,9 @@ function erasableTextCodecCtor(info) {
|
|
|
5476
6125
|
if (args.length !== 1)
|
|
5477
6126
|
return false;
|
|
5478
6127
|
const label = stripTypeCasts(args[0]);
|
|
5479
|
-
return ts.isStringLiteralLike(label) && (label.text
|
|
6128
|
+
return ts.isStringLiteralLike(label) && staticTextDecoderEncoding(label.text) !== null;
|
|
5480
6129
|
}
|
|
5481
|
-
/** `const encoder = new TextEncoder()` /
|
|
6130
|
+
/** `const encoder = new TextEncoder()` / a statically-labelled TextDecoder twin:
|
|
5482
6131
|
* compile-time alias plumbing with no runtime object. Calls through the
|
|
5483
6132
|
* stable binding are recognized by textCodecReceiverOf below; any other
|
|
5484
6133
|
* reached value use keeps the ordinary SC2020 representation fence. Both
|
|
@@ -5573,8 +6222,9 @@ function textCodecReceiverOf(L, expr) {
|
|
|
5573
6222
|
* the idiomatic store-then-call equivalents. The codec object never
|
|
5574
6223
|
* exists (the crypto/Date precedent); bare construction and value uses
|
|
5575
6224
|
* are fenced with the composed hint. decode is the runtime's WHATWG
|
|
5576
|
-
*
|
|
5577
|
-
* 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
|
|
5578
6228
|
* storage is well-formed UTF-8, so the bytes are identical (lone
|
|
5579
6229
|
* surrogates became U+FFFD at string construction, exactly what the
|
|
5580
6230
|
* spec's encoder emits). Null when this is neither supported form. */
|
|
@@ -5594,17 +6244,33 @@ export function lowerTextCodecCall(L, call, access) {
|
|
|
5594
6244
|
const loc = locOf(call);
|
|
5595
6245
|
const ctorArgs = recv.arguments ?? [];
|
|
5596
6246
|
if (cls === "TextDecoder") {
|
|
5597
|
-
// The
|
|
5598
|
-
//
|
|
5599
|
-
//
|
|
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.
|
|
5600
6250
|
const labelT = ctorArgs.length >= 1 ? L.typeOf(ctorArgs[0]) : null;
|
|
5601
|
-
const
|
|
5602
|
-
|
|
5603
|
-
|
|
5604
|
-
|
|
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
|
+
};
|
|
5605
6271
|
if (call.arguments.length === 0) {
|
|
5606
6272
|
// decode() with no input is "" per spec — nothing to evaluate.
|
|
5607
|
-
return { kind: "strLit", value: "", type: STRING, loc };
|
|
6273
|
+
return afterLabel({ kind: "strLit", value: "", type: STRING, loc });
|
|
5608
6274
|
}
|
|
5609
6275
|
if (call.arguments.length !== 1) {
|
|
5610
6276
|
L.noLowering("decode with a stream option", call, "streaming decode has no lowering — decode whole buffers");
|
|
@@ -5614,7 +6280,16 @@ export function lowerTextCodecCall(L, call, access) {
|
|
|
5614
6280
|
if (!(arg.type.kind === "bytes" && arg.type.elem === "u8")) {
|
|
5615
6281
|
L.noLowering(`TextDecoder.decode of '${L.fmt(arg.type)}' values`, argNode, "Uint8Array/Buffer input decodes (ArrayBuffer values have no representation)");
|
|
5616
6282
|
}
|
|
5617
|
-
|
|
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);
|
|
5618
6293
|
}
|
|
5619
6294
|
if (ctorArgs.length > 0) {
|
|
5620
6295
|
L.noLowering("new TextEncoder with arguments", recv);
|