Files
scriptc/tests/corpus/8501-object-spread-optional-source.ts
T
Malte UblandClaude Opus 5.5 61ab4fbc25 Lower an object spread of a maybe-absent union source
`{ ...maybeRecord }` where the source is `T | undefined` was an SC1090
refusal when the target field had no earlier contributor. Node treats an
absent source as a no-op, so a present source now copies field by field
through the checked extraction (`x!`): the record arm copies the value,
and the absent unit arm throws the catchable TypeError instead of leaving
a required slot unset. An optional target field keeps its no-copy arm.

Corpus 8501 pins the present-source copy and the optional destination; a
new errors-harness case covers both arms of the required-field path.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 09:37:06 -07:00

50 lines
1.7 KiB
TypeScript

// A spread source the checker types `T | undefined` — the runtime-optional
// record ABI of callback parameters, an unchecked indexed read, and a for-of
// binding over a dense array. The source is present in every case here, so
// the required destination fields come from the record itself.
interface Rgb {
r: number;
g: number;
b: number;
}
const BASIC: Rgb[] = [
{ r: 1, g: 2, b: 3 },
{ r: 4, g: 5, b: 6 },
{ r: 7, g: 8, b: 9 },
];
function basicColor(index: number): Rgb {
if (index < 16) return { ...BASIC[index] };
return { r: 0, g: 0, b: 0 };
}
for (const i of [0, 2]) {
const color = basicColor(i);
console.log("basic", i, color.r, color.g, color.b);
}
interface Entry {
path: string;
isDirectory: boolean;
}
const entries: Entry[] = [
{ path: "src", isDirectory: true },
{ path: "readme.md", isDirectory: false },
];
// Dense slice callbacks read through the runtime-optional record ABI.
const scored = entries.slice().map((entry) => ({ ...entry, score: entry.isDirectory ? 1 : 2 }));
for (const s of scored) console.log("scored", s.path, s.isDirectory, s.score);
type Source = { source: string; scope: "user" | "project" };
type Target = Source & { parsed: number };
const sources: Source[] = [
{ source: "a", scope: "user" },
{ source: "b", scope: "project" },
];
const parsed: Target[] = [];
for (const entry of sources) parsed.push({ ...entry, parsed: 1 });
for (const p of parsed) console.log("parsed", p.source, p.scope, p.parsed);
// A default first: the earlier contributor fills what the source can lack.
const defaults: Rgb = { r: 0, g: 0, b: 0 };
const merged = BASIC.slice().map((entry) => ({ ...defaults, ...entry }));
for (const m of merged) console.log("merged", m.r, m.g, m.b);