mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-21 05:50:55 +08:00
80 lines
2.7 KiB
TypeScript
80 lines
2.7 KiB
TypeScript
// Object spread beyond identifier sources: side-effect-free property-chain
|
|
// sources copy field by field (narrowed union reads included), and the
|
|
// conditional-spread idiom `...(c ? { k: v } : {})` compiles as one
|
|
// conditional optional field at the spread's own position — the condition
|
|
// evaluates exactly once, the value only when the arm is taken, and the
|
|
// untaken arm behaves exactly like the absent key (JSON.stringify and the
|
|
// undefined reads agree with Node byte-for-byte).
|
|
|
|
interface Info {
|
|
a: number;
|
|
b: string;
|
|
}
|
|
|
|
function get(): { data?: Info } {
|
|
return { data: { a: 1, b: "x" } };
|
|
}
|
|
|
|
const json = get();
|
|
if (json.data) {
|
|
// A property-chain source (narrowed past `| undefined`): pure re-read.
|
|
const merged = { ...json.data, b: json.data.b + "!" };
|
|
console.log(merged.a, merged.b);
|
|
// The source is untouched (fresh record).
|
|
console.log(json.data.b);
|
|
}
|
|
|
|
interface Entry {
|
|
id: string;
|
|
name?: string;
|
|
cw?: number;
|
|
released?: number;
|
|
}
|
|
|
|
function mk(e: { id: string; name?: string; cw?: number; released?: number }): Entry {
|
|
return {
|
|
id: e.id,
|
|
...(e.name ? { name: e.name } : {}),
|
|
...(e.cw != null ? { cw: e.cw } : {}),
|
|
...(e.released != null ? { released: e.released } : {}),
|
|
};
|
|
}
|
|
|
|
const m1 = mk({ id: "a", name: "N", cw: 5, released: 0 });
|
|
const m2 = mk({ id: "b" });
|
|
const m3 = mk({ id: "c", cw: 0 });
|
|
console.log(m1.id, m1.name ?? "-", m1.cw ?? -1, m1.released ?? -1);
|
|
console.log(m2.id, m2.name ?? "-", m2.cw ?? -1, m2.released ?? -1);
|
|
console.log(m3.id, m3.name ?? "-", m3.cw ?? -1, m3.released ?? -1);
|
|
|
|
// The untaken arm serializes like the absent key (Node drops both).
|
|
console.log(JSON.stringify(mk({ id: "d", name: "only" })));
|
|
|
|
// Side-effect discipline: the condition runs exactly once, the value only
|
|
// when the condition holds.
|
|
let condEvals = 0;
|
|
let valueEvals = 0;
|
|
function cond(answer: boolean): boolean {
|
|
condEvals++;
|
|
return answer;
|
|
}
|
|
function value(): string {
|
|
valueEvals++;
|
|
return "V";
|
|
}
|
|
const took: { id: string; name?: string } = { id: "t", ...(cond(true) ? { name: value() } : {}) };
|
|
const skipped: { id: string; name?: string } = { id: "u", ...(cond(false) ? { name: value() } : {}) };
|
|
console.log(took.name ?? "-", skipped.name ?? "-", condEvals, valueEvals);
|
|
|
|
// The reversed orientation: the empty arm first.
|
|
function alt(flag: boolean): { id: string; tag?: string } {
|
|
return { id: "r", ...(flag ? {} : { tag: "off" }) };
|
|
}
|
|
console.log(alt(true).tag ?? "-", alt(false).tag ?? "-");
|
|
|
|
// Shorthand carrier property.
|
|
function short(label: string | undefined): { id: string; label?: string } {
|
|
return { id: "s", ...(label ? { label } : {}) };
|
|
}
|
|
console.log(short("L").label ?? "-", short(undefined).label ?? "-");
|