diff --git a/packages/compiler/ambient/scriptc-node-fallback.d.ts b/packages/compiler/ambient/scriptc-node-fallback.d.ts index 79305a8ba9..2d8d25218d 100644 --- a/packages/compiler/ambient/scriptc-node-fallback.d.ts +++ b/packages/compiler/ambient/scriptc-node-fallback.d.ts @@ -5827,6 +5827,12 @@ declare module "net" { }, connectListener?: () => void, ): Socket; + /* A Unix domain socket client (Node's IpcNetConnectOpts and path + * overloads). */ + export function connect(path: string, connectListener?: () => void): Socket; + export function connect(options: { path: string }, connectListener?: () => void): Socket; + export function createConnection(path: string, connectListener?: () => void): Socket; + export function createConnection(options: { path: string }, connectListener?: () => void): Socket; /* The happy-eyeballs attempt budget (Node's process-wide knob; the * autoSelectFamily dial consults it): one runtime int, default 250ms * like Node's. */ diff --git a/packages/compiler/src/backend/llvm/lib-network.ts b/packages/compiler/src/backend/llvm/lib-network.ts index 63661ab4c0..5136d4aa08 100644 --- a/packages/compiler/src/backend/llvm/lib-network.ts +++ b/packages/compiler/src/backend/llvm/lib-network.ts @@ -401,6 +401,22 @@ export function emitNetworkHttpLibCall(host: LlvmEmitterContext, e: LibCallExpr) host.emitPendingCheck(); return out; } + if (e.fn === "net.connectPath" || e.fn === "net.connectPathCb") { + const args = e.args.map((a) => host.emitExpr(a)); + let cb = "null"; + if (e.fn === "net.connectPathCb") { + host.toHeap(args[2]!); + cb = args[2]!.name; + } + host.declare(`declare ptr @scr_net_connect_path_checked(ptr, i1 zeroext, ptr)`); + const t = B.tmp(); + B.line( + `${t} = call ptr @scr_net_connect_path_checked(ptr ${args[0]!.name}, i1 ${args[1]!.name}, ptr ${cb})`, + ); + const out = host.own({ name: t, type: e.type }); + host.emitPendingCheck(); + return out; + } if (e.fn === "net.sockOnData" || e.fn === "http.reqOnData") { const cbT = e.args[1]!.type; if (cbT.kind !== "func") diff --git a/packages/compiler/src/backend/llvm/lib-shared.ts b/packages/compiler/src/backend/llvm/lib-shared.ts index 182ac49aba..1051fb36e6 100644 --- a/packages/compiler/src/backend/llvm/lib-shared.ts +++ b/packages/compiler/src/backend/llvm/lib-shared.ts @@ -1967,6 +1967,8 @@ export const USES_TIMERS_LIB_FNS = new Set([ "net.listenOptsReusePortCb", "net.connect", "net.connectCb", + "net.connectPath", + "net.connectPathCb", "net.connectLookup", "net.connectLookupDyn", "net.connectAttempt", diff --git a/packages/compiler/src/frontend/lowering/completion-callbacks.ts b/packages/compiler/src/frontend/lowering/completion-callbacks.ts index 1f4013d5eb..198b35abdd 100644 --- a/packages/compiler/src/frontend/lowering/completion-callbacks.ts +++ b/packages/compiler/src/frontend/lowering/completion-callbacks.ts @@ -45,6 +45,7 @@ const COMPLETION_CALLBACKS: ReadonlyMap = new Map< ["http.requestDynCb", "last"], ["http.requestUrlCb", "last"], ["net.connectCb", 2], + ["net.connectPathCb", 2], ["net.listenCb", 2], ["net.listenOptsCb", 4], ["net.listenOptsReusePortCb", 5], diff --git a/packages/compiler/src/frontend/lowering/expressions/identifiers.ts b/packages/compiler/src/frontend/lowering/expressions/identifiers.ts index a6957401a8..7e67629258 100644 --- a/packages/compiler/src/frontend/lowering/expressions/identifiers.ts +++ b/packages/compiler/src/frontend/lowering/expressions/identifiers.ts @@ -55,6 +55,7 @@ import { varRef } from "../../../ir/build.js"; import { ambientUndefReadType, ambientUndefinedFnSymbolOf, + ambientUndefinedVarSymbolOf, contextualUndefReadType, fenceEarlyAliasUse, isTemporalGlobalRoot, @@ -862,14 +863,7 @@ export function lowerIdentifierExpr(lowerer: Lowerer, expr: ts.Identifier, loc: // Stdlib/@types/node ambients never reach here (their chokepoint // is above); only user-file declares do. { - const sym = lowerer.resolveValueSymbol(expr); - const decl = sym ? sym.declarations[0] : undefined; - if ( - decl !== undefined && - ts.isVariableDeclaration(decl) && - decl.initializer === undefined && - ts.getCombinedModifierFlags(decl) & ts.ModifierFlags.Ambient - ) { + if (ambientUndefinedVarSymbolOf(lowerer, expr)) { const declared = lowerer.mapTypeOf(lowerer.typeOf(expr)); if (declared && declared.kind !== "void") { return { diff --git a/packages/compiler/src/frontend/lowering/expressions/typeof.ts b/packages/compiler/src/frontend/lowering/expressions/typeof.ts index f6743768a8..5f1e4a8307 100644 --- a/packages/compiler/src/frontend/lowering/expressions/typeof.ts +++ b/packages/compiler/src/frontend/lowering/expressions/typeof.ts @@ -34,7 +34,11 @@ import { varRef } from "../../../ir/build.js"; import { isExecExceptionType, refuseExecExceptionCode } from "../builtins/errors.js"; import { InternalCompilerError } from "../../../errors.js"; import { TEMPORAL_GLOBAL } from "../../builtin-modules.js"; -import { isTemporalGlobalRoot } from "../lower-namespaces.js"; +import { + ambientUndefinedFnSymbolOf, + ambientUndefinedVarSymbolOf, + isTemporalGlobalRoot, +} from "../lower-namespaces.js"; import { isF64HandleTyped } from "../f64-handles.js"; export function lowerTypeofExpr(lowerer: Lowerer, expr: ts.TypeOfExpression, loc: SrcLoc): IrExpr { @@ -220,6 +224,16 @@ export function lowerTypeofExpr(lowerer: Lowerer, expr: ts.TypeOfExpression, loc if (isTemporalGlobalRoot(lowerer, tested, sym)) { return { kind: "strLit", value: "undefined", type: STRING, loc }; } + // A name the program declares ambiently but nothing defines + // (`declare const Bun`, `declare function f(): void`): Node erases the + // declaration, so typeof answers "undefined" without reading it, where + // a read throws ReferenceError. + if ( + ambientUndefinedVarSymbolOf(lowerer, tested) || + ambientUndefinedFnSymbolOf(lowerer, tested) + ) { + return { kind: "strLit", value: "undefined", type: STRING, loc }; + } // `const { inspect } = require("util")` / `import { inspect } from // "node:util"`: a binding of a builtin module's exported function, // class or object. JavaScript sources represent such a value without diff --git a/packages/compiler/src/frontend/lowering/lower-namespaces.ts b/packages/compiler/src/frontend/lowering/lower-namespaces.ts index 13d1c29fc8..4ea102769a 100644 --- a/packages/compiler/src/frontend/lowering/lower-namespaces.ts +++ b/packages/compiler/src/frontend/lowering/lower-namespaces.ts @@ -676,6 +676,30 @@ export function ambientUndefReadType(lowerer: Lowerer, node: ts.Node): IrType | return null; } +/** The symbol of a variable a program file declares ambiently without + * an initializer (`declare const Bun: ...`), which nothing defines: Node + * erases the declaration, so a read throws the catchable ReferenceError + * " is not defined" and typeof answers "undefined". Null for other + * identifiers, including the standard library's declarations, which + * describe real runtime values. */ +export function ambientUndefinedVarSymbolOf( + lowerer: Lowerer, + ident: ts.Identifier, +): ts.Symbol | null { + const sym = lowerer.resolveValueSymbol(ident); + const decl = sym ? sym.declarations[0] : undefined; + if ( + sym === null || + decl === undefined || + lowerer.isStdlibSymbol(sym) || + !ts.isVariableDeclaration(decl) || + decl.initializer !== undefined || + !(ts.getCombinedModifierFlags(decl) & ts.ModifierFlags.Ambient) + ) + return null; + return sym; +} + /** The symbol behind `ident` when it names an ambient `declare function` * NOTHING defines: every function declaration on the symbol is body-less * and ambient in a USER program file (merge partners — the fundule's diff --git a/packages/compiler/src/frontend/lowering/lower-server.ts b/packages/compiler/src/frontend/lowering/lower-server.ts index 614d6a2067..d3132d02f3 100644 --- a/packages/compiler/src/frontend/lowering/lower-server.ts +++ b/packages/compiler/src/frontend/lowering/lower-server.ts @@ -804,6 +804,19 @@ export function lowerNetModuleCall( `the supported form is ${bi.member}(port, host?, connectListener?)`, ); } + // connect(path[, connectListener]): a string first argument names a + // Unix socket, or a localhost port when it reads as a non-negative + // number (Node's isPipeName, decided at run time). + if (lowerer.mapTypeOf(lowerer.typeOf(args[0]!))?.kind === "string") { + return lowerNetConnectPath( + lowerer, + expr, + bi.member, + lowerer.lowerExpr(args[0]!), + true, + args[1], + ); + } const port = lowerer.lowerExprExpecting(args[0]!, F64); // The optional middle host: a 2-arg call's second argument is the // host when string-typed, the connect listener when func-typed. @@ -841,6 +854,47 @@ export function lowerNetModuleCall( ); } +/** net.connect / net.createConnection of a Unix socket path (a string + * argument or the `path` option) with an optional connect listener. Node + * ignores any other argument between them for a path, so only a listener + * may follow. */ +function lowerNetConnectPath( + lowerer: Lowerer, + expr: ts.CallExpression, + member: string, + path: IrExpr, + pipeNameRule: boolean, + listener: ts.Expression | undefined, +): IrExpr { + const loc = locOf(expr); + const rule: IrExpr = { kind: "boolLit", value: pipeNameRule, type: BOOL, loc }; + if (expr.arguments.nodes.length > 2) { + lowerer.noLowering( + `${member} of a path with ${expr.arguments.nodes.length} arguments`, + expr, + `the supported form is ${member}(path[, connectListener])`, + ); + } + if (listener === undefined) + return { kind: "libCall", fn: "net.connectPath", args: [path, rule], type: NETSOCKET_T, loc }; + const { cb } = lowerCallbackArg( + lowerer, + listener, + "connect listeners", + 0, + () => false, + "use ()", + [], + ); + return { + kind: "libCall", + fn: "net.connectPathCb", + args: [path, rule, cb], + type: NETSOCKET_T, + loc, + }; +} + /** True when `t` is the lookup-function shape the connect lowering * accepts: (hostname: string, options: unknown, callback) => void where * callback is (err: , addresses: { address: @@ -929,6 +983,29 @@ function lowerNetConnectOptions( } } } + // connect({ path }[, connectListener]): the Unix-socket form. + { + const keys = optsNode.properties.nodes.map((p) => + (ts.isPropertyAssignment(p) || ts.isShorthandPropertyAssignment(p)) && + (ts.isIdentifier(p.name) || ts.isStringLiteral(p.name)) + ? p.name.text + : null, + ); + if (keys.length === 1 && keys[0] === "path") { + const prop = optsNode.properties.nodes[0]!; + const path = ts.isPropertyAssignment(prop) + ? lowerer.lowerExpr(prop.initializer) + : lowerer.lowerShorthandValue(prop as ts.ShorthandPropertyAssignment); + if (path.type.kind !== "string") { + lowerer.noLowering( + `a ${member} 'path' option of '${lowerer.fmt(path.type)}' values`, + prop, + "the path is a string here", + ); + } + return lowerNetConnectPath(lowerer, expr, member, path, false, args[1]); + } + } let port: IrExpr | null = null; let host: IrExpr | null = null; let lookup: IrExpr | null = null; diff --git a/packages/compiler/src/frontend/lowering/static-typeof.ts b/packages/compiler/src/frontend/lowering/static-typeof.ts index 852cc31175..cef2d21aa3 100644 --- a/packages/compiler/src/frontend/lowering/static-typeof.ts +++ b/packages/compiler/src/frontend/lowering/static-typeof.ts @@ -1,12 +1,14 @@ import * as ts from "../ts7/adapter.js"; import type { Lowerer } from "./lowerer.js"; +import { ambientUndefinedFnSymbolOf, ambientUndefinedVarSymbolOf } from "./lower-namespaces.js"; /** The answer `typeof ` gives without evaluating anything, for * the global-detection idioms (`typeof window < "u"`, `typeof global !== - * "undefined"`): a name nothing declares answers "undefined" (Node's - * answer for an undeclared global such as window or self, and typeof never - * throws for one), and the Node global object and process answer - * "object". Null for every other operand. */ + * "undefined"`): a name nothing declares, or one the program declares + * ambiently and nothing defines, answers "undefined" (Node's answer for an + * undeclared global such as window or self, and typeof never throws for + * one), and the Node global object and process answer "object". Null for + * every other operand. */ export function staticTypeofAnswer(lowerer: Lowerer, operand: ts.Expression): string | null { let e = operand; while (ts.isParenthesizedExpression(e)) e = e.expression; @@ -14,6 +16,10 @@ export function staticTypeofAnswer(lowerer: Lowerer, operand: ts.Expression): st if (lowerer.checker.getSymbolAtLocation(e) === undefined) return "undefined"; for (const name of ["global", "globalThis", "process"]) if (lowerer.isStdlibGlobal(e, name)) return "object"; + // A name the program declares ambiently but nothing defines (`declare + // const Bun`): undeclared at run time. + if (ambientUndefinedVarSymbolOf(lowerer, e) || ambientUndefinedFnSymbolOf(lowerer, e)) + return "undefined"; return null; } diff --git a/packages/compiler/src/ir/builtin-effects.ts b/packages/compiler/src/ir/builtin-effects.ts index 709f72d45c..a0d75c8646 100644 --- a/packages/compiler/src/ir/builtin-effects.ts +++ b/packages/compiler/src/ir/builtin-effects.ts @@ -944,6 +944,8 @@ export const MAY_THROW_LIB_FNS: ReadonlySet = new Set([ "dns.setDefaultResultOrder", "net.connect", "net.connectCb", + "net.connectPath", + "net.connectPathCb", // resetAndDestroy on a TLS socket throws ERR_INVALID_HANDLE_TYPE. "net.sockResetAndDestroy", // SocketAddress's option checks and parse()'s input check. diff --git a/packages/compiler/src/ir/builtin-signatures.ts b/packages/compiler/src/ir/builtin-signatures.ts index 0be10a37a6..843c65ed59 100644 --- a/packages/compiler/src/ir/builtin-signatures.ts +++ b/packages/compiler/src/ir/builtin-signatures.ts @@ -1554,6 +1554,15 @@ function buildLibFnSignatures() { argTypes: [F64, STRING, { kind: "func", params: [], ret: VOID }], result: NETSOCKET_T, }, + /** net.connect/createConnection(path[, connectListener]) and the + * `path` option: a client of the Unix socket at `path`. With the flag + * (a positional string), Node's isPipeName applies first: a string that + * reads as a non-negative number is a localhost port. */ + "net.connectPath": { argTypes: [STRING, BOOL], result: NETSOCKET_T }, + "net.connectPathCb": { + argTypes: [STRING, BOOL, { kind: "func", params: [], ret: VOID }], + result: NETSOCKET_T, + }, // The lookup's exact func shape is program data (its answer callback's // union/record types) — checked specially in the libCall case. /** connect({ port, host, autoSelectFamily: true, lookup }) — the diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/array-prototype-call-native-arrays.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/array-prototype-call-native-arrays.ts.json new file mode 100644 index 0000000000..2904322c51 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/array-prototype-call-native-arrays.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/array-prototype-call-native-arrays.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/net-unix-socket-connect-default-import.mjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/net-unix-socket-connect-default-import.mjs.json new file mode 100644 index 0000000000..a1bb1df19d --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/net-unix-socket-connect-default-import.mjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/net-unix-socket-connect-default-import.mjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/net-unix-socket-connect.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/net-unix-socket-connect.ts.json new file mode 100644 index 0000000000..7bf0a928d4 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/net-unix-socket-connect.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/net-unix-socket-connect.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/typeof-ambient-undefined-names.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/typeof-ambient-undefined-names.ts.json new file mode 100644 index 0000000000..a2bd8dc1c3 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/typeof-ambient-undefined-names.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/typeof-ambient-undefined-names.ts" + ], + "diags": [] +} diff --git a/packages/runtime/src/scr_dyn_invoke.c b/packages/runtime/src/scr_dyn_invoke.c index e31359efd6..8ea75df0b6 100644 --- a/packages/runtime/src/scr_dyn_invoke.c +++ b/packages/runtime/src/scr_dyn_invoke.c @@ -1801,7 +1801,10 @@ static ScrDyn *dyn_array_method_value_call(ScrClosure *closure, ScrDyn *const *a ScrStr *method = scr_box_get_ref(closure->caps[0]); ScrDyn *receiver = scr_dyn_this_get(); ScrDyn *result; - if (receiver->kind == SCR_DYN_ARR) { + /* An array, or a live view of a native array, runs the method its own + * dispatch has; other receivers take the generic array-like algorithms. */ + if (receiver->kind == SCR_DYN_ARR || + (receiver->kind == SCR_DYN_TYPED_REF && scr_dyn_typed_ref_array_ops(receiver))) { result = scr_dyn_invoke(receiver, method->data, args, argc, method->data); } else { ScrDyn *pack = scr_dyn_new_arr(); diff --git a/packages/runtime/src/scr_net.c b/packages/runtime/src/scr_net.c index a940fb7f14..776829e8c2 100644 --- a/packages/runtime/src/scr_net.c +++ b/packages/runtime/src/scr_net.c @@ -2741,6 +2741,24 @@ ScrNetSocket *scr_net_connect_checked(double port, ScrStr *host /*borrowed, null return scr_net_connect(port, host, cb); } +/* net.connect/createConnection(path[, connectListener]) and the `path` + * option from compiled code: a client of the Unix socket at `path` + * (borrowed). For a positional string (`pipe_name_rule`), Node's isPipeName + * applies first: a string that reads as a non-negative number is a port on + * localhost. `cb` (moves, nullable) is the connect listener. */ +ScrNetSocket *scr_net_connect_path_checked(ScrStr *path, bool pipe_name_rule, ScrClosure *cb) { + double port = pipe_name_rule ? scr_string_to_number(path) : -1; + if (port >= 0) { + ScrStr *host = scr_str_new("localhost", 9); + ScrNetSocket *s = scr_net_connect_checked(port, host, cb); + scr_str_release(host); + return s; + } + ScrNetSocket *s = scr_net_connect_path(path, false); + if (cb) scr_net_ls_add(&s->conn_ls, cb, NULL, true); + return s; +} + static bool scr_net_dyn_truthy(const ScrDyn *v) { switch (v->kind) { case SCR_DYN_UNDEF: diff --git a/packages/runtime/src/scr_net.h b/packages/runtime/src/scr_net.h index 6a41e26d86..af4b8e1b83 100644 --- a/packages/runtime/src/scr_net.h +++ b/packages/runtime/src/scr_net.h @@ -265,6 +265,10 @@ ScrNetSocket *scr_net_connect_lookup_dyn(double port, ScrStr *host /*borrowed*/, /* A client socket for the Unix socket at `path`, dialed now or (deferred) * by scr_net_sock_dial_start. A failure is the deferred 'error'. */ ScrNetSocket *scr_net_connect_path(ScrStr *path /*borrowed*/, bool deferred); /* +1 */ +/* net.connect(path[, cb]) and connect({ path }): with `pipe_name_rule`, a + * numeric string is a localhost port, as in Node. */ +ScrNetSocket *scr_net_connect_path_checked(ScrStr *path /*borrowed*/, bool pipe_name_rule, + ScrClosure *cb /*moves, nullable*/); /* +1 */ /* server.listen(path[, cb]): a Unix-socket server, its file removed at close. */ void scr_net_listen_path(ScrNetServer *s, ScrStr *path /*borrowed*/, ScrClosure *cb /*moves, nullable*/); void scr_net_sock_dial_start(ScrNetSocket *s); diff --git a/tests/corpus/array-prototype-call-native-arrays.ts b/tests/corpus/array-prototype-call-native-arrays.ts new file mode 100644 index 0000000000..ce198ff739 --- /dev/null +++ b/tests/corpus/array-prototype-call-native-arrays.ts @@ -0,0 +1,22 @@ +// Array.prototype methods called with .call on native arrays run the arrays' own +// methods, which mutate the array in place: copyWithin, fill, reverse, +// sort, push, pop, splice, and the read-only join, indexOf and includes. +const optional: (number | undefined)[] = [1, 2, 3, 4]; +Array.prototype.copyWithin.call(optional, 0, 2, 4); +console.log(optional.join(",")); +const nums: number[] = [5, 1, 4, 2, 3]; +Array.prototype.fill.call(nums, 0, 0, 1); +console.log(nums.join(",")); +console.log(Array.prototype.reverse.call(nums) === nums, nums.join(",")); +Array.prototype.sort.call(nums, (a: number, b: number) => a - b); +console.log(nums.join(",")); +console.log(Array.prototype.push.call(nums, 9, 10), nums.join(",")); +console.log(Array.prototype.pop.call(nums), nums.length); +console.log(Array.prototype.splice.call(nums, 1, 2).join(","), nums.join(",")); +console.log(Array.prototype.join.call(nums, "-"), Array.prototype.indexOf.call(nums, 4)); +const words: string[] = ["c", "a", "b"]; +Array.prototype.sort.call(words); +console.log(words.join(","), Array.prototype.includes.call(words, "a")); +const records: { id: number }[] = [{ id: 1 }, { id: 2 }, { id: 3 }]; +Array.prototype.copyWithin.call(records, 1, 0, 1); +console.log(records.map((r) => r.id).join(","), records[0] === records[1]); diff --git a/tests/corpus/net-unix-socket-connect-default-import.mjs b/tests/corpus/net-unix-socket-connect-default-import.mjs new file mode 100644 index 0000000000..7e58c18e2b --- /dev/null +++ b/tests/corpus/net-unix-socket-connect-default-import.mjs @@ -0,0 +1,22 @@ +// net.createConnection(path) through the default import of node:net, as a +// JavaScript module reads it: a client of a Unix domain socket server. +import net from "node:net"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; + +const dir = fs.mkdtempSync(path.join(os.tmpdir(), "scriptc-unix-default-")); +const socketPath = path.join(dir, "server.sock"); +const server = net.createServer((socket) => socket.end("hello")); +server.listen(socketPath, () => { + const sock = net.createConnection(socketPath); + sock.once("connect", () => console.log("connected")); + sock.on("data", (chunk) => console.log("data", String(chunk))); + sock.once("error", (e) => console.log("error", String(e))); + sock.on("close", () => { + server.close(() => { + fs.rmSync(dir, { recursive: true, force: true }); + console.log("closed"); + }); + }); +}); diff --git a/tests/corpus/net-unix-socket-connect.ts b/tests/corpus/net-unix-socket-connect.ts new file mode 100644 index 0000000000..77832d60df --- /dev/null +++ b/tests/corpus/net-unix-socket-connect.ts @@ -0,0 +1,69 @@ +// net.createConnection and net.connect to a Unix domain socket: a path +// argument, the `path` option, a connect listener, data both ways, the +// error for a path nothing listens on, and a numeric string, which Node +// treats as a localhost port. +import * as net from "node:net"; +import * as fs from "node:fs"; +import * as os from "node:os"; +import * as path from "node:path"; + +const dir = fs.mkdtempSync(path.join(os.tmpdir(), "scriptc-unix-connect-")); +const socketPath = path.join(dir, "server.sock"); +const missingPath = path.join(dir, "missing.sock"); + +const server = net.createServer((socket) => { + socket.on("data", (chunk) => { + socket.write(`echo:${chunk.toString()}`); + }); +}); + +function exchange(label: string, socket: net.Socket, message: string): Promise { + return new Promise((resolve) => { + socket.on("data", (chunk) => { + console.log(label, chunk.toString()); + socket.end(); + }); + socket.on("close", () => resolve()); + socket.write(message); + }); +} + +server.listen(socketPath, async () => { + console.log("listening"); + const first = net.createConnection(socketPath); + first.once("connect", () => console.log("first connected")); + await exchange("first", first, "one"); + + let listenerCalls = 0; + const second = net.connect(socketPath, () => { + listenerCalls++; + }); + await exchange("second", second, "two"); + console.log("second listener calls", listenerCalls); + + const third = net.createConnection({ path: socketPath }); + await exchange("third", third, "three"); + + await new Promise((resolve) => { + const missing = net.createConnection(missingPath); + missing.once("connect", () => console.log("missing connected")); + missing.once("error", (error: NodeJS.ErrnoException) => { + console.log("missing", error.code, error.message === `connect ENOENT ${missingPath}`); + resolve(); + }); + }); + + server.close(() => { + const tcp = net.createServer((socket) => socket.end("tcp")); + tcp.listen(0, "localhost", () => { + const { port } = tcp.address() as net.AddressInfo; + const client = net.createConnection(String(port)); + client.on("data", (chunk) => console.log("numeric string", chunk.toString())); + client.on("close", () => { + tcp.close(); + fs.rmSync(dir, { recursive: true, force: true }); + console.log("done"); + }); + }); + }); +}); diff --git a/tests/corpus/typeof-ambient-undefined-names.ts b/tests/corpus/typeof-ambient-undefined-names.ts new file mode 100644 index 0000000000..94713319e5 --- /dev/null +++ b/tests/corpus/typeof-ambient-undefined-names.ts @@ -0,0 +1,21 @@ +// typeof on names the program declares (`declare const`, `declare function`) +// but nothing defines at run time answers "undefined"; reading them throws +// ReferenceError. +declare const Bun: { randomUUIDv7: () => string } | undefined; +declare const Deno: { version: { deno: string } }; +declare function webkitRequestAnimationFrame(callback: () => void): number; +console.log("start"); +console.log(typeof Bun, typeof Deno, typeof webkitRequestAnimationFrame); +console.log(typeof Bun !== "undefined" ? Bun?.randomUUIDv7() : "no Bun"); +const runtime = typeof Deno === "object" ? "deno" : typeof Bun === "object" ? "bun" : "node"; +console.log(runtime); +try { + console.log(Bun); +} catch (error) { + console.log((error as Error).name, (error as Error).message); +} +try { + console.log(typeof Deno.version); +} catch (error) { + console.log((error as Error).name, (error as Error).message); +} diff --git a/tests/harness/corpus-costs/array-prototype-call-native-arrays.ts.json b/tests/harness/corpus-costs/array-prototype-call-native-arrays.ts.json new file mode 100644 index 0000000000..d88973daa3 --- /dev/null +++ b/tests/harness/corpus-costs/array-prototype-call-native-arrays.ts.json @@ -0,0 +1 @@ +{ "plain": 5514, "san": 7205 } diff --git a/tests/harness/corpus-costs/net-unix-socket-connect-default-import.mjs.json b/tests/harness/corpus-costs/net-unix-socket-connect-default-import.mjs.json new file mode 100644 index 0000000000..0c62f10cea --- /dev/null +++ b/tests/harness/corpus-costs/net-unix-socket-connect-default-import.mjs.json @@ -0,0 +1 @@ +{ "plain": 3242, "san": 5402 } diff --git a/tests/harness/corpus-costs/net-unix-socket-connect.ts.json b/tests/harness/corpus-costs/net-unix-socket-connect.ts.json new file mode 100644 index 0000000000..d03fa63895 --- /dev/null +++ b/tests/harness/corpus-costs/net-unix-socket-connect.ts.json @@ -0,0 +1 @@ +{ "plain": 3555, "san": 6682 } diff --git a/tests/harness/corpus-costs/typeof-ambient-undefined-names.ts.json b/tests/harness/corpus-costs/typeof-ambient-undefined-names.ts.json new file mode 100644 index 0000000000..91c82c6552 --- /dev/null +++ b/tests/harness/corpus-costs/typeof-ambient-undefined-names.ts.json @@ -0,0 +1 @@ +{ "plain": 4212, "san": 4590 }