Skip to content

Commit c27acd0

Browse files
authored
Fix stack overflow caused by circular destructuring (#2542)
1 parent 9799455 commit c27acd0

File tree

5 files changed

+55
-10
lines changed

5 files changed

+55
-10
lines changed

internal/checker/checker.go

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13326,6 +13326,7 @@ func (c *Checker) getNarrowedTypeOfSymbol(symbol *ast.Symbol, location *ast.Node
1332613326
t := c.getTypeOfSymbol(symbol)
1332713327
declaration := symbol.ValueDeclaration
1332813328
if declaration != nil {
13329+
switch {
1332913330
// If we have a non-rest binding element with no initializer declared as a const variable or a const-like
1333013331
// parameter (a parameter for which there are no assignments in the function body), and if the parent type
1333113332
// for the destructuring is a union type, one or more of the binding elements may represent discriminant
@@ -13349,7 +13350,7 @@ func (c *Checker) getNarrowedTypeOfSymbol(symbol *ast.Symbol, location *ast.Node
1334913350
// the binding pattern AST instance for '{ kind, payload }' as a pseudo-reference and narrow this reference
1335013351
// as if it occurred in the specified location. We then recompute the narrowed binding element type by
1335113352
// destructuring from the narrowed parent type.
13352-
if ast.IsBindingElement(declaration) && declaration.Initializer() == nil && !hasDotDotDotToken(declaration) && len(declaration.Parent.Elements()) >= 2 {
13353+
case ast.IsBindingElement(declaration) && declaration.Initializer() == nil && !hasDotDotDotToken(declaration) && len(declaration.Parent.Elements()) >= 2:
1335313354
parent := declaration.Parent.Parent
1335413355
rootDeclaration := ast.GetRootDeclaration(parent)
1335513356
if ast.IsVariableDeclaration(rootDeclaration) && c.getCombinedNodeFlagsCached(rootDeclaration)&ast.NodeFlagsConstant != 0 || ast.IsParameter(rootDeclaration) {
@@ -13361,21 +13362,21 @@ func (c *Checker) getNarrowedTypeOfSymbol(symbol *ast.Symbol, location *ast.Node
1336113362
if parentType != nil {
1336213363
parentTypeConstraint = c.mapType(parentType, c.getBaseConstraintOrType)
1336313364
}
13364-
links.flags &^= NodeCheckFlagsInCheckIdentifier
1336513365
if parentTypeConstraint != nil && parentTypeConstraint.flags&TypeFlagsUnion != 0 && !(ast.IsParameter(rootDeclaration) && c.isSomeSymbolAssigned(rootDeclaration)) {
1336613366
pattern := declaration.Parent
1336713367
narrowedType := c.getFlowTypeOfReferenceEx(pattern, parentTypeConstraint, parentTypeConstraint, nil /*flowContainer*/, getFlowNodeOfNode(location))
1336813368
if narrowedType.flags&TypeFlagsNever != 0 {
13369-
return c.neverType
13369+
t = c.neverType
13370+
} else {
13371+
// Destructurings are validated against the parent type elsewhere. Here we disable tuple bounds
13372+
// checks because the narrowed type may have lower arity than the full parent type. For example,
13373+
// for the declaration [x, y]: [1, 2] | [3], we may have narrowed the parent type to just [3].
13374+
t = c.getBindingElementTypeFromParentType(declaration, narrowedType, true /*noTupleBoundsCheck*/)
1337013375
}
13371-
// Destructurings are validated against the parent type elsewhere. Here we disable tuple bounds
13372-
// checks because the narrowed type may have lower arity than the full parent type. For example,
13373-
// for the declaration [x, y]: [1, 2] | [3], we may have narrowed the parent type to just [3].
13374-
return c.getBindingElementTypeFromParentType(declaration, narrowedType, true /*noTupleBoundsCheck*/)
1337513376
}
13377+
links.flags &^= NodeCheckFlagsInCheckIdentifier
1337613378
}
1337713379
}
13378-
}
1337913380
// If we have a const-like parameter with no type annotation or initializer, and if the parameter is contextually
1338013381
// typed by a signature with a single rest parameter of a union of tuple types, one or more of the parameters may
1338113382
// represent discriminant tuple elements, and we want the effects of conditional checks on such discriminants to
@@ -13396,7 +13397,7 @@ func (c *Checker) getNarrowedTypeOfSymbol(symbol *ast.Symbol, location *ast.Node
1339613397
// the arrow function AST node for '(kind, payload) => ...' as a pseudo-reference and narrow this reference as
1339713398
// if it occurred in the specified location. We then recompute the narrowed parameter type by indexing into the
1339813399
// narrowed tuple type.
13399-
if ast.IsParameter(declaration) && declaration.Type() == nil && declaration.Initializer() == nil && !hasDotDotDotToken(declaration) {
13400+
case ast.IsParameter(declaration) && declaration.Type() == nil && declaration.Initializer() == nil && !hasDotDotDotToken(declaration):
1340013401
fn := declaration.Parent
1340113402
if len(fn.Parameters()) >= 2 && c.isContextSensitiveFunctionOrObjectLiteralMethod(fn) {
1340213403
contextualSignature := c.getContextualSignature(fn)
@@ -13410,7 +13411,7 @@ func (c *Checker) getNarrowedTypeOfSymbol(symbol *ast.Symbol, location *ast.Node
1341013411
if restType.flags&TypeFlagsUnion != 0 && everyType(restType, isTupleType) && !core.Some(fn.Parameters(), c.isSomeSymbolAssigned) {
1341113412
narrowedType := c.getFlowTypeOfReferenceEx(fn, restType, restType, nil /*flowContainer*/, getFlowNodeOfNode(location))
1341213413
index := slices.Index(fn.Parameters(), declaration) - (core.IfElse(ast.GetThisParameter(fn) != nil, 1, 0))
13413-
return c.getIndexedAccessType(narrowedType, c.getNumberLiteralType(jsnum.Number(index)))
13414+
t = c.getIndexedAccessType(narrowedType, c.getNumberLiteralType(jsnum.Number(index)))
1341413415
}
1341513416
}
1341613417
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
circularDestructuring.ts(1,7): error TS2322: Type '{ c: number; f: any; }' is not assignable to type 'string | number'.
2+
circularDestructuring.ts(1,9): error TS2339: Property 'c' does not exist on type 'string | number'.
3+
circularDestructuring.ts(1,12): error TS2339: Property 'f' does not exist on type 'string | number'.
4+
circularDestructuring.ts(1,12): error TS7022: 'f' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer.
5+
circularDestructuring.ts(1,43): error TS2448: Block-scoped variable 'f' used before its declaration.
6+
7+
8+
==== circularDestructuring.ts (5 errors) ====
9+
const { c, f }: string | number = { c: 0, f };
10+
~~~~~~~~
11+
!!! error TS2322: Type '{ c: number; f: any; }' is not assignable to type 'string | number'.
12+
~
13+
!!! error TS2339: Property 'c' does not exist on type 'string | number'.
14+
~
15+
!!! error TS2339: Property 'f' does not exist on type 'string | number'.
16+
~
17+
!!! error TS7022: 'f' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer.
18+
~
19+
!!! error TS2448: Block-scoped variable 'f' used before its declaration.
20+
!!! related TS2728 circularDestructuring.ts:1:12: 'f' is declared here.
21+
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
//// [tests/cases/compiler/circularDestructuring.ts] ////
2+
3+
=== circularDestructuring.ts ===
4+
const { c, f }: string | number = { c: 0, f };
5+
>c : Symbol(c, Decl(circularDestructuring.ts, 0, 7))
6+
>f : Symbol(f, Decl(circularDestructuring.ts, 0, 10))
7+
>c : Symbol(c, Decl(circularDestructuring.ts, 0, 35))
8+
>f : Symbol(f, Decl(circularDestructuring.ts, 0, 41))
9+
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
//// [tests/cases/compiler/circularDestructuring.ts] ////
2+
3+
=== circularDestructuring.ts ===
4+
const { c, f }: string | number = { c: 0, f };
5+
>c : any
6+
>f : any
7+
>{ c: 0, f } : { c: number; f: any; }
8+
>c : number
9+
>0 : 0
10+
>f : any
11+
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
// @strict: true
2+
// @noEmit: true
3+
const { c, f }: string | number = { c: 0, f };

0 commit comments

Comments
 (0)