diff --git a/packages/compiler/src/frontend/lowering/lowerer.ts b/packages/compiler/src/frontend/lowering/lowerer.ts index 97131ba96..9b90c008b 100644 --- a/packages/compiler/src/frontend/lowering/lowerer.ts +++ b/packages/compiler/src/frontend/lowering/lowerer.ts @@ -5377,14 +5377,25 @@ export class Lowerer { /** `const { a, b } = table` over an index-signature record: a name that * is not a declared field reads the index signature and can be * missing. */ - const scanRecordPattern = (name: ts.ObjectBindingPattern, init: ts.Expression): boolean => { - const sourceType = this.mapTypeOf(this.typeOf(init)); + const scanRecordPattern = ( + name: ts.ObjectBindingPattern, + init: ts.Expression, + sourceType: IrType | null = this.mapTypeOf(this.typeOf(init)), + ): boolean => { if (sourceType?.kind !== "record") return false; const shape = this.shapes.get(sourceType.shapeId); - const indexValue = shape?.indexValue; - if (!shape || shape.tuple || !indexValue || indexValue.kind === "dyn") return false; + if (!shape || shape.tuple) return false; + const indexValue = shape.indexValue; let changed = false; for (const el of name.elements.nodes) { + // A nested pattern reads its element the same way + // (`const { limits: { [key]: limit } } = config`). + if (!el.dotDotDotToken && el.name !== undefined && ts.isObjectBindingPattern(el.name)) { + if (scanRecordPattern(el.name, init, this.mapTypeOf(this.typeOf(el.name)))) + changed = true; + continue; + } + if (!indexValue || indexValue.kind === "dyn") continue; if (el.dotDotDotToken || el.initializer || !ts.isIdentifier(el.name)) continue; const key = el.propertyName ?? el.name; // A computed key that folds to one name reads like that name; a diff --git a/tests/corpus/6492-runtime-key-destructuring.ts b/tests/corpus/6492-runtime-key-destructuring.ts index aa338c177..ec346550e 100644 --- a/tests/corpus/6492-runtime-key-destructuring.ts +++ b/tests/corpus/6492-runtime-key-destructuring.ts @@ -51,3 +51,14 @@ for (const resource of ["cpu", "memory", "disk"]) { const missingKey = process.argv.length > 99 ? "a" : "missing"; const { [missingKey]: missingWeight } = weights; console.log(missingWeight, missingWeight === undefined); + +// The same in a nested module-scope pattern, with and without a default +// for the nested source. +const nestedConfig = { limits: { cpu: 2 } as Record }; +const { + limits: { [missingKey]: nestedLimit }, +} = nestedConfig; +const { + limits: { [missingKey]: defaultedLimit } = {}, +} = nestedConfig; +console.log(nestedLimit, defaultedLimit === undefined);