Files
scriptc/packages/compiler/test/native-callable-values.test.ts
Chris Tate c257368c5e Compile Effect generators statically (#589)
* Compile Effect generators statically

* test: declare the dormant array write's expected fence
2026-09-30 14:25:29 -05:00

109 lines
5.3 KiB
TypeScript

import { spawnSync } from "node:child_process";
import { mkdirSync, mkdtempSync, rmSync, statSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { expect, test } from "vitest";
import { analyze, compile } from "../src/index.js";
const sanitize = process.env["SCRIPTC_SAN"] === "1";
test.each(["llvm"] as const)("native callable checks preserve supported arguments and reject before invoking (%s)", async (backend) => {
const dir = mkdtempSync(join(tmpdir(), "scriptc-callable-checks-"));
try {
const entry = join(dir, "main.ts");
writeFileSync(join(dir, "close.js"), `
export function closeUnknown(value) { return value.close(); }
export function enumerateUnknown(value) {
Object.defineProperties(value, { extra: { value: "visible", enumerable: true } });
const keys = [];
for (const key in value) keys.push(key);
return keys.join(",");
}
`);
writeFileSync(entry, `
import { open } from "node:fs/promises";
import { closeUnknown, enumerateUnknown } from "./close.js";
function join(prefix: string, ...parts: string[]): string { console.log("invoked"); return prefix + parts.join(":"); }
const boxed: unknown = join;
function argument(value: unknown): unknown { console.log("argument"); return value; }
if (typeof boxed === "function") {
try { boxed(argument("prefix:"), argument("valid"), argument(4)); }
catch (error) { if (error instanceof Error) console.log(error.name); }
console.log(boxed("after:", "one", "two"));
}
try { console.log(enumerateUnknown(boxed)); }
catch (error) { if (error instanceof Error) console.log(error.message.includes("for-in over this checked-dynamic kind is not supported yet")); }
const widened = open as (path: string, flags?: string | number, mode?: number | string) => ReturnType<typeof open>;
const operations = { open: widened };
function describe(value: unknown): string { console.log("described"); return typeof value; }
const describeKey = describe as (value: string | symbol) => string;
console.log(describeKey("supported"));
console.log(describeKey(Symbol.for("supported")));
const file = process.argv[2]!;
try { await operations.open(file, 0); }
catch (error) { if (error instanceof Error) console.log(error.name); }
try { await operations.open(file, "w", "600"); }
catch (error) { if (error instanceof Error) console.log(error.name); }
const handle = await operations.open(file, "wx", 0o600);
const stored: unknown = handle;
await closeUnknown(stored);
console.log(handle.fd);
`);
const result = await compile(entry, { backend, dynamic: false, sanitize, outDir: dir, outPath: join(dir, "program") });
expect(result.ok, JSON.stringify(result.diagnostics)).toBe(true);
if (!result.ok) return;
const path = join(dir, "created.txt");
const child = spawnSync(result.binaryPath, [path], { encoding: "utf8" });
expect(child.status, child.stderr).toBe(0);
const stderr = sanitize && process.platform === "linux"
? child.stderr.replace(/^==\d+==WARNING: ASan doesn't fully support makecontext\/swapcontext functions and may produce false positives in some cases!\n/gm, "")
: child.stderr;
expect(stderr).toBe("");
expect(child.stdout).toBe("argument\nargument\nargument\nTypeError\ninvoked\nafter:one:two\ntrue\ndescribed\nstring\ndescribed\nsymbol\nTypeError\nTypeError\n-1\n");
expect(statSync(path).mode & 0o777).toBe(0o600);
} finally {
rmSync(dir, { recursive: true, force: true });
}
});
test.each(["llvm"] as const)("failed implicit specializations retain a callable throwing body (%s)", async (backend) => {
const dir = mkdtempSync(join(tmpdir(), "scriptc-failed-specialization-"));
try {
const pkg = join(dir, "node_modules", "callbacks");
mkdirSync(pkg, { recursive: true });
writeFileSync(join(dir, "package.json"), '{"type":"module"}');
writeFileSync(join(pkg, "package.json"), '{"name":"callbacks","type":"module","main":"index.js","types":"index.d.ts"}');
writeFileSync(join(pkg, "index.d.ts"), "export {};\n");
writeFileSync(join(pkg, "index.js"), `
const inspect = (value) => eval(value);
function stopped(value) {
eval(value);
return { done() { return "wrong"; } };
}
try { console.log(stopped("0").done()); }
catch (error) { console.log(String(error).includes("SC2011")); }
const first = { inspect };
const second = { inspect };
console.log(typeof first.inspect, first.inspect === second.inspect);
for (const callback of [first.inspect, second.inspect]) {
try { callback({}); }
catch (error) { console.log(String(error).includes("SC2011")); }
}
`);
const entry = join(dir, "main.js");
writeFileSync(entry, 'import "callbacks";');
const { coverage } = analyze(entry, { dynamic: false, npmStatic: "auto" });
expect(coverage.diagnostics).toEqual([]);
expect(coverage.runtimeFences?.length).toBeGreaterThan(0);
const result = await compile(entry, { backend, dynamic: false, npmStatic: "auto", sanitize, outDir: dir, outPath: join(dir, "program") });
expect(result.ok, JSON.stringify(result.diagnostics)).toBe(true);
if (!result.ok) return;
const child = spawnSync(result.binaryPath, [], { encoding: "utf8" });
expect(child.status, child.stderr).toBe(0);
expect(child.stderr).toBe("");
expect(child.stdout).toBe("true\nfunction true\ntrue\ntrue\n");
} finally {
rmSync(dir, { recursive: true, force: true });
}
});