From def00df7a22776eb94a92cd4931bb8c39da50a34 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 16 Jan 2026 19:48:21 +0000 Subject: [PATCH 1/2] Initial plan From 9c7eea55ef693d3348d87a82d171320b124f45dd Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 16 Jan 2026 19:59:05 +0000 Subject: [PATCH 2/2] Fix stack overflow in binding element type resolution with circular references Co-authored-by: RyanCavanaugh <6685088+RyanCavanaugh@users.noreply.github.com> --- src/compiler/checker.ts | 8 +- ...larBindingElementTypeResolution.errors.txt | 80 +++++++++++++++++ ...rcularBindingElementTypeResolution.symbols | 41 +++++++++ ...circularBindingElementTypeResolution.types | 87 +++++++++++++++++++ .../circularBindingElementTypeResolution.ts | 14 +++ 5 files changed, 228 insertions(+), 2 deletions(-) create mode 100644 tests/baselines/reference/circularBindingElementTypeResolution.errors.txt create mode 100644 tests/baselines/reference/circularBindingElementTypeResolution.symbols create mode 100644 tests/baselines/reference/circularBindingElementTypeResolution.types create mode 100644 tests/cases/compiler/circularBindingElementTypeResolution.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 390c843b0c968..c3bd1ade76e42 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -11689,8 +11689,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { parentType = getNonNullableType(parentType); } // Filter `undefined` from the type we check against if the parent has an initializer and that initializer is not possibly `undefined` - else if (strictNullChecks && pattern.parent.initializer && !(hasTypeFacts(getTypeOfInitializer(pattern.parent.initializer), TypeFacts.EQUndefined))) { - parentType = getTypeWithFacts(parentType, TypeFacts.NEUndefined); + // Use a non-recursive type check to avoid stack overflow when the initializer references binding elements + else if (strictNullChecks && pattern.parent.initializer) { + const initializerType = getQuickTypeOfExpression(pattern.parent.initializer) || getNodeLinks(pattern.parent.initializer).resolvedType; + if (initializerType && !(hasTypeFacts(initializerType, TypeFacts.EQUndefined))) { + parentType = getTypeWithFacts(parentType, TypeFacts.NEUndefined); + } } const accessFlags = AccessFlags.ExpressionPosition | (noTupleBoundsCheck || hasDefaultValue(declaration) ? AccessFlags.AllowMissing : 0); diff --git a/tests/baselines/reference/circularBindingElementTypeResolution.errors.txt b/tests/baselines/reference/circularBindingElementTypeResolution.errors.txt new file mode 100644 index 0000000000000..3c1757720312b --- /dev/null +++ b/tests/baselines/reference/circularBindingElementTypeResolution.errors.txt @@ -0,0 +1,80 @@ +circularBindingElementTypeResolution.ts(2,7): error TS2322: Type '{ c: number; f: any; }' is not assignable to type 'string | number'. +circularBindingElementTypeResolution.ts(2,9): error TS2339: Property 'c' does not exist on type 'string | number'. +circularBindingElementTypeResolution.ts(2,12): error TS2339: Property 'f' does not exist on type 'string | number'. +circularBindingElementTypeResolution.ts(2,43): error TS2448: Block-scoped variable 'f' used before its declaration. +circularBindingElementTypeResolution.ts(5,7): error TS2322: Type '{ a: number; f2: any; }' is not assignable to type 'string | number'. +circularBindingElementTypeResolution.ts(5,9): error TS2339: Property 'a' does not exist on type 'string | number'. +circularBindingElementTypeResolution.ts(5,12): error TS2339: Property 'f2' does not exist on type 'string | number'. +circularBindingElementTypeResolution.ts(5,53): error TS2448: Block-scoped variable 'f2' used before its declaration. +circularBindingElementTypeResolution.ts(8,7): error TS2322: Type '{ a2: any; f3: any; }' is not assignable to type 'string | number'. +circularBindingElementTypeResolution.ts(8,9): error TS2339: Property 'a2' does not exist on type 'string | number'. +circularBindingElementTypeResolution.ts(8,13): error TS2339: Property 'f3' does not exist on type 'string | number'. +circularBindingElementTypeResolution.ts(8,43): error TS2448: Block-scoped variable 'f3' used before its declaration. +circularBindingElementTypeResolution.ts(8,51): error TS2448: Block-scoped variable 'a2' used before its declaration. +circularBindingElementTypeResolution.ts(11,80): error TS2322: Type 'string' is not assignable to type 'number'. +circularBindingElementTypeResolution.ts(11,83): error TS2448: Block-scoped variable 'y' used before its declaration. +circularBindingElementTypeResolution.ts(11,83): error TS2454: Variable 'y' is used before being assigned. +circularBindingElementTypeResolution.ts(11,86): error TS2322: Type 'number' is not assignable to type 'string'. +circularBindingElementTypeResolution.ts(11,89): error TS2448: Block-scoped variable 'x' used before its declaration. +circularBindingElementTypeResolution.ts(11,89): error TS2454: Variable 'x' is used before being assigned. + + +==== circularBindingElementTypeResolution.ts (19 errors) ==== + // Test case 1: Simple self-reference with shorthand property + const { c, f }: string | number = { c: 0, f }; + ~~~~~~~~ +!!! error TS2322: Type '{ c: number; f: any; }' is not assignable to type 'string | number'. + ~ +!!! error TS2339: Property 'c' does not exist on type 'string | number'. + ~ +!!! error TS2339: Property 'f' does not exist on type 'string | number'. + ~ +!!! error TS2448: Block-scoped variable 'f' used before its declaration. +!!! related TS2728 circularBindingElementTypeResolution.ts:2:12: 'f' is declared here. + + // Test case 2: Self-reference with expression + const { a, f2 }: string | number = { a: 0, f2: (1 + f2) }; + ~~~~~~~~~ +!!! error TS2322: Type '{ a: number; f2: any; }' is not assignable to type 'string | number'. + ~ +!!! error TS2339: Property 'a' does not exist on type 'string | number'. + ~~ +!!! error TS2339: Property 'f2' does not exist on type 'string | number'. + ~~ +!!! error TS2448: Block-scoped variable 'f2' used before its declaration. +!!! related TS2728 circularBindingElementTypeResolution.ts:5:12: 'f2' is declared here. + + // Test case 3: Circular reference between two binding elements + const { a2, f3 }: string | number = { a2: f3, f3: a2 }; + ~~~~~~~~~~ +!!! error TS2322: Type '{ a2: any; f3: any; }' is not assignable to type 'string | number'. + ~~ +!!! error TS2339: Property 'a2' does not exist on type 'string | number'. + ~~ +!!! error TS2339: Property 'f3' does not exist on type 'string | number'. + ~~ +!!! error TS2448: Block-scoped variable 'f3' used before its declaration. +!!! related TS2728 circularBindingElementTypeResolution.ts:8:13: 'f3' is declared here. + ~~ +!!! error TS2448: Block-scoped variable 'a2' used before its declaration. +!!! related TS2728 circularBindingElementTypeResolution.ts:8:9: 'a2' is declared here. + + // Test case 4: Nested destructuring with self-reference + const { nested: { x, y } }: { nested: { x: number, y: string } } = { nested: { x: y, y: x } }; + ~ +!!! error TS2322: Type 'string' is not assignable to type 'number'. +!!! related TS6500 circularBindingElementTypeResolution.ts:11:41: The expected type comes from property 'x' which is declared here on type '{ x: number; y: string; }' + ~ +!!! error TS2448: Block-scoped variable 'y' used before its declaration. +!!! related TS2728 circularBindingElementTypeResolution.ts:11:22: 'y' is declared here. + ~ +!!! error TS2454: Variable 'y' is used before being assigned. + ~ +!!! error TS2322: Type 'number' is not assignable to type 'string'. +!!! related TS6500 circularBindingElementTypeResolution.ts:11:52: The expected type comes from property 'y' which is declared here on type '{ x: number; y: string; }' + ~ +!!! error TS2448: Block-scoped variable 'x' used before its declaration. +!!! related TS2728 circularBindingElementTypeResolution.ts:11:19: 'x' is declared here. + ~ +!!! error TS2454: Variable 'x' is used before being assigned. + \ No newline at end of file diff --git a/tests/baselines/reference/circularBindingElementTypeResolution.symbols b/tests/baselines/reference/circularBindingElementTypeResolution.symbols new file mode 100644 index 0000000000000..10af1e516c3e0 --- /dev/null +++ b/tests/baselines/reference/circularBindingElementTypeResolution.symbols @@ -0,0 +1,41 @@ +//// [tests/cases/compiler/circularBindingElementTypeResolution.ts] //// + +=== circularBindingElementTypeResolution.ts === +// Test case 1: Simple self-reference with shorthand property +const { c, f }: string | number = { c: 0, f }; +>c : Symbol(c, Decl(circularBindingElementTypeResolution.ts, 1, 7)) +>f : Symbol(f, Decl(circularBindingElementTypeResolution.ts, 1, 10)) +>c : Symbol(c, Decl(circularBindingElementTypeResolution.ts, 1, 35)) +>f : Symbol(f, Decl(circularBindingElementTypeResolution.ts, 1, 41)) + +// Test case 2: Self-reference with expression +const { a, f2 }: string | number = { a: 0, f2: (1 + f2) }; +>a : Symbol(a, Decl(circularBindingElementTypeResolution.ts, 4, 7)) +>f2 : Symbol(f2, Decl(circularBindingElementTypeResolution.ts, 4, 10)) +>a : Symbol(a, Decl(circularBindingElementTypeResolution.ts, 4, 36)) +>f2 : Symbol(f2, Decl(circularBindingElementTypeResolution.ts, 4, 42)) +>f2 : Symbol(f2, Decl(circularBindingElementTypeResolution.ts, 4, 10)) + +// Test case 3: Circular reference between two binding elements +const { a2, f3 }: string | number = { a2: f3, f3: a2 }; +>a2 : Symbol(a2, Decl(circularBindingElementTypeResolution.ts, 7, 7)) +>f3 : Symbol(f3, Decl(circularBindingElementTypeResolution.ts, 7, 11)) +>a2 : Symbol(a2, Decl(circularBindingElementTypeResolution.ts, 7, 37)) +>f3 : Symbol(f3, Decl(circularBindingElementTypeResolution.ts, 7, 11)) +>f3 : Symbol(f3, Decl(circularBindingElementTypeResolution.ts, 7, 45)) +>a2 : Symbol(a2, Decl(circularBindingElementTypeResolution.ts, 7, 7)) + +// Test case 4: Nested destructuring with self-reference +const { nested: { x, y } }: { nested: { x: number, y: string } } = { nested: { x: y, y: x } }; +>nested : Symbol(nested, Decl(circularBindingElementTypeResolution.ts, 10, 29)) +>x : Symbol(x, Decl(circularBindingElementTypeResolution.ts, 10, 17)) +>y : Symbol(y, Decl(circularBindingElementTypeResolution.ts, 10, 20)) +>nested : Symbol(nested, Decl(circularBindingElementTypeResolution.ts, 10, 29)) +>x : Symbol(x, Decl(circularBindingElementTypeResolution.ts, 10, 39)) +>y : Symbol(y, Decl(circularBindingElementTypeResolution.ts, 10, 50)) +>nested : Symbol(nested, Decl(circularBindingElementTypeResolution.ts, 10, 68)) +>x : Symbol(x, Decl(circularBindingElementTypeResolution.ts, 10, 78)) +>y : Symbol(y, Decl(circularBindingElementTypeResolution.ts, 10, 20)) +>y : Symbol(y, Decl(circularBindingElementTypeResolution.ts, 10, 84)) +>x : Symbol(x, Decl(circularBindingElementTypeResolution.ts, 10, 17)) + diff --git a/tests/baselines/reference/circularBindingElementTypeResolution.types b/tests/baselines/reference/circularBindingElementTypeResolution.types new file mode 100644 index 0000000000000..d2aa046ea7739 --- /dev/null +++ b/tests/baselines/reference/circularBindingElementTypeResolution.types @@ -0,0 +1,87 @@ +//// [tests/cases/compiler/circularBindingElementTypeResolution.ts] //// + +=== circularBindingElementTypeResolution.ts === +// Test case 1: Simple self-reference with shorthand property +const { c, f }: string | number = { c: 0, f }; +>c : any +> : ^^^ +>f : any +> : ^^^ +>{ c: 0, f } : { c: number; f: any; } +> : ^^^^^^^^^^^^^^^^^^^^^^ +>c : number +> : ^^^^^^ +>0 : 0 +> : ^ +>f : any +> : ^^^ + +// Test case 2: Self-reference with expression +const { a, f2 }: string | number = { a: 0, f2: (1 + f2) }; +>a : any +> : ^^^ +>f2 : any +> : ^^^ +>{ a: 0, f2: (1 + f2) } : { a: number; f2: any; } +> : ^^^^^^^^^^^^^^^^^^^^^^^ +>a : number +> : ^^^^^^ +>0 : 0 +> : ^ +>f2 : any +> : ^^^ +>(1 + f2) : any +> : ^^^ +>1 + f2 : any +> : ^^^ +>1 : 1 +> : ^ +>f2 : any +> : ^^^ + +// Test case 3: Circular reference between two binding elements +const { a2, f3 }: string | number = { a2: f3, f3: a2 }; +>a2 : any +> : ^^^ +>f3 : any +> : ^^^ +>{ a2: f3, f3: a2 } : { a2: any; f3: any; } +> : ^^^^^^^^^^^^^^^^^^^^^ +>a2 : any +> : ^^^ +>f3 : any +> : ^^^ +>f3 : any +> : ^^^ +>a2 : any +> : ^^^ + +// Test case 4: Nested destructuring with self-reference +const { nested: { x, y } }: { nested: { x: number, y: string } } = { nested: { x: y, y: x } }; +>nested : any +> : ^^^ +>x : number +> : ^^^^^^ +>y : string +> : ^^^^^^ +>nested : { x: number; y: string; } +> : ^^^^^ ^^^^^ ^^^ +>x : number +> : ^^^^^^ +>y : string +> : ^^^^^^ +>{ nested: { x: y, y: x } } : { nested: { x: string; y: number; }; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>nested : { x: string; y: number; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^^ +>{ x: y, y: x } : { x: string; y: number; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^^ +>x : string +> : ^^^^^^ +>y : string +> : ^^^^^^ +>y : number +> : ^^^^^^ +>x : number +> : ^^^^^^ + diff --git a/tests/cases/compiler/circularBindingElementTypeResolution.ts b/tests/cases/compiler/circularBindingElementTypeResolution.ts new file mode 100644 index 0000000000000..89ab17942e512 --- /dev/null +++ b/tests/cases/compiler/circularBindingElementTypeResolution.ts @@ -0,0 +1,14 @@ +// @strict: true +// @noEmit: true + +// Test case 1: Simple self-reference with shorthand property +const { c, f }: string | number = { c: 0, f }; + +// Test case 2: Self-reference with expression +const { a, f2 }: string | number = { a: 0, f2: (1 + f2) }; + +// Test case 3: Circular reference between two binding elements +const { a2, f3 }: string | number = { a2: f3, f3: a2 }; + +// Test case 4: Nested destructuring with self-reference +const { nested: { x, y } }: { nested: { x: number, y: string } } = { nested: { x: y, y: x } };