Files
scriptc/packages/compiler/test/collection-keys.test.ts
Chris Tate 3f66c8e20b Make LLVM the sole production backend
- Remove C emission, debugging options, and automatic backend fallback.
- Complete LLVM lowering and native bootstrap coverage across supported targets.
- Link executables and libraries with precompiled C runtime packs, including iOS and Android.
2026-09-29 20:54:09 -05:00

97 lines
4.4 KiB
TypeScript

import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { expect, test } from "vitest";
import { analyze } from "../src/index.js";
import { BOOL, DYN, F64, STRING, UNDEFINED_T, VOID, arrayOf, isIdentityCollectionKey, isSupportedMapKey, isSupportedSetElem, mapOf, setOf, type IrExpr, type IrModule, type IrType } from "../src/ir/ir.js";
import { validateModule } from "../src/ir/validate.js";
import { IR_VERSION } from "../src/ir/serialize.js";
import { mapKeyAccess, mapKeyKindNum } from "../src/backend/llvm/shapes.js";
const reference: IrType = { kind: "record", shapeId: "key" };
const references: IrType[] = [reference, { kind: "object", className: "Key" }, arrayOf(F64), { kind: "symbol" }, { kind: "netServer" }];
const union: IrType = { kind: "union", unionId: "keys" };
test("checked collection slots refuse snapshot and adapting reference conversions", () => {
const dir = mkdtempSync(join(tmpdir(), "scriptc-collection-conversion-"));
try {
const entry = join(dir, "main.ts");
writeFileSync(entry, `
const promise = Promise.resolve(1);
const error = new Error("key");
class CustomError extends Error {}
const custom = new CustomError("key");
const map = new Map<unknown, unknown>();
map.set(promise, 1);
map.set(1, promise);
map.set(error, 1);
map.set(custom, 1);
const set = new Set<unknown>([promise]);
set.add(error);
`);
const { coverage } = analyze(entry, { dynamic: false });
expect(coverage.preflightFailed).toBe(false);
expect(coverage.diagnostics).toHaveLength(6);
for (const diagnostic of coverage.diagnostics) {
expect(diagnostic.message).toContain("collection slot conversion requiring an Error snapshot or promise adapter");
}
} finally {
rmSync(dir, { recursive: true, force: true });
}
});
test.each(references)("identity key %j uses the reference ABI in LLVM", (type) => {
expect(isIdentityCollectionKey(type)).toBe(true);
expect(isSupportedMapKey(type)).toBe(true);
expect(isSupportedSetElem(type)).toBe(true);
expect(mapKeyAccess(type)).toBe("ref");
expect(mapKeyKindNum(type)).toBe(2);
});
test("boxed reference keys select payload identity, not wrapper identity", () => {
expect(isSupportedMapKey(union, references)).toBe(true);
expect(isSupportedSetElem(union, references)).toBe(true);
expect(mapKeyAccess(union)).toBe("ref");
expect(mapKeyKindNum(union)).toBe(3);
});
test("unresolved, empty and mixed-value unions cannot enter the identity ABI", () => {
expect(isSupportedMapKey(union)).toBe(false);
expect(isSupportedMapKey(union, [])).toBe(false);
for (const scalar of [F64, STRING, BOOL, UNDEFINED_T]) {
expect(isSupportedMapKey(union, [reference, scalar])).toBe(false);
expect(isSupportedSetElem(union, [reference, scalar])).toBe(false);
}
for (const unsupported of [BOOL, { kind: "date" } as IrType, mapOf(STRING, F64), setOf(STRING)]) {
expect(isSupportedMapKey(unsupported)).toBe(false);
}
});
test("primitive key ABI constants remain stable", () => {
expect([mapKeyKindNum(F64), mapKeyAccess(F64)]).toEqual([0, "f64"]);
expect([mapKeyKindNum(STRING), mapKeyAccess(STRING)]).toEqual([1, "str"]);
});
test("unknown keys select value equality separately from reference identity", () => {
expect(isIdentityCollectionKey(DYN)).toBe(false);
expect(isSupportedMapKey(DYN)).toBe(true);
expect(isSupportedSetElem(DYN)).toBe(true);
expect([mapKeyKindNum(DYN), mapKeyAccess(DYN)])
.toEqual([4, "ref"]);
});
test.each(["map", "set"] as const)("validator checks the arms behind a %s key union", (kind) => {
const loc = { file: "keys.ts", start: 0, end: 1 };
const type = kind === "map" ? mapOf(union, F64) : setOf(union);
const expr: IrExpr = kind === "map" ? { kind: "mapNew", type, loc } : { kind: "setNew", type, loc };
const mod: IrModule = {
irVersion: IR_VERSION, sourceFile: loc.file, entry: "main",
records: [{ id: "key", fields: [{ name: "id", type: F64 }] }],
unions: [{ id: "keys", arms: [reference, arrayOf(F64)] }],
functions: [{ name: "main", params: [], locals: [], returnType: VOID, body: [{ kind: "exprStmt", expr, loc }], loc }],
};
expect(validateModule(mod)).toEqual([]);
mod.unions![0]!.arms = [reference, F64];
expect(validateModule(mod).some((error) => error.message.includes(kind === "map" ? "mapNew key kind union" : "setNew element kind union"))).toBe(true);
});