diff --git a/packages/compiler/src/frontend/lowering/lower-stmts.ts b/packages/compiler/src/frontend/lowering/lower-stmts.ts index 05a8cfa2d9..5027610026 100644 --- a/packages/compiler/src/frontend/lowering/lower-stmts.ts +++ b/packages/compiler/src/frontend/lowering/lower-stmts.ts @@ -2581,14 +2581,46 @@ export function lowerDestructuringDecl( (arm.kind === "array" || arm.kind === "record") ) { const tag = def.arms.indexOf(arm); + const unionId = init.type.unionId; const utmp = lowerer.declareHiddenLocal("%destrU", init.type); out.push({ kind: "varDecl", localId: utmp.id, init, loc }); const uref: IrExpr = { kind: "varRef", localId: utmp.id, type: init.type, loc }; + // The failure branch holds one of the unit arms: the check reads it + // as the dyn null or undefined it is, so the data arm needs no dyn + // form (a record holding a generator has none). + const unitValue = (): IrExpr => { + const unitOf = (t: IrType): IrExpr => + lowerer.coerceToExpected( + { kind: "unitLit", unit: t.kind === "nullT" ? "null" : "undefined", type: t, loc }, + DYN, + ); + const units = def.arms.filter(isUnitType); + const last = units[units.length - 1]!; + return units.slice(0, -1).reduceRight( + (rest, t) => ({ + kind: "ternary", + cond: { + kind: "unionIsTag", + unionId, + tag: def.arms.indexOf(t), + negated: false, + value: uref, + type: BOOL, + loc, + }, + then: unitOf(t), + else_: rest, + type: DYN, + loc, + }), + unitOf(last), + ); + }; const spelling = destrSpellingOf(src); const check: IrExpr | null = ts.isArrayBindingPattern(decl.name) ? { kind: "dynIterN", - value: lowerer.coerceToExpected(uref, DYN), + value: unitValue(), count: decl.name.elements.nodes.length, ...(dynSpell ? { notIterableMessage: dynSpell } : {}), type: DYN, @@ -2597,7 +2629,7 @@ export function lowerDestructuringDecl( : spelling !== null ? { kind: "dynDestrCheck", - value: lowerer.coerceToExpected(uref, DYN), + value: unitValue(), spelling, ...firstPatternProp(lowerer, decl.name as ts.ObjectBindingPattern), type: DYN, diff --git a/tests/corpus/destructure-nullable-union-source.js b/tests/corpus/destructure-nullable-union-source.js index 84af858bfe..b1d9de7b34 100644 --- a/tests/corpus/destructure-nullable-union-source.js +++ b/tests/corpus/destructure-nullable-union-source.js @@ -56,6 +56,36 @@ show("array-function-call", () => { 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/);