From b69e485840defd2a0ac39a620131b3d0313f0cce Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 16 Jan 2026 18:05:21 +0000 Subject: [PATCH 1/4] Initial plan From 43898d202f1f4c21fdc977d76644013aac5f48f1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 16 Jan 2026 18:14:45 +0000 Subject: [PATCH 2/4] Progress: add regression test for destructuring contextual typing Co-authored-by: RyanCavanaugh <6685088+RyanCavanaugh@users.noreply.github.com> --- src/compiler/checker.ts | 2 ++ .../compiler/destructuringContextualBindingStackOverflow.ts | 3 +++ 2 files changed, 5 insertions(+) create mode 100644 tests/cases/compiler/destructuringContextualBindingStackOverflow.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 390c843b0c968..5638bf4e32dbf 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -45047,7 +45047,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // Don't validate for-in initializer as it is already an error const widenedType = getWidenedTypeForVariableLikeDeclaration(node); if (needCheckInitializer) { + contextualBindingPatterns.push(node.name); const initializerType = checkExpressionCached(node.initializer); + contextualBindingPatterns.pop(); if (strictNullChecks && needCheckWidenedType) { checkNonNullNonVoidType(initializerType, node); } diff --git a/tests/cases/compiler/destructuringContextualBindingStackOverflow.ts b/tests/cases/compiler/destructuringContextualBindingStackOverflow.ts new file mode 100644 index 0000000000000..986d2fde21308 --- /dev/null +++ b/tests/cases/compiler/destructuringContextualBindingStackOverflow.ts @@ -0,0 +1,3 @@ +// @strict: true + +const { c, f }: string | number | symbol = { c: 0, f }; From 2abf5a1a7b330116a60318b084695544fb830e37 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 16 Jan 2026 18:18:37 +0000 Subject: [PATCH 3/4] Guard destructuring initializer contextual typing Co-authored-by: RyanCavanaugh <6685088+RyanCavanaugh@users.noreply.github.com> --- src/compiler/checker.ts | 16 ++++++++++++++-- ...ngContextualBindingStackOverflow.errors.txt | 18 ++++++++++++++++++ ...tructuringContextualBindingStackOverflow.js | 9 +++++++++ ...uringContextualBindingStackOverflow.symbols | 9 +++++++++ ...cturingContextualBindingStackOverflow.types | 17 +++++++++++++++++ 5 files changed, 67 insertions(+), 2 deletions(-) create mode 100644 tests/baselines/reference/destructuringContextualBindingStackOverflow.errors.txt create mode 100644 tests/baselines/reference/destructuringContextualBindingStackOverflow.js create mode 100644 tests/baselines/reference/destructuringContextualBindingStackOverflow.symbols create mode 100644 tests/baselines/reference/destructuringContextualBindingStackOverflow.types diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 5638bf4e32dbf..639d0ab4c96d9 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -28312,7 +28312,19 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // from its initializer, we'll already have cached the type. Otherwise we compute it now // without caching such that transient types are reflected. const links = getNodeLinks(node); - return links.resolvedType || getTypeOfExpression(node); + if (links.resolvedType) { + return links.resolvedType; + } + const pattern = isVariableDeclaration(node.parent) && node.parent.initializer === node && isBindingPattern(node.parent.name) + ? node.parent.name + : undefined; + if (pattern) { + contextualBindingPatterns.push(pattern); + const type = getTypeOfExpression(node); + contextualBindingPatterns.pop(); + return type; + } + return getTypeOfExpression(node); } function getInitialTypeOfVariableDeclaration(node: VariableDeclaration) { @@ -31023,7 +31035,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // If the identifier is declared in a binding pattern for which we're currently computing the implied type and the // reference occurs with the same binding pattern, return the non-inferrable any type. This for example occurs in // 'const [a, b = a + 1] = [2]' when we're computing the contextual type for the array literal '[2]'. - if (declaration && declaration.kind === SyntaxKind.BindingElement && contains(contextualBindingPatterns, declaration.parent) && findAncestor(node, parent => parent === declaration!.parent)) { + if (declaration && declaration.kind === SyntaxKind.BindingElement && contains(contextualBindingPatterns, declaration.parent)) { return nonInferrableAnyType; } diff --git a/tests/baselines/reference/destructuringContextualBindingStackOverflow.errors.txt b/tests/baselines/reference/destructuringContextualBindingStackOverflow.errors.txt new file mode 100644 index 0000000000000..be4d1692381f5 --- /dev/null +++ b/tests/baselines/reference/destructuringContextualBindingStackOverflow.errors.txt @@ -0,0 +1,18 @@ +destructuringContextualBindingStackOverflow.ts(1,7): error TS2322: Type '{ c: number; f: any; }' is not assignable to type 'string | number | symbol'. +destructuringContextualBindingStackOverflow.ts(1,9): error TS2339: Property 'c' does not exist on type 'string | number | symbol'. +destructuringContextualBindingStackOverflow.ts(1,12): error TS2339: Property 'f' does not exist on type 'string | number | symbol'. +destructuringContextualBindingStackOverflow.ts(1,52): error TS2448: Block-scoped variable 'f' used before its declaration. + + +==== destructuringContextualBindingStackOverflow.ts (4 errors) ==== + const { c, f }: string | number | symbol = { c: 0, f }; + ~~~~~~~~ +!!! error TS2322: Type '{ c: number; f: any; }' is not assignable to type 'string | number | symbol'. + ~ +!!! error TS2339: Property 'c' does not exist on type 'string | number | symbol'. + ~ +!!! error TS2339: Property 'f' does not exist on type 'string | number | symbol'. + ~ +!!! error TS2448: Block-scoped variable 'f' used before its declaration. +!!! related TS2728 destructuringContextualBindingStackOverflow.ts:1:12: 'f' is declared here. + \ No newline at end of file diff --git a/tests/baselines/reference/destructuringContextualBindingStackOverflow.js b/tests/baselines/reference/destructuringContextualBindingStackOverflow.js new file mode 100644 index 0000000000000..9d1de147d23d4 --- /dev/null +++ b/tests/baselines/reference/destructuringContextualBindingStackOverflow.js @@ -0,0 +1,9 @@ +//// [tests/cases/compiler/destructuringContextualBindingStackOverflow.ts] //// + +//// [destructuringContextualBindingStackOverflow.ts] +const { c, f }: string | number | symbol = { c: 0, f }; + + +//// [destructuringContextualBindingStackOverflow.js] +"use strict"; +var _a = { c: 0, f: f }, c = _a.c, f = _a.f; diff --git a/tests/baselines/reference/destructuringContextualBindingStackOverflow.symbols b/tests/baselines/reference/destructuringContextualBindingStackOverflow.symbols new file mode 100644 index 0000000000000..1d8fc2524a402 --- /dev/null +++ b/tests/baselines/reference/destructuringContextualBindingStackOverflow.symbols @@ -0,0 +1,9 @@ +//// [tests/cases/compiler/destructuringContextualBindingStackOverflow.ts] //// + +=== destructuringContextualBindingStackOverflow.ts === +const { c, f }: string | number | symbol = { c: 0, f }; +>c : Symbol(c, Decl(destructuringContextualBindingStackOverflow.ts, 0, 7)) +>f : Symbol(f, Decl(destructuringContextualBindingStackOverflow.ts, 0, 10)) +>c : Symbol(c, Decl(destructuringContextualBindingStackOverflow.ts, 0, 44)) +>f : Symbol(f, Decl(destructuringContextualBindingStackOverflow.ts, 0, 50)) + diff --git a/tests/baselines/reference/destructuringContextualBindingStackOverflow.types b/tests/baselines/reference/destructuringContextualBindingStackOverflow.types new file mode 100644 index 0000000000000..a1d79026336b8 --- /dev/null +++ b/tests/baselines/reference/destructuringContextualBindingStackOverflow.types @@ -0,0 +1,17 @@ +//// [tests/cases/compiler/destructuringContextualBindingStackOverflow.ts] //// + +=== destructuringContextualBindingStackOverflow.ts === +const { c, f }: string | number | symbol = { c: 0, f }; +>c : any +> : ^^^ +>f : any +> : ^^^ +>{ c: 0, f } : { c: number; f: any; } +> : ^^^^^^^^^^^^^^^^^^^^^^ +>c : number +> : ^^^^^^ +>0 : 0 +> : ^ +>f : any +> : ^^^ + From 0392f4f55fe81efca0dd77c672c2f94c9d97b8df Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 16 Jan 2026 20:34:12 +0000 Subject: [PATCH 4/4] Add additional destructuring recursion tests Co-authored-by: RyanCavanaugh <6685088+RyanCavanaugh@users.noreply.github.com> --- ...gContextualBindingStackOverflow.errors.txt | 34 +++++++++++++- ...ructuringContextualBindingStackOverflow.js | 4 ++ ...ringContextualBindingStackOverflow.symbols | 15 +++++++ ...turingContextualBindingStackOverflow.types | 44 +++++++++++++++++++ ...ructuringContextualBindingStackOverflow.ts | 2 + 5 files changed, 98 insertions(+), 1 deletion(-) diff --git a/tests/baselines/reference/destructuringContextualBindingStackOverflow.errors.txt b/tests/baselines/reference/destructuringContextualBindingStackOverflow.errors.txt index be4d1692381f5..ca54c0d2c73dc 100644 --- a/tests/baselines/reference/destructuringContextualBindingStackOverflow.errors.txt +++ b/tests/baselines/reference/destructuringContextualBindingStackOverflow.errors.txt @@ -2,9 +2,18 @@ destructuringContextualBindingStackOverflow.ts(1,7): error TS2322: Type '{ c: nu destructuringContextualBindingStackOverflow.ts(1,9): error TS2339: Property 'c' does not exist on type 'string | number | symbol'. destructuringContextualBindingStackOverflow.ts(1,12): error TS2339: Property 'f' does not exist on type 'string | number | symbol'. destructuringContextualBindingStackOverflow.ts(1,52): error TS2448: Block-scoped variable 'f' used before its declaration. +destructuringContextualBindingStackOverflow.ts(2,7): error TS2322: Type '{ a: number; f: any; }' is not assignable to type 'string | number'. +destructuringContextualBindingStackOverflow.ts(2,9): error TS2339: Property 'a' does not exist on type 'string | number'. +destructuringContextualBindingStackOverflow.ts(2,12): error TS2339: Property 'f' does not exist on type 'string | number'. +destructuringContextualBindingStackOverflow.ts(2,55): error TS2448: Block-scoped variable 'f1' used before its declaration. +destructuringContextualBindingStackOverflow.ts(3,7): error TS2322: Type '{ a: any; f: any; }' is not assignable to type 'string | number'. +destructuringContextualBindingStackOverflow.ts(3,9): error TS2339: Property 'a' does not exist on type 'string | number'. +destructuringContextualBindingStackOverflow.ts(3,16): error TS2339: Property 'f' does not exist on type 'string | number'. +destructuringContextualBindingStackOverflow.ts(3,48): error TS2448: Block-scoped variable 'f2' used before its declaration. +destructuringContextualBindingStackOverflow.ts(3,55): error TS2448: Block-scoped variable 'a1' used before its declaration. -==== destructuringContextualBindingStackOverflow.ts (4 errors) ==== +==== destructuringContextualBindingStackOverflow.ts (13 errors) ==== const { c, f }: string | number | symbol = { c: 0, f }; ~~~~~~~~ !!! error TS2322: Type '{ c: number; f: any; }' is not assignable to type 'string | number | symbol'. @@ -15,4 +24,27 @@ destructuringContextualBindingStackOverflow.ts(1,52): error TS2448: Block-scoped ~ !!! error TS2448: Block-scoped variable 'f' used before its declaration. !!! related TS2728 destructuringContextualBindingStackOverflow.ts:1:12: 'f' is declared here. + const { a, f: f1 }: string | number = { a: 0, f: (1 + f1) }; + ~~~~~~~~~~~~ +!!! error TS2322: Type '{ a: number; f: any; }' is not assignable to type 'string | number'. + ~ +!!! error TS2339: Property 'a' does not exist on type 'string | number'. + ~ +!!! error TS2339: Property 'f' does not exist on type 'string | number'. + ~~ +!!! error TS2448: Block-scoped variable 'f1' used before its declaration. +!!! related TS2728 destructuringContextualBindingStackOverflow.ts:2:15: 'f1' is declared here. + const { a: a1, f: f2 }: string | number = { a: f2, f: a1 }; + ~~~~~~~~~~~~~~~~ +!!! error TS2322: Type '{ a: any; f: any; }' is not assignable to type 'string | number'. + ~ +!!! error TS2339: Property 'a' does not exist on type 'string | number'. + ~ +!!! error TS2339: Property 'f' does not exist on type 'string | number'. + ~~ +!!! error TS2448: Block-scoped variable 'f2' used before its declaration. +!!! related TS2728 destructuringContextualBindingStackOverflow.ts:3:19: 'f2' is declared here. + ~~ +!!! error TS2448: Block-scoped variable 'a1' used before its declaration. +!!! related TS2728 destructuringContextualBindingStackOverflow.ts:3:12: 'a1' is declared here. \ No newline at end of file diff --git a/tests/baselines/reference/destructuringContextualBindingStackOverflow.js b/tests/baselines/reference/destructuringContextualBindingStackOverflow.js index 9d1de147d23d4..9e03fadbbd5cf 100644 --- a/tests/baselines/reference/destructuringContextualBindingStackOverflow.js +++ b/tests/baselines/reference/destructuringContextualBindingStackOverflow.js @@ -2,8 +2,12 @@ //// [destructuringContextualBindingStackOverflow.ts] const { c, f }: string | number | symbol = { c: 0, f }; +const { a, f: f1 }: string | number = { a: 0, f: (1 + f1) }; +const { a: a1, f: f2 }: string | number = { a: f2, f: a1 }; //// [destructuringContextualBindingStackOverflow.js] "use strict"; var _a = { c: 0, f: f }, c = _a.c, f = _a.f; +var _b = { a: 0, f: (1 + f1) }, a = _b.a, f1 = _b.f; +var _c = { a: f2, f: a1 }, a1 = _c.a, f2 = _c.f; diff --git a/tests/baselines/reference/destructuringContextualBindingStackOverflow.symbols b/tests/baselines/reference/destructuringContextualBindingStackOverflow.symbols index 1d8fc2524a402..45852adeb76c4 100644 --- a/tests/baselines/reference/destructuringContextualBindingStackOverflow.symbols +++ b/tests/baselines/reference/destructuringContextualBindingStackOverflow.symbols @@ -7,3 +7,18 @@ const { c, f }: string | number | symbol = { c: 0, f }; >c : Symbol(c, Decl(destructuringContextualBindingStackOverflow.ts, 0, 44)) >f : Symbol(f, Decl(destructuringContextualBindingStackOverflow.ts, 0, 50)) +const { a, f: f1 }: string | number = { a: 0, f: (1 + f1) }; +>a : Symbol(a, Decl(destructuringContextualBindingStackOverflow.ts, 1, 7)) +>f1 : Symbol(f1, Decl(destructuringContextualBindingStackOverflow.ts, 1, 10)) +>a : Symbol(a, Decl(destructuringContextualBindingStackOverflow.ts, 1, 39)) +>f : Symbol(f, Decl(destructuringContextualBindingStackOverflow.ts, 1, 45)) +>f1 : Symbol(f1, Decl(destructuringContextualBindingStackOverflow.ts, 1, 10)) + +const { a: a1, f: f2 }: string | number = { a: f2, f: a1 }; +>a1 : Symbol(a1, Decl(destructuringContextualBindingStackOverflow.ts, 2, 7)) +>f2 : Symbol(f2, Decl(destructuringContextualBindingStackOverflow.ts, 2, 14)) +>a : Symbol(a, Decl(destructuringContextualBindingStackOverflow.ts, 2, 43)) +>f2 : Symbol(f2, Decl(destructuringContextualBindingStackOverflow.ts, 2, 14)) +>f : Symbol(f, Decl(destructuringContextualBindingStackOverflow.ts, 2, 50)) +>a1 : Symbol(a1, Decl(destructuringContextualBindingStackOverflow.ts, 2, 7)) + diff --git a/tests/baselines/reference/destructuringContextualBindingStackOverflow.types b/tests/baselines/reference/destructuringContextualBindingStackOverflow.types index a1d79026336b8..d25524462433b 100644 --- a/tests/baselines/reference/destructuringContextualBindingStackOverflow.types +++ b/tests/baselines/reference/destructuringContextualBindingStackOverflow.types @@ -15,3 +15,47 @@ const { c, f }: string | number | symbol = { c: 0, f }; >f : any > : ^^^ +const { a, f: f1 }: string | number = { a: 0, f: (1 + f1) }; +>a : any +> : ^^^ +>f : any +> : ^^^ +>f1 : any +> : ^^^ +>{ a: 0, f: (1 + f1) } : { a: number; f: any; } +> : ^^^^^^^^^^^^^^^^^^^^^^ +>a : number +> : ^^^^^^ +>0 : 0 +> : ^ +>f : any +> : ^^^ +>(1 + f1) : any +> : ^^^ +>1 + f1 : any +> : ^^^ +>1 : 1 +> : ^ +>f1 : any +> : ^^^ + +const { a: a1, f: f2 }: string | number = { a: f2, f: a1 }; +>a : any +> : ^^^ +>a1 : any +> : ^^^ +>f : any +> : ^^^ +>f2 : any +> : ^^^ +>{ a: f2, f: a1 } : { a: any; f: any; } +> : ^^^^^^^^^^^^^^^^^^^ +>a : any +> : ^^^ +>f2 : any +> : ^^^ +>f : any +> : ^^^ +>a1 : any +> : ^^^ + diff --git a/tests/cases/compiler/destructuringContextualBindingStackOverflow.ts b/tests/cases/compiler/destructuringContextualBindingStackOverflow.ts index 986d2fde21308..6aa9592848df6 100644 --- a/tests/cases/compiler/destructuringContextualBindingStackOverflow.ts +++ b/tests/cases/compiler/destructuringContextualBindingStackOverflow.ts @@ -1,3 +1,5 @@ // @strict: true const { c, f }: string | number | symbol = { c: 0, f }; +const { a, f: f1 }: string | number = { a: 0, f: (1 + f1) }; +const { a: a1, f: f2 }: string | number = { a: f2, f: a1 };