mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-11-08 08:44:37 +08:00
An object pattern over a union whose arms type a field differently, or do not declare it at all, was refused with SC1031, even though the pattern element's own type is the checker's answer: the join of the arms' declared types, with undefined for an arm that omits the key, or the narrowed arm when a discriminant test or an `in` guard refined the source at the pattern's site. Keep the unionDisc fast path wherever every arm declares the field with one type, so programs that compile today emit identical IR. Otherwise read through the existing keyed join (unionKeyGet): each arm answers its declared type and a record arm that does not declare the key answers undefined, which is exactly the desugar's own property read. Only destructuring passes the new missingFieldAsUndefined flag, so dotted reads keep their current refusals. Narrow first when the un-narrowed pattern does not fit: bind the checker-narrowed view of the source to a hidden local — a trusting arm extraction for one arm, a sub-union re-tag whose excluded arms trap, and the stored layout view for an intersection refinement — and lower the pattern over that view. A rest element over a union source packs each arm's unconsumed fields into a fresh record of the checker's arm-wise rest type, selected by the runtime tag; CopyDataProperties makes a fresh object too, so nothing aliases the source. Shapes that do not spell the rest type still fence. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
70 lines
2.5 KiB
TypeScript
70 lines
2.5 KiB
TypeScript
// Destructuring now lowers defaults in every position (tuple, array with
|
|
// the bounds test, record fields, nested patterns, whole-pattern and
|
|
// rest-pattern parameters, getter results), rest in every declaration
|
|
// position (array slices, tuple tails pack fresh, object rest copies the
|
|
// unconsumed fields — class instances included, corpus 2539),
|
|
// assignment-position patterns over static sources — record AND
|
|
// class-instance, with nested patterns, property/element targets, and
|
|
// defaults (corpus 2536-2538) — and for-of expression heads. What stays
|
|
// fenced is the honest residue: METHOD extraction over class instances
|
|
// (a detached method loses its receiver), rest whose packed type is not
|
|
// a plain record (a class of methods only packs '{}' — 'unknown'),
|
|
// rest that would copy a NON-PUBLIC field (JS copies it; the rest type
|
|
// cannot name it), setter-only properties, union sources (narrow
|
|
// first), object patterns over arrays, index-signature rest packing,
|
|
// defaults on NESTED assignment targets (no single binding type to
|
|
// test against), and assignment targets with no static write form.
|
|
|
|
class C {
|
|
f = 1;
|
|
describe(): string {
|
|
return `f=${this.f}`;
|
|
}
|
|
set onlyIn(v: number) {
|
|
this.f = v;
|
|
}
|
|
}
|
|
const { describe } = new C(); // method extraction keeps the fence
|
|
console.log(describe());
|
|
const { f, ...restOfC } = new C(); // rest over a class instance keeps the fence
|
|
console.log(f, Object.keys(restOfC).length);
|
|
const { onlyIn } = new C(); // tsc types the setter-only read; the fence names it
|
|
console.log(onlyIn);
|
|
|
|
interface E1 {
|
|
kind: "one";
|
|
b: string;
|
|
count: number;
|
|
}
|
|
interface E2 {
|
|
kind: "two";
|
|
b: number;
|
|
label: string;
|
|
}
|
|
function fromUnion(u: E1 | E2): string {
|
|
const { b } = u; // arms type the field differently: the joined read compiles
|
|
return String(b);
|
|
}
|
|
console.log(fromUnion({ kind: "one", b: "s", count: 1 }));
|
|
|
|
const xs = [1, 2, 3];
|
|
const { push: append } = xs; // object patterns over arrays read positions and length only
|
|
console.log(typeof append);
|
|
|
|
const indexed: Record<string, number> = { a: 1 };
|
|
const { ...allOfIt } = indexed; // index-signature rest needs overflow packing
|
|
console.log(allOfIt["a"]);
|
|
|
|
const holder = { inner: { v: 1 } };
|
|
let sink = 0;
|
|
({ inner: { v: sink } = { v: 2 } } = holder); // a DEFAULT on a nested assignment target keeps the fence
|
|
console.log(sink);
|
|
|
|
class WithPrivate {
|
|
pub = 1;
|
|
private hidden = 2;
|
|
also = 3;
|
|
}
|
|
const { pub, ...restOfPriv } = new WithPrivate(); // JS copies 'hidden' into the rest object; the rest type cannot name it
|
|
console.log(pub, JSON.stringify(restOfPriv));
|