@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.
Files changed (93) hide show
  1. package/ambient/scriptc-node-fallback.d.ts +139 -14
  2. package/dist/backend/cc.d.ts +13 -0
  3. package/dist/backend/cc.js +11 -0
  4. package/dist/backend/cc.js.map +1 -1
  5. package/dist/backend/emission/emit-async.d.ts +4 -0
  6. package/dist/backend/emission/emit-async.js +34 -2
  7. package/dist/backend/emission/emit-async.js.map +1 -1
  8. package/dist/backend/emission/emit-exprs.d.ts +10 -0
  9. package/dist/backend/emission/emit-exprs.js +272 -22
  10. package/dist/backend/emission/emit-exprs.js.map +1 -1
  11. package/dist/backend/emission/emit-shapes.js +1 -0
  12. package/dist/backend/emission/emit-shapes.js.map +1 -1
  13. package/dist/backend/emission/emit-stmts.js +36 -9
  14. package/dist/backend/emission/emit-stmts.js.map +1 -1
  15. package/dist/backend/emission/emit-types.js +14 -1
  16. package/dist/backend/emission/emit-types.js.map +1 -1
  17. package/dist/backend/emission/emitter.d.ts +19 -1
  18. package/dist/backend/emission/emitter.js +81 -2
  19. package/dist/backend/emission/emitter.js.map +1 -1
  20. package/dist/backend/llvm/emitter.js +578 -36
  21. package/dist/backend/llvm/emitter.js.map +1 -1
  22. package/dist/backend/llvm/shapes.js +12 -3
  23. package/dist/backend/llvm/shapes.js.map +1 -1
  24. package/dist/backend/mangle.d.ts +3 -0
  25. package/dist/backend/mangle.js +5 -0
  26. package/dist/backend/mangle.js.map +1 -1
  27. package/dist/compat/fetch-profile.d.ts +10 -0
  28. package/dist/compat/fetch-profile.js +55 -6
  29. package/dist/compat/fetch-profile.js.map +1 -1
  30. package/dist/coverage/surface-manifest.js +16 -1
  31. package/dist/coverage/surface-manifest.js.map +1 -1
  32. package/dist/diagnostics/diagnostic.d.ts +10 -0
  33. package/dist/diagnostics/diagnostic.js +27 -1
  34. package/dist/diagnostics/diagnostic.js.map +1 -1
  35. package/dist/frontend/lowering/lower-builtins.d.ts +17 -16
  36. package/dist/frontend/lowering/lower-builtins.js +817 -117
  37. package/dist/frontend/lowering/lower-builtins.js.map +1 -1
  38. package/dist/frontend/lowering/lower-calls.js +3 -1
  39. package/dist/frontend/lowering/lower-calls.js.map +1 -1
  40. package/dist/frontend/lowering/lower-classes.js +28 -8
  41. package/dist/frontend/lowering/lower-classes.js.map +1 -1
  42. package/dist/frontend/lowering/lower-containers.d.ts +2 -1
  43. package/dist/frontend/lowering/lower-containers.js +99 -32
  44. package/dist/frontend/lowering/lower-containers.js.map +1 -1
  45. package/dist/frontend/lowering/lower-exprs.d.ts +1 -1
  46. package/dist/frontend/lowering/lower-exprs.js +78 -6
  47. package/dist/frontend/lowering/lower-exprs.js.map +1 -1
  48. package/dist/frontend/lowering/lower-generators.js +7 -1
  49. package/dist/frontend/lowering/lower-generators.js.map +1 -1
  50. package/dist/frontend/lowering/lower-inspect.js +59 -1
  51. package/dist/frontend/lowering/lower-inspect.js.map +1 -1
  52. package/dist/frontend/lowering/lower-island.d.ts +21 -0
  53. package/dist/frontend/lowering/lower-island.js +284 -10
  54. package/dist/frontend/lowering/lower-island.js.map +1 -1
  55. package/dist/frontend/lowering/lower-server.d.ts +5 -0
  56. package/dist/frontend/lowering/lower-server.js +1 -1
  57. package/dist/frontend/lowering/lower-server.js.map +1 -1
  58. package/dist/frontend/lowering/lower-stmts.d.ts +6 -2
  59. package/dist/frontend/lowering/lower-stmts.js +53 -14
  60. package/dist/frontend/lowering/lower-stmts.js.map +1 -1
  61. package/dist/frontend/lowering/lowerer.d.ts +3 -2
  62. package/dist/frontend/lowering/lowerer.js +17 -8
  63. package/dist/frontend/lowering/lowerer.js.map +1 -1
  64. package/dist/frontend/lowering/surfaces.js +108 -25
  65. package/dist/frontend/lowering/surfaces.js.map +1 -1
  66. package/dist/frontend/program.d.ts +4 -4
  67. package/dist/frontend/program.js +264 -46
  68. package/dist/frontend/program.js.map +1 -1
  69. package/dist/frontend/resolve.d.ts +10 -0
  70. package/dist/frontend/resolve.js +37 -0
  71. package/dist/frontend/resolve.js.map +1 -1
  72. package/dist/frontend/ts7/enums.d.ts +9 -0
  73. package/dist/frontend/ts7/enums.js +8 -6
  74. package/dist/frontend/ts7/enums.js.map +1 -1
  75. package/dist/frontend/ts7/program.js +4 -1
  76. package/dist/frontend/ts7/program.js.map +1 -1
  77. package/dist/frontend/types.d.ts +7 -4
  78. package/dist/frontend/types.js +103 -8
  79. package/dist/frontend/types.js.map +1 -1
  80. package/dist/index.js +8 -1
  81. package/dist/index.js.map +1 -1
  82. package/dist/ir/integer-loops.d.ts +23 -0
  83. package/dist/ir/integer-loops.js +82 -0
  84. package/dist/ir/integer-loops.js.map +1 -0
  85. package/dist/ir/nodes.d.ts +104 -28
  86. package/dist/ir/nodes.js +89 -1
  87. package/dist/ir/nodes.js.map +1 -1
  88. package/dist/ir/validate.js +136 -25
  89. package/dist/ir/validate.js.map +1 -1
  90. package/dist/library/fence-eval.js +4 -3
  91. package/dist/library/fence-eval.js.map +1 -1
  92. package/package.json +2 -2
  93. 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): the lowered options are a literal
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 — the Buffer form
1159
- // never carries options in the supported corpus.
1160
- if (bi.module === "fs" && bi.member === "writeFileSync" && expr.arguments.length === 3) {
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
- const path = L.lowerExprExpecting(expr.arguments[0], STRING);
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
- let modeNode = null;
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
- modeNode = m.value;
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("writeFileSync with the flag option", p, "the write truncates-or-creates (Node's default 'w') — appendFileSync is the lowered append; other flags have no lowering");
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, "writeFileSync", FS_WRITE_FILE_DOCUMENTED_OPTIONS, 'the supported options are { mode: <number>, encoding: "utf8" }');
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("fs.writeFileSync with 3 arguments", optsNode, 'the supported options are { mode: <number>, encoding: "utf8" } over string data');
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
- const mode = L.lowerExprExpecting(modeNode, F64);
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): Stats.mtimeMs (milliseconds with the nanosecond fraction,
3737
- * Node's arithmetic) and SpawnSyncReturns.signal (the termination
3738
- * signal's name as the call site's `Signals | null` union — null for a
3739
- * normal exit or spawn failure; a timeout kill reports its killSignal,
3740
- * Node's shape). Null for everything else, so the ordinary chain (and
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" && name === "mtimeMs") {
4274
+ if (kind === "stats" &&
4275
+ (name === "blocks" || name === "nlink" || name === "atimeMs" || name === "mtimeMs")) {
3783
4276
  const receiver = L.lowerExpr(expr.expression);
3784
- return { kind: "libCall", fn: "stats.mtimeMs", args: [receiver], type: F64, loc };
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) / process.stderr.write(s): the raw byte
4760
- // write — no newline, no formatting. stdout shares console.log's
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
- // @types/node's wider forms (Buffer data, encoding, callback) typecheck
4764
- // and fence here.
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
- if (call.arguments.length !== 1) {
4849
- L.noLowering(`process.${stream}.write with ${call.arguments.length} arguments`, call, "the supported form is write(data) with one string — encodings and callbacks have no lowering");
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
- let data = L.lowerExpr(call.arguments[0]);
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
- // The Buffer overload writes raw bytes through the same promptly
4861
- // submitted streams.
4862
- if (data.type.kind === "bytes" && data.type.elem === "u8") {
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: [data],
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
- /** The Date slice: `Date.now()` and the COMPOSED
5330
- * `new Date(ms?).toISOString()` — the Date object between the two calls
5331
- * never exists (the crypto randomBytes(n).toString(enc) precedent). An
5332
- * omitted constructor argument completes with date.now; a one-argument
5333
- * form takes the milliseconds. String parsing, Y/M/D fields (local-time
5334
- * semantics), and Date VALUES stay fenced — lowerNew carries the hint.
5335
- * Null when this is neither form. */
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 (access.name.text !== "toISOString" && access.name.text !== "getTime")
5992
+ if (L.mapTypeOf(L.typeOf(access.expression))?.kind !== "date")
5367
5993
  return null;
5368
- const recv = access.expression;
5369
- if (!ts.isNewExpression(recv) || !ts.isIdentifier(recv.expression))
5370
- return null;
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("toISOString with arguments", call);
5998
+ L.noLowering(`Date.prototype.${name} with arguments`, call, DATE_METHOD_HINT);
5403
5999
  }
5404
- const ctorArgs = recv.arguments ?? [];
5405
- let ms;
5406
- if (ctorArgs.length === 0) {
5407
- ms = { kind: "libCall", fn: "date.now", args: [], type: F64, loc };
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
- else if (ctorArgs.length === 1) {
5410
- const arg = L.lowerExpr(ctorArgs[0]);
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
- else {
5417
- L.noLowering("new Date with year/month/day arguments", recv, "the field constructor is local-time; only new Date() and new Date(ms) compose with .toISOString()");
6015
+ const fn = DATE_GETTER_FNS[name];
6016
+ if (fn !== undefined) {
6017
+ return { kind: "libCall", fn, args: [receiver], type: F64, loc };
5418
6018
  }
5419
- return { kind: "libCall", fn: "date.toISOString", args: [ms], type: STRING, loc };
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 merely typed
5444
- * as "utf-8" would move or repeat its effects when the binding is erased. */
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 === "utf-8" || label.text === "utf8");
6128
+ return ts.isStringLiteralLike(label) && staticTextDecoderEncoding(label.text) !== null;
5455
6129
  }
5456
- /** `const encoder = new TextEncoder()` / the default TextDecoder twin:
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
- * utf-8 decode with the leading BOM stripped; a zero-argument decode()
5552
- * is "" like the spec's. encode IS Buffer.from(s, "utf8") — ScrStr
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 constructor may spell the default label; anything else (other
5573
- // labels, { fatal }/{ ignoreBOM } options) changes behavior the
5574
- // runtime doesn't implement.
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 utf8Label = labelT !== null && labelT.isStringLiteralType() && (labelT.value === "utf-8" || labelT.value === "utf8");
5577
- if (ctorArgs.length > 1 || (ctorArgs.length === 1 && !utf8Label)) {
5578
- L.noLowering("new TextDecoder beyond the default utf-8", recv, "utf-8 with default options is the supported decoder: new TextDecoder().decode(bytes)");
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
- return { kind: "libCall", fn: "text.decode", args: [arg], type: STRING, loc };
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);