mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-11-01 07:33:12 +08:00
Fix Array.prototype calls on native arrays, typeof on ambient names, and net connections to Unix sockets (#2326)
This commit is contained in:
@@ -356,6 +356,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. */
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -1980,6 +1980,8 @@ export const USES_TIMERS_LIB_FNS = new Set<string>([
|
||||
"net.listenOptsReusePortCb",
|
||||
"net.connect",
|
||||
"net.connectCb",
|
||||
"net.connectPath",
|
||||
"net.connectPathCb",
|
||||
"net.connectLookup",
|
||||
"net.connectLookupDyn",
|
||||
"net.connectAttempt",
|
||||
|
||||
@@ -45,6 +45,7 @@ const COMPLETION_CALLBACKS: ReadonlyMap<IrLibFn, CallbackPosition> = new Map<
|
||||
["http.requestDynCb", "last"],
|
||||
["http.requestUrlCb", "last"],
|
||||
["net.connectCb", 2],
|
||||
["net.connectPathCb", 2],
|
||||
["net.listenCb", 2],
|
||||
["net.listenOptsCb", 4],
|
||||
["net.listenOptsReusePortCb", 5],
|
||||
|
||||
@@ -56,6 +56,7 @@ import { varRef } from "../../../ir/build.js";
|
||||
import {
|
||||
ambientUndefReadType,
|
||||
ambientUndefinedFnSymbolOf,
|
||||
ambientUndefinedVarSymbolOf,
|
||||
contextualUndefReadType,
|
||||
fenceEarlyAliasUse,
|
||||
isTemporalGlobalRoot,
|
||||
@@ -829,14 +830,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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
* "<name> 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
|
||||
|
||||
@@ -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: <union with a null arm>, 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;
|
||||
|
||||
@@ -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 <operand>` 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;
|
||||
}
|
||||
|
||||
|
||||
@@ -944,6 +944,8 @@ export const MAY_THROW_LIB_FNS: ReadonlySet<IrLibFn> = 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.
|
||||
|
||||
@@ -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
|
||||
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/array-prototype-call-native-arrays.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/net-unix-socket-connect-default-import.mjs"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/net-unix-socket-connect.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/typeof-ambient-undefined-names.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
@@ -916,6 +916,117 @@ void scr_dyn_invoke_discard_id(ScrDyn *recv, ScrDyn *callee, const char *method,
|
||||
scr_dyn_release(scr_dyn_invoke_prepared_id(recv, callee, method, mid_slot, args, argc, what));
|
||||
}
|
||||
|
||||
/* The callback methods of a dense dyn array that take a thisArg:
|
||||
* forEach, map, filter, some, every, find, findIndex, findLast,
|
||||
* findLastIndex and flatMap. The caller binds `this` for the callbacks.
|
||||
* `len` is the length before the first callback. */
|
||||
static ScrDyn *dyn_arr_callback_method(ScrDyn *recv, int mid, ScrDyn *const *args, size_t argc,
|
||||
size_t len, ScrDyn *callback_recv) {
|
||||
if ((mid == DYN_M_findLast) || (mid == DYN_M_findLastIndex)) {
|
||||
if (!dyn_cb_check(args, argc)) return NULL;
|
||||
ScrDyn *visible_recv = callback_recv ? callback_recv : recv;
|
||||
for (size_t i = len; i > 0; i--) {
|
||||
size_t index = i - 1;
|
||||
ScrDyn *item = index < recv->v.arr.len
|
||||
? scr_dyn_retain(recv->v.arr.items[index])
|
||||
: scr_dyn_retain(scr_dyn_undefined());
|
||||
ScrDyn *result = dyn_call_cb(args[0], item, index, visible_recv);
|
||||
if (!result) { scr_dyn_release(item); return NULL; }
|
||||
bool matched = scr_dyn_truthy(result);
|
||||
scr_dyn_release(result);
|
||||
if (matched) {
|
||||
if ((mid == DYN_M_findLast)) return item;
|
||||
scr_dyn_release(item);
|
||||
return scr_dyn_new_num((double)index);
|
||||
}
|
||||
scr_dyn_release(item);
|
||||
}
|
||||
return (mid == DYN_M_findLast)
|
||||
? scr_dyn_retain(scr_dyn_undefined()) : scr_dyn_new_num(-1);
|
||||
}
|
||||
if ((mid == DYN_M_forEach) || (mid == DYN_M_map) ||
|
||||
(mid == DYN_M_filter) || (mid == DYN_M_some) ||
|
||||
(mid == DYN_M_every) || (mid == DYN_M_find) ||
|
||||
(mid == DYN_M_findIndex)) {
|
||||
if (!dyn_cb_check(args, argc)) return NULL;
|
||||
ScrDyn *cb = args[0];
|
||||
ScrDyn *out = ((mid == DYN_M_map) || (mid == DYN_M_filter)) ? scr_dyn_new_arr() : NULL;
|
||||
/* Array iteration methods snapshot length before the first callback.
|
||||
* A shrinking callback can remove a later dense entry; an expanding
|
||||
* callback must not make the new tail observable to this pass. Live
|
||||
* Web-boundary capsules are the externally visible receiver, so pass
|
||||
* that capsule as callback arg 3: mutations through the callback's
|
||||
* array argument then commit directly to the original static array. */
|
||||
size_t n = len;
|
||||
ScrDyn *visible_recv = callback_recv ? callback_recv : recv;
|
||||
for (size_t step = 0; step < n; step++) {
|
||||
size_t i = step;
|
||||
if (i >= recv->v.arr.len) continue;
|
||||
ScrDyn *item = scr_dyn_retain(recv->v.arr.items[i]);
|
||||
ScrDyn *r = dyn_call_cb(cb, item, i, visible_recv);
|
||||
if (!r) { scr_dyn_release(item); scr_dyn_release(out); return NULL; }
|
||||
if ((mid == DYN_M_map)) {
|
||||
scr_dyn_arr_push(out, r); /* ownership moves in */
|
||||
r = NULL;
|
||||
} else {
|
||||
bool truthy = scr_dyn_truthy(r);
|
||||
scr_dyn_release(r);
|
||||
if ((mid == DYN_M_filter) && truthy) scr_dyn_arr_push(out, scr_dyn_retain(item));
|
||||
if ((mid == DYN_M_some) && truthy) { scr_dyn_release(item); return scr_dyn_new_bool(true); }
|
||||
if ((mid == DYN_M_every) && !truthy) { scr_dyn_release(item); return scr_dyn_new_bool(false); }
|
||||
if ((mid == DYN_M_find) && truthy) return item; /* +1 moves out */
|
||||
if ((mid == DYN_M_findIndex) && truthy) { scr_dyn_release(item); return scr_dyn_new_num((double)i); }
|
||||
}
|
||||
scr_dyn_release(item);
|
||||
}
|
||||
if (out) return out;
|
||||
if ((mid == DYN_M_some)) return scr_dyn_new_bool(false);
|
||||
if ((mid == DYN_M_every)) return scr_dyn_new_bool(true);
|
||||
if ((mid == DYN_M_find)) return scr_dyn_retain(scr_dyn_undefined());
|
||||
if ((mid == DYN_M_findIndex)) return scr_dyn_new_num(-1);
|
||||
return scr_dyn_retain(scr_dyn_undefined()); /* forEach */
|
||||
}
|
||||
if ((mid == DYN_M_flatMap)) {
|
||||
/* JS Array.prototype.flatMap over a dyn array: map + a depth-1
|
||||
* flatten. Native dyn-array results flatten element-by-element; a
|
||||
* WRAPPED engine array flattens through the routed keyed reads
|
||||
* (elements wrap back scalar-normalized); everything else pushes
|
||||
* as a single element (JS keeps non-array callback results whole).
|
||||
* JS's spec snapshots the length up front (elements appended by
|
||||
* the callback are not visited). */
|
||||
if (!dyn_cb_check(args, argc)) return NULL;
|
||||
ScrDyn *cb = args[0];
|
||||
ScrDyn *out = scr_dyn_new_arr();
|
||||
size_t n = recv->v.arr.len;
|
||||
for (size_t i = 0; i < n && i < recv->v.arr.len; i++) {
|
||||
if (!dyn_borrow_present(recv, i)) { if (scr_exc_pending()) { scr_dyn_release(out); return NULL; } continue; }
|
||||
ScrDyn *item = dyn_borrow_item(recv, i);
|
||||
if (!item) { scr_dyn_release(out); return NULL; }
|
||||
ScrDyn *r = dyn_call_cb(
|
||||
cb, item, i, callback_recv ? callback_recv : recv);
|
||||
scr_dyn_release(item);
|
||||
if (!r) { scr_dyn_release(out); return NULL; }
|
||||
if (r->kind == SCR_DYN_TYPED_REF && scr_dyn_ref_is_array(r)) {
|
||||
ScrDyn *view = scr_dyn_typed_ref_materialize(r);
|
||||
scr_dyn_release(r);
|
||||
r = view;
|
||||
if (!r) { scr_dyn_release(out); return NULL; }
|
||||
}
|
||||
if (r->kind == SCR_DYN_ARR) {
|
||||
for (size_t j = 0; j < r->v.arr.len; j++) {
|
||||
if (dyn_borrow_present(r, j)) scr_dyn_arr_push(out, dyn_borrow_item(r, j));
|
||||
if (scr_exc_pending()) { scr_dyn_release(r); scr_dyn_release(out); return NULL; }
|
||||
}
|
||||
scr_dyn_release(r);
|
||||
} else {
|
||||
scr_dyn_arr_push(out, r); /* ownership moves in */
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static ScrDyn *scr_dyn_invoke_impl(
|
||||
ScrDyn *recv, const char *method, int mid, ScrDyn *const *args, size_t argc,
|
||||
const char *what, ScrDyn *callback_recv) {
|
||||
@@ -1367,27 +1478,16 @@ static ScrDyn *scr_dyn_invoke_impl(
|
||||
}
|
||||
return (mid == DYN_M_includes) ? scr_dyn_new_bool(false) : scr_dyn_new_num(-1);
|
||||
}
|
||||
if ((mid == DYN_M_findLast) || (mid == DYN_M_findLastIndex)) {
|
||||
if (!dyn_cb_check(args, argc)) return NULL;
|
||||
ScrDyn *visible_recv = callback_recv ? callback_recv : recv;
|
||||
for (size_t i = len; i > 0; i--) {
|
||||
size_t index = i - 1;
|
||||
ScrDyn *item = index < recv->v.arr.len
|
||||
? scr_dyn_retain(recv->v.arr.items[index])
|
||||
: scr_dyn_retain(scr_dyn_undefined());
|
||||
ScrDyn *result = dyn_call_cb(args[0], item, index, visible_recv);
|
||||
if (!result) { scr_dyn_release(item); return NULL; }
|
||||
bool matched = scr_dyn_truthy(result);
|
||||
scr_dyn_release(result);
|
||||
if (matched) {
|
||||
if ((mid == DYN_M_findLast)) return item;
|
||||
scr_dyn_release(item);
|
||||
return scr_dyn_new_num((double)index);
|
||||
}
|
||||
scr_dyn_release(item);
|
||||
}
|
||||
return (mid == DYN_M_findLast)
|
||||
? scr_dyn_retain(scr_dyn_undefined()) : scr_dyn_new_num(-1);
|
||||
if ((mid == DYN_M_forEach) || (mid == DYN_M_map) || (mid == DYN_M_filter) ||
|
||||
(mid == DYN_M_some) || (mid == DYN_M_every) || (mid == DYN_M_find) ||
|
||||
(mid == DYN_M_findIndex) || (mid == DYN_M_findLast) || (mid == DYN_M_findLastIndex) ||
|
||||
(mid == DYN_M_flatMap)) {
|
||||
/* Every callback runs with `this` bound to the thisArg, or undefined
|
||||
* when there is none. */
|
||||
scr_dyn_this_push_dyn(argc > 1 ? args[1] : scr_dyn_undefined());
|
||||
ScrDyn *result = dyn_arr_callback_method(recv, mid, args, argc, len, callback_recv);
|
||||
scr_dyn_this_pop();
|
||||
return result;
|
||||
}
|
||||
if ((mid == DYN_M_join)) {
|
||||
ScrJsonBuf b;
|
||||
@@ -1550,48 +1650,6 @@ static ScrDyn *scr_dyn_invoke_impl(
|
||||
}
|
||||
return scr_dyn_retain(recv);
|
||||
}
|
||||
if ((mid == DYN_M_forEach) || (mid == DYN_M_map) ||
|
||||
(mid == DYN_M_filter) || (mid == DYN_M_some) ||
|
||||
(mid == DYN_M_every) || (mid == DYN_M_find) ||
|
||||
(mid == DYN_M_findIndex)) {
|
||||
if (!dyn_cb_check(args, argc)) return NULL;
|
||||
ScrDyn *cb = args[0];
|
||||
ScrDyn *out = ((mid == DYN_M_map) || (mid == DYN_M_filter)) ? scr_dyn_new_arr() : NULL;
|
||||
/* Array iteration methods snapshot length before the first callback.
|
||||
* A shrinking callback can remove a later dense entry; an expanding
|
||||
* callback must not make the new tail observable to this pass. Live
|
||||
* Web-boundary capsules are the externally visible receiver, so pass
|
||||
* that capsule as callback arg 3: mutations through the callback's
|
||||
* array argument then commit directly to the original static array. */
|
||||
size_t n = len;
|
||||
ScrDyn *visible_recv = callback_recv ? callback_recv : recv;
|
||||
for (size_t step = 0; step < n; step++) {
|
||||
size_t i = step;
|
||||
if (i >= recv->v.arr.len) continue;
|
||||
ScrDyn *item = scr_dyn_retain(recv->v.arr.items[i]);
|
||||
ScrDyn *r = dyn_call_cb(cb, item, i, visible_recv);
|
||||
if (!r) { scr_dyn_release(item); scr_dyn_release(out); return NULL; }
|
||||
if ((mid == DYN_M_map)) {
|
||||
scr_dyn_arr_push(out, r); /* ownership moves in */
|
||||
r = NULL;
|
||||
} else {
|
||||
bool truthy = scr_dyn_truthy(r);
|
||||
scr_dyn_release(r);
|
||||
if ((mid == DYN_M_filter) && truthy) scr_dyn_arr_push(out, scr_dyn_retain(item));
|
||||
if ((mid == DYN_M_some) && truthy) { scr_dyn_release(item); return scr_dyn_new_bool(true); }
|
||||
if ((mid == DYN_M_every) && !truthy) { scr_dyn_release(item); return scr_dyn_new_bool(false); }
|
||||
if ((mid == DYN_M_find) && truthy) return item; /* +1 moves out */
|
||||
if ((mid == DYN_M_findIndex) && truthy) { scr_dyn_release(item); return scr_dyn_new_num((double)i); }
|
||||
}
|
||||
scr_dyn_release(item);
|
||||
}
|
||||
if (out) return out;
|
||||
if ((mid == DYN_M_some)) return scr_dyn_new_bool(false);
|
||||
if ((mid == DYN_M_every)) return scr_dyn_new_bool(true);
|
||||
if ((mid == DYN_M_find)) return scr_dyn_retain(scr_dyn_undefined());
|
||||
if ((mid == DYN_M_findIndex)) return scr_dyn_new_num(-1);
|
||||
return scr_dyn_retain(scr_dyn_undefined()); /* forEach */
|
||||
}
|
||||
if ((mid == DYN_M_reduce) || (mid == DYN_M_reduceRight)) {
|
||||
if (!dyn_cb_check(args, argc)) return NULL;
|
||||
bool backward = (mid == DYN_M_reduceRight);
|
||||
@@ -1616,44 +1674,6 @@ static ScrDyn *scr_dyn_invoke_impl(
|
||||
scr_throw_error_msg(SCR_ERR_TYPE, message, sizeof message - 1);
|
||||
return NULL;
|
||||
}
|
||||
if ((mid == DYN_M_flatMap)) {
|
||||
/* JS Array.prototype.flatMap over a dyn array: map + a depth-1
|
||||
* flatten. Native dyn-array results flatten element-by-element; a
|
||||
* WRAPPED engine array flattens through the routed keyed reads
|
||||
* (elements wrap back scalar-normalized); everything else pushes
|
||||
* as a single element (JS keeps non-array callback results whole).
|
||||
* JS's spec snapshots the length up front (elements appended by
|
||||
* the callback are not visited). */
|
||||
if (!dyn_cb_check(args, argc)) return NULL;
|
||||
ScrDyn *cb = args[0];
|
||||
ScrDyn *out = scr_dyn_new_arr();
|
||||
size_t n = recv->v.arr.len;
|
||||
for (size_t i = 0; i < n && i < recv->v.arr.len; i++) {
|
||||
if (!dyn_borrow_present(recv, i)) { if (scr_exc_pending()) { scr_dyn_release(out); return NULL; } continue; }
|
||||
ScrDyn *item = dyn_borrow_item(recv, i);
|
||||
if (!item) { scr_dyn_release(out); return NULL; }
|
||||
ScrDyn *r = dyn_call_cb(
|
||||
cb, item, i, callback_recv ? callback_recv : recv);
|
||||
scr_dyn_release(item);
|
||||
if (!r) { scr_dyn_release(out); return NULL; }
|
||||
if (r->kind == SCR_DYN_TYPED_REF && scr_dyn_ref_is_array(r)) {
|
||||
ScrDyn *view = scr_dyn_typed_ref_materialize(r);
|
||||
scr_dyn_release(r);
|
||||
r = view;
|
||||
if (!r) { scr_dyn_release(out); return NULL; }
|
||||
}
|
||||
if (r->kind == SCR_DYN_ARR) {
|
||||
for (size_t j = 0; j < r->v.arr.len; j++) {
|
||||
if (dyn_borrow_present(r, j)) scr_dyn_arr_push(out, dyn_borrow_item(r, j));
|
||||
if (scr_exc_pending()) { scr_dyn_release(r); scr_dyn_release(out); return NULL; }
|
||||
}
|
||||
scr_dyn_release(r);
|
||||
} else {
|
||||
scr_dyn_arr_push(out, r); /* ownership moves in */
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
if ((mid == DYN_M_sort)) {
|
||||
ScrDyn *cmp = argc > 0 ? args[0] : scr_dyn_undefined();
|
||||
if (cmp->kind != SCR_DYN_UNDEF && cmp->kind != SCR_DYN_FUNC) {
|
||||
@@ -1801,7 +1821,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();
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
// 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]);
|
||||
|
||||
// The callback methods bind `this` to their thisArg, for native arrays
|
||||
// through .call and for dynamic arrays called directly.
|
||||
type Scale = { k: number; seen: number[] };
|
||||
const scale: Scale = { k: 10, seen: [] };
|
||||
const values: number[] = [1, 2, 3, 4];
|
||||
Array.prototype.forEach.call(values, function (this: Scale, v: number) {
|
||||
this.seen.push(v);
|
||||
}, scale);
|
||||
console.log(scale.seen.join(","));
|
||||
console.log(Array.prototype.map.call(values, function (this: Scale, v: number) {
|
||||
return v * this.k;
|
||||
}, scale).join(","));
|
||||
console.log(Array.prototype.filter.call(values, function (this: Scale, v: number) {
|
||||
return v * this.k > 15;
|
||||
}, scale).join(","));
|
||||
console.log(
|
||||
Array.prototype.some.call(values, function (this: Scale, v: number) { return v === this.k / 5; }, scale),
|
||||
Array.prototype.every.call(values, function (this: Scale, v: number) { return v < this.k; }, scale),
|
||||
Array.prototype.find.call(values, function (this: Scale, v: number) { return v * this.k === 30; }, scale),
|
||||
Array.prototype.findIndex.call(values, function (this: Scale, v: number) { return v * this.k === 30; }, scale),
|
||||
Array.prototype.findLast.call(values, function (this: Scale, v: number) { return v * this.k < 30; }, scale),
|
||||
Array.prototype.findLastIndex.call(values, function (this: Scale, v: number) { return v * this.k < 30; }, scale),
|
||||
);
|
||||
console.log(Array.prototype.flatMap.call(values, function (this: unknown, v: number) {
|
||||
return [v, v * (this as Scale).k];
|
||||
}, scale).join(","));
|
||||
const parsed: any = JSON.parse("[1, 2, 3]");
|
||||
const sum = { total: 0 };
|
||||
parsed.forEach(function (this: { total: number }, v: number) {
|
||||
this.total += v;
|
||||
}, sum);
|
||||
console.log(sum.total, parsed.map(function (this: Scale, v: number) { return v * this.k; }, scale).join(","));
|
||||
class Counter {
|
||||
total = 0;
|
||||
addAll(items: number[]): number {
|
||||
Array.prototype.forEach.call(items, (v: number) => {
|
||||
this.total += v;
|
||||
});
|
||||
return this.total;
|
||||
}
|
||||
}
|
||||
console.log(new Counter().addAll(values));
|
||||
@@ -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");
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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<void> {
|
||||
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<void>((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");
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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);
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
{ "plain": 5514, "san": 7205 }
|
||||
@@ -0,0 +1 @@
|
||||
{ "plain": 3242, "san": 5402 }
|
||||
@@ -0,0 +1 @@
|
||||
{ "plain": 3555, "san": 6682 }
|
||||
@@ -0,0 +1 @@
|
||||
{ "plain": 4212, "san": 4590 }
|
||||
Reference in New Issue
Block a user