Files
scriptc/tests/corpus/destructure-nullable-union-source.js
T
Chris Tate 0725682c87 Check untyped destructuring sources in JavaScript files before reading keys
A JavaScript object pattern over an untyped source (the result of an untyped function, a parameter, a property of an untyped object) now checks the source first when V8 can spell it, as the `any` path in TypeScript files does: a null or undefined source throws "Cannot destructure property 'size' of 'lookup(...)' as it is null." instead of "Cannot read properties of null (reading 'size')". When the first element has a default and a static key (`{ size = 0 }`), the source is not checked first and the read throws, as in V8; this also applies to `any` sources in TypeScript files and to nullable union sources.
2026-10-29 15:58:44 -05:00

104 lines
2.9 KiB
JavaScript

// A destructuring declaration whose source is one object or array type or
// null/undefined (`s.match(re)`, `items.find(...)`) throws Node's TypeError
// when the source is null or undefined, with V8's message for the source
// spelling, and binds normally otherwise.
function show(label, fn) {
try {
fn();
} catch (error) {
console.log(label, error instanceof TypeError, error.message);
}
}
const text = "abc";
show("object-call", () => {
const { index } = text.match(/z/);
console.log("never", index);
});
show("object-empty", () => {
const {} = text.match(/z/);
console.log("never");
});
show("object-identifier", () => {
const found = "abc".match(/z/);
const { index, input } = found;
console.log("never", index, input);
});
show("object-string-key", () => {
const found = "abc".match(/z/);
const { "index": at } = found;
console.log("never", at);
});
show("array-method-call", () => {
const [whole] = "abc".match(/z/);
console.log("never", whole);
});
show("array-identifier", () => {
const found = "abc".match(/z/);
const [whole, group] = found;
console.log("never", whole, group);
});
const items = [
{ name: "a", size: 1 },
{ name: "b", size: 2 },
];
show("object-undefined", () => {
const { name } = items.find((item) => item.size > 5);
console.log("never", name);
});
// A default on the first element: V8 reads that key without checking
// the source first, so the read's TypeError is the one thrown.
show("object-first-default", () => {
const { name = "none", size } = items.find((item) => item.size > 5);
console.log("never", name, size);
});
show("object-later-default", () => {
const { name, size = 0 } = items.find((item) => item.size > 5);
console.log("never", name, size);
});
function firstMatch(text) {
return text.match(/q/);
}
show("array-function-call", () => {
const [first] = firstMatch("abc");
console.log("never", first);
});
// A source that may be null or undefined, and data arms holding a
// generator, which has no untyped form.
function* count() {
yield 1;
yield 2;
}
function pick(kind) {
if (kind === 0) return null;
if (kind === 1) return undefined;
return { counter: count(), label: "picked" };
}
function counters(present) {
return present ? [count(), count()] : null;
}
show("object-null-arm", () => {
const { counter } = pick(0);
console.log("never", counter);
});
show("object-undefined-arm", () => {
const { label } = pick(1);
console.log("never", label);
});
show("array-generator-elements", () => {
const [first] = counters(false);
console.log("never", first);
});
const { counter, label } = pick(2);
const [left, right] = counters(true);
console.log(label, [...counter].join(","), [...left, ...right].join(","));
// Sources that hold a value bind as before.
const { index } = "abc".match(/b/);
const [whole] = "abc".match(/c/);
const { name, size } = items.find((item) => item.size > 1);
console.log("bound", index, whole, name, size);