mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-27 06:43:19 +08:00
Match class capsules in union fields of checked unions
A checked conversion to a union of object types tests each member's fields with the members' matchers. For a field of type `C | undefined` holding a class instance, the union's matcher found the instance's capsule, which is not a capsule of the union itself, and answered no (or matched the instance's plain-object view, which no class arm accepts), so no member matched and the conversion threw a TypeError. workerData read as such a union failed this way in every worker. A union now matches capsules of other types through its arms, where the class arm accepts the instance.
This commit is contained in:
@@ -762,7 +762,9 @@ export class LlDyn {
|
||||
B.terminate(`ret i1 true`);
|
||||
B.startBlock(lNext);
|
||||
}
|
||||
if (t.kind !== "dyn") {
|
||||
// A union matches a capsule of another type through its arms below: a
|
||||
// class capsule in a `C | undefined` field matches the `C` arm.
|
||||
if (t.kind !== "dyn" && t.kind !== "union") {
|
||||
const view = this.viewFn(wrapped);
|
||||
this.host.declare(`declare void @scr_dyn_release_v(ptr)`);
|
||||
const kind = this.kindOf(B, "%d");
|
||||
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/6063-union-class-field-checks.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
// A checked conversion of a dynamic value to a union of object types picks
|
||||
// the member whose fields match, including a field of type `C | undefined`
|
||||
// that holds an instance of the class C: workerData read as such a union,
|
||||
// and an unknown value asserted to it, give the instance or undefined.
|
||||
import { isMainThread, parentPort, Worker, workerData } from "node:worker_threads";
|
||||
import { publish } from "@scriptc/threads";
|
||||
|
||||
class Job {
|
||||
readonly name: string;
|
||||
constructor(name: string) {
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
|
||||
interface WithJob {
|
||||
readonly kind: "job";
|
||||
readonly job: Job | undefined;
|
||||
}
|
||||
interface Plain {
|
||||
readonly kind: "plain";
|
||||
readonly note: string;
|
||||
}
|
||||
interface Counted {
|
||||
readonly kind: "counted";
|
||||
readonly count: number;
|
||||
readonly last: Job | undefined;
|
||||
}
|
||||
type Data = WithJob | Plain | Counted;
|
||||
|
||||
function describe(data: Data): string {
|
||||
switch (data.kind) {
|
||||
case "job":
|
||||
return data.job === undefined ? "job none" : `job ${data.job.name}`;
|
||||
case "plain":
|
||||
return `plain ${data.note}`;
|
||||
case "counted":
|
||||
return `counted ${data.count} ${data.last?.name ?? "none"}`;
|
||||
}
|
||||
}
|
||||
|
||||
function run(data: Data): Promise<void> {
|
||||
return new Promise((resolve) => {
|
||||
const worker = new Worker(new URL(import.meta.url), { workerData: data });
|
||||
worker.on("message", (message) => console.log("worker:", message));
|
||||
worker.on("error", (error) => console.log("worker failed:", String(error)));
|
||||
worker.on("exit", () => resolve());
|
||||
});
|
||||
}
|
||||
|
||||
if (isMainThread) {
|
||||
const values: unknown[] = [
|
||||
{ kind: "job", job: new Job("local") },
|
||||
{ kind: "job", job: undefined },
|
||||
{ kind: "plain", note: "text" },
|
||||
{ kind: "counted", count: 2, last: new Job("tail") },
|
||||
];
|
||||
for (const value of values) console.log("local:", describe(value as Data));
|
||||
const shared = publish(new Job("shared"));
|
||||
await run({ kind: "job", job: shared });
|
||||
await run({ kind: "job", job: undefined });
|
||||
await run({ kind: "plain", note: "worker" });
|
||||
await run({ kind: "counted", count: 3, last: shared });
|
||||
} else {
|
||||
parentPort!.postMessage(describe(workerData as Data));
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
{ "plain": 4152, "san": 6474 }
|
||||
Reference in New Issue
Block a user