mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-11-02 07:43:42 +08:00
`{ ...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>
50 lines
1.7 KiB
TypeScript
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);
|