mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-11-03 07:54:40 +08:00
Track unions in nonJsonPart and expect per-platform lifecycle output
This commit is contained in:
@@ -22,7 +22,9 @@ import { DYN, STRING, type IrExpr, type IrType } from "../../ir/ir.js";
|
||||
const JSON_HINT =
|
||||
"only JSON data (numbers, strings, booleans, null, arrays, plain objects of those, and unknown) crosses into Node.js in this version";
|
||||
|
||||
/** The first part of `t` that is not JSON data, or null. */
|
||||
/** The first part of `t` that is not JSON data, or null. `seen` holds the
|
||||
* records and unions already being checked, so a recursive type (a JSON
|
||||
* value type that contains arrays or records of itself) ends there. */
|
||||
function nonJsonPart(lowerer: Lowerer, t: IrType, seen: Set<string>): IrType | null {
|
||||
switch (t.kind) {
|
||||
case "f64":
|
||||
@@ -35,8 +37,8 @@ function nonJsonPart(lowerer: Lowerer, t: IrType, seen: Set<string>): IrType | n
|
||||
case "array":
|
||||
return nonJsonPart(lowerer, t.elem, seen);
|
||||
case "record": {
|
||||
if (seen.has(t.shapeId)) return null;
|
||||
seen.add(t.shapeId);
|
||||
if (seen.has(`record:${t.shapeId}`)) return null;
|
||||
seen.add(`record:${t.shapeId}`);
|
||||
const shape = lowerer.shapes.get(t.shapeId);
|
||||
if (shape === undefined) return t;
|
||||
for (const field of shape.fields) {
|
||||
@@ -46,6 +48,8 @@ function nonJsonPart(lowerer: Lowerer, t: IrType, seen: Set<string>): IrType | n
|
||||
return shape.indexValue === undefined ? null : nonJsonPart(lowerer, shape.indexValue, seen);
|
||||
}
|
||||
case "union": {
|
||||
if (seen.has(`union:${t.unionId}`)) return null;
|
||||
seen.add(`union:${t.unionId}`);
|
||||
const def = lowerer.unions.get(t.unionId);
|
||||
if (def === undefined) return t;
|
||||
for (const arm of def.arms) {
|
||||
|
||||
+1
@@ -1,2 +1,3 @@
|
||||
"use strict";
|
||||
exports.answer = () => 42;
|
||||
exports.echo = (value) => value;
|
||||
|
||||
+6
-2
@@ -4,8 +4,12 @@ import { readFileSync } from "node:fs";
|
||||
import { callSync } from "@scriptc/node";
|
||||
|
||||
const mode = process.argv[2] ?? "call";
|
||||
const mapped = (): boolean =>
|
||||
process.platform !== "linux" || readFileSync("/proc/self/maps", "utf8").includes("libnode");
|
||||
// Whether libnode is mapped into the process, from /proc/self/maps; other
|
||||
// platforms report "unknown".
|
||||
const mapped = (): boolean | string =>
|
||||
process.platform === "linux"
|
||||
? readFileSync("/proc/self/maps", "utf8").includes("libnode")
|
||||
: "unknown";
|
||||
console.log("mapped before", mapped());
|
||||
if (mode !== "never") {
|
||||
console.log(callSync<number>("./helper.cjs", "answer"));
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
// A recursive JSON value type as an argument and a result of a Node island
|
||||
// call (tests/harness/node-island.test.ts).
|
||||
import { callSync } from "@scriptc/node";
|
||||
|
||||
type Json = number | string | boolean | null | Json[] | { [key: string]: Json };
|
||||
|
||||
const input: Json = { list: [1, "two", [true, null]] };
|
||||
const output = callSync<Json>("./helper.cjs", "echo", input);
|
||||
console.log(JSON.stringify(output));
|
||||
@@ -6,11 +6,11 @@
|
||||
* (6800-6803) pins the call semantics against Node. Needs a libnode pack
|
||||
* in SCRIPTC_LIBNODE; required in the Linux x64 CI lanes (libnode.ts). */
|
||||
import { execFile } from "node:child_process";
|
||||
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
||||
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import { promisify } from "node:util";
|
||||
import { beforeAll, describe, expect, test } from "vitest";
|
||||
import { compile } from "@scriptc/compiler";
|
||||
import { compile, deserializeModule } from "@scriptc/compiler";
|
||||
import { definedFunctionNames, nodeIslandSymbols } from "../../scripts/zero-cost-compare.mjs";
|
||||
import { libnodePack } from "./libnode.js";
|
||||
|
||||
@@ -31,6 +31,11 @@ interface Run {
|
||||
|
||||
let binary = "";
|
||||
|
||||
// The fixture reads whether libnode is mapped from /proc/self/maps; other
|
||||
// platforms print "unknown".
|
||||
const before = `mapped before ${process.platform === "linux" ? "false" : "unknown"}`;
|
||||
const after = `mapped after ${process.platform === "linux" ? "true" : "unknown"}`;
|
||||
|
||||
async function run(
|
||||
path: string,
|
||||
args: string[],
|
||||
@@ -103,12 +108,12 @@ describe.skipIf(skip)("Node islands in-process", () => {
|
||||
|
||||
test("loads Node on the first call, and only then", async () => {
|
||||
expect(await run(binary, [])).toEqual({
|
||||
stdout: "mapped before false\n42\nmapped after true\ndone\n",
|
||||
stdout: `${before}\n42\n${after}\ndone\n`,
|
||||
stderr: "",
|
||||
code: 0,
|
||||
});
|
||||
expect(await run(binary, ["never"])).toEqual({
|
||||
stdout: "mapped before false\ndone\n",
|
||||
stdout: `${before}\ndone\n`,
|
||||
stderr: "",
|
||||
code: 0,
|
||||
});
|
||||
@@ -117,7 +122,7 @@ describe.skipIf(skip)("Node islands in-process", () => {
|
||||
test("process.exit after loading Node keeps its code", async () => {
|
||||
const result = await run(binary, ["exit"]);
|
||||
expect(result.code).toBe(5);
|
||||
expect(result.stdout).toBe("mapped before false\n42\nmapped after true\n");
|
||||
expect(result.stdout).toBe(`${before}\n42\n${after}\n`);
|
||||
});
|
||||
|
||||
test("beside packaging carries its own pack", async () => {
|
||||
@@ -126,14 +131,14 @@ describe.skipIf(skip)("Node islands in-process", () => {
|
||||
const env = { ...process.env };
|
||||
delete env["SCRIPTC_LIBNODE"];
|
||||
const result = await run(beside, [], env);
|
||||
expect(result.stdout).toBe("mapped before false\n42\nmapped after true\ndone\n");
|
||||
expect(result.stdout).toBe(`${before}\n42\n${after}\ndone\n`);
|
||||
expect(result.code).toBe(0);
|
||||
}, 300_000);
|
||||
|
||||
test("a missing pack fails the first call", async () => {
|
||||
const result = await run(binary, [], { ...process.env, SCRIPTC_LIBNODE: "/nonexistent/pack" });
|
||||
expect(result.code).toBe(1);
|
||||
expect(result.stdout).toBe("mapped before false\n");
|
||||
expect(result.stdout).toBe(`${before}\n`);
|
||||
expect(result.stderr).toMatch(
|
||||
/^scriptc: cannot load Node\.js for this program's Node islands: .*\/nonexistent\/pack\/libscr_node_bridge\.(so|dylib).*\n$/,
|
||||
);
|
||||
@@ -184,3 +189,20 @@ describe.skipIf(skip)("Node islands in-process", () => {
|
||||
expect(conflicting).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
// Lowering checks every argument and result type for JSON data; IR output
|
||||
// needs no libnode pack.
|
||||
test("recursive JSON types cross into Node", async () => {
|
||||
const dir = join(outDir, "recursive-json");
|
||||
mkdirSync(dir, { recursive: true });
|
||||
const outPath = join(dir, "main.ir.json");
|
||||
const result = await compile(join(repoRoot, "tests/fixtures/node-island/recursive-json.ts"), {
|
||||
outPath,
|
||||
outDir: dir,
|
||||
outputKind: "ir",
|
||||
});
|
||||
if (!result.ok)
|
||||
throw new Error(result.diagnostics.map((d) => `${d.code}: ${d.message}`).join("\n"));
|
||||
const ir = JSON.stringify(deserializeModule(readFileSync(outPath, "utf8")));
|
||||
expect(ir).toContain("nodeIsland.callSync");
|
||||
}, 300_000);
|
||||
|
||||
Reference in New Issue
Block a user