Prefer the union arm that keeps every field of a converted record (#2259)

This commit is contained in:
Chris Tate
2026-10-25 16:26:24 -05:00
committed by GitHub
4 changed files with 81 additions and 0 deletions
@@ -128,6 +128,25 @@ export function widthLiftPlan(lowerer: Lowerer, src: IrType, dst: IrType): Width
const route = presenceRoute(lowerer, src.shapeId, selected[0]!, selected[1]!);
if (route) return route;
}
// A record whose fields some arm keeps EVERY one of, each lifting
// (`{ id; limit; opts?: A | B }` into `{ id; limit; opts?: A | B | C } |
// { id; opts?: C }`): that arm is the value's own layout, widened; the
// others hold it only by dropping fields. Prefer the unique lossless arm.
if (selected.length > 1 && src.kind === "record") {
const source = lowerer.shapes.get(src.shapeId);
const lossless = selected.filter(({ arm }) => {
const target = arm.kind === "record" ? lowerer.shapes.get(arm.shapeId) : undefined;
return (
source !== undefined &&
target !== undefined &&
!source.tuple &&
!target.tuple &&
target.fields.length === source.fields.length &&
source.fields.every((field) => target.fields.some((kept) => kept.name === field.name))
);
});
if (lossless.length === 1) selected = lossless;
}
if (selected.length !== 1) return null;
return { how: "liftWrap", tag: selected[0]!.tag, arm: selected[0]!.arm };
}
@@ -0,0 +1,6 @@
{
"order": [
"<repo>/tests/corpus/union-record-lossless-arm.ts"
],
"diags": []
}
+55
View File
@@ -0,0 +1,55 @@
// A record flowing into a union where several record arms can hold it by a
// width conversion: the arm that keeps every one of its fields is the
// value's own layout, widened, and wins over arms that drop fields.
interface FastOpts {
fast?: boolean;
strict?: boolean;
}
interface DepthOpts {
depth?: number;
}
interface StrictOpts {
strict?: boolean;
}
interface Wide {
id: string;
window: number;
kind?: string;
limit: number;
opts?: FastOpts | DepthOpts | StrictOpts;
}
interface Judge {
id: string;
window: number;
kind: string;
opts?: StrictOpts;
}
interface Narrow {
id: string;
window: number;
kind?: string;
limit: number;
opts?: FastOpts | DepthOpts;
}
function show(entry: Wide | Judge): string {
const limit = "limit" in entry ? ` limit=${entry.limit}` : "";
return `${entry.kind ?? "wide"} ${entry.id} window=${entry.window}${limit} ${JSON.stringify(entry.opts ?? null)}`;
}
const narrow: Narrow = { id: "n1", window: 4, limit: 2, opts: { depth: 3 } };
console.log(show(narrow));
const entries: (Wide | Judge)[] = [narrow, { id: "j1", window: 1, kind: "judge", opts: { strict: true } }];
const plain: Narrow = { id: "n2", window: 8, kind: "custom", limit: 5 };
entries.push(plain);
for (const entry of entries) console.log(show(entry));
let latest: Wide | Judge | undefined;
for (const item of [narrow, plain]) latest = item;
console.log(latest === undefined ? "none" : show(latest));
@@ -0,0 +1 @@
{ "plain": 11809, "san": 13580 }