From 68adac88e0c67ed610aa418cfe93bde22bf9393e Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Tue, 25 Aug 2026 14:08:00 -0700 Subject: [PATCH 1/2] Disable expression type caching in CFA for loops --- tsc/internal/checker/checker.go | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/tsc/internal/checker/checker.go b/tsc/internal/checker/checker.go index 9c9cb64c78a31..4f5d2e3abef17 100644 --- a/tsc/internal/checker/checker.go +++ b/tsc/internal/checker/checker.go @@ -7605,7 +7605,7 @@ func (c *Checker) checkExpressionCached(node *ast.Node) *Type { } func (c *Checker) checkExpressionCachedEx(node *ast.Node, checkMode CheckMode) *Type { - if checkMode != CheckModeNormal { + if checkMode != CheckModeNormal || len(c.flowLoopStack) != 0 { return c.checkExpressionEx(node, checkMode) } links := c.typeNodeLinks.Get(node) @@ -7613,13 +7613,7 @@ func (c *Checker) checkExpressionCachedEx(node *ast.Node, checkMode CheckMode) * // When computing a type that we're going to cache, we need to ignore any ongoing control flow // analysis because variables may have transient types in indeterminable states. Moving flowLoopStart // to the top of the stack ensures all transient types are computed from a known point. - saveFlowLoopStack := c.flowLoopStack - saveFlowTypeCache := c.flowTypeCache - c.flowLoopStack = nil - c.flowTypeCache = nil links.resolvedType = c.checkExpressionEx(node, checkMode) - c.flowTypeCache = saveFlowTypeCache - c.flowLoopStack = saveFlowLoopStack } return links.resolvedType } @@ -30224,11 +30218,7 @@ func (c *Checker) getEffectiveCallArguments(node *ast.Node) []*ast.Node { var spreadType *Type // We can call checkExpressionCached because spread expressions never have a contextual type. if ast.IsSpreadElement(arg) { - if len(c.flowLoopStack) != 0 { - spreadType = c.checkExpression(arg.Expression()) - } else { - spreadType = c.checkExpressionCached(arg.Expression()) - } + spreadType = c.checkExpressionCached(arg.Expression()) } if spreadType != nil && isTupleType(spreadType) { for i, t := range c.getElementTypes(spreadType) { From 15a2420ded95b3d3e597a25c3db6b297f012ed5f Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Tue, 25 Aug 2026 14:09:31 -0700 Subject: [PATCH 2/2] Accept new baselines --- ...initeRecursionDestructuringLoop.errors.txt | 46 ------------------- .../infiniteRecursionDestructuringLoop.types | 32 ++++++------- 2 files changed, 16 insertions(+), 62 deletions(-) delete mode 100644 tsc/testdata/baselines/reference/compiler/infiniteRecursionDestructuringLoop.errors.txt diff --git a/tsc/testdata/baselines/reference/compiler/infiniteRecursionDestructuringLoop.errors.txt b/tsc/testdata/baselines/reference/compiler/infiniteRecursionDestructuringLoop.errors.txt deleted file mode 100644 index 6cafda70f1702..0000000000000 --- a/tsc/testdata/baselines/reference/compiler/infiniteRecursionDestructuringLoop.errors.txt +++ /dev/null @@ -1,46 +0,0 @@ -infiniteRecursionDestructuringLoop.ts(11,17): error TS7022: 'children' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer. -infiniteRecursionDestructuringLoop.ts(11,27): error TS7022: 'index' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer. -infiniteRecursionDestructuringLoop.ts(27,17): error TS7022: 'children' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer. -infiniteRecursionDestructuringLoop.ts(27,27): error TS7022: 'index' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer. - - -==== infiniteRecursionDestructuringLoop.ts (4 errors) ==== - // Repro from https://github.com/microsoft/TypeScript/issues/63192 - - interface Node { - children?: readonly Node[]; - index?: number; - } - - function IterateNodes(data: { node: Node }) { - let node: Node | undefined = data.node; - while (node) { - const { children, index = -1 } = node; - ~~~~~~~~ -!!! error TS7022: 'children' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer. - ~~~~~ -!!! error TS7022: 'index' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer. - const activeNode: Node | undefined = index != -1 && children ? children[index] : undefined; - - node = activeNode; - } - } - - // Simplified repro - interface MyNode { - children: MyNode[]; - index?: number; - } - - function f(init: MyNode) { - let node: MyNode | undefined = init; - while (node) { - const { children, index = 0 } = node; - ~~~~~~~~ -!!! error TS7022: 'children' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer. - ~~~~~ -!!! error TS7022: 'index' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer. - node = children[index]; - } - } - \ No newline at end of file diff --git a/tsc/testdata/baselines/reference/compiler/infiniteRecursionDestructuringLoop.types b/tsc/testdata/baselines/reference/compiler/infiniteRecursionDestructuringLoop.types index 2c80de5323db8..44c05ce35f3b6 100644 --- a/tsc/testdata/baselines/reference/compiler/infiniteRecursionDestructuringLoop.types +++ b/tsc/testdata/baselines/reference/compiler/infiniteRecursionDestructuringLoop.types @@ -26,24 +26,24 @@ function IterateNodes(data: { node: Node }) { >node : Node | undefined const { children, index = -1 } = node; ->children : any ->index : any +>children : readonly Node[] | undefined +>index : number >-1 : -1 >1 : 1 >node : Node const activeNode: Node | undefined = index != -1 && children ? children[index] : undefined; >activeNode : Node | undefined ->index != -1 && children ? children[index] : undefined : any ->index != -1 && children : any +>index != -1 && children ? children[index] : undefined : Node | undefined +>index != -1 && children : false | readonly Node[] | undefined >index != -1 : boolean ->index : any +>index : number >-1 : -1 >1 : 1 ->children : any ->children[index] : any ->children : any ->index : any +>children : readonly Node[] | undefined +>children[index] : Node +>children : readonly Node[] +>index : number >undefined : undefined node = activeNode; @@ -71,20 +71,20 @@ function f(init: MyNode) { >init : MyNode while (node) { ->node : MyNode | undefined +>node : MyNode const { children, index = 0 } = node; ->children : any ->index : any +>children : MyNode[] +>index : number >0 : 0 >node : MyNode node = children[index]; ->node = children[index] : any +>node = children[index] : MyNode >node : MyNode | undefined ->children[index] : any ->children : any ->index : any +>children[index] : MyNode +>children : MyNode[] +>index : number } }