diff --git a/tsc/internal/checker/checker.go b/tsc/internal/checker/checker.go index 9c9cb64c78a31..0f8fe9755160c 100644 --- a/tsc/internal/checker/checker.go +++ b/tsc/internal/checker/checker.go @@ -7608,6 +7608,10 @@ func (c *Checker) checkExpressionCachedEx(node *ast.Node, checkMode CheckMode) * if checkMode != CheckModeNormal { return c.checkExpressionEx(node, checkMode) } + if len(c.flowLoopStack) != 0 { + // Don't cache expression types computed with incomplete loop flow types. + return c.checkExpressionEx(node, checkMode) + } links := c.typeNodeLinks.Get(node) if links.resolvedType == nil { // When computing a type that we're going to cache, we need to ignore any ongoing control flow diff --git a/tsc/testdata/baselines/reference/compiler/asConstObjectAssignedToLoopVariable.symbols b/tsc/testdata/baselines/reference/compiler/asConstObjectAssignedToLoopVariable.symbols new file mode 100644 index 0000000000000..d5329cc451fb0 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/asConstObjectAssignedToLoopVariable.symbols @@ -0,0 +1,37 @@ +//// [tests/cases/compiler/asConstObjectAssignedToLoopVariable.ts] //// + +=== asConstObjectAssignedToLoopVariable.ts === +type Candidate = { mode: "a"; output: unknown } | { mode: "b" }; +>Candidate : Symbol(Candidate, Decl(asConstObjectAssignedToLoopVariable.ts, 0, 0)) +>mode : Symbol(mode, Decl(asConstObjectAssignedToLoopVariable.ts, 0, 18)) +>output : Symbol(output, Decl(asConstObjectAssignedToLoopVariable.ts, 0, 29)) +>mode : Symbol(mode, Decl(asConstObjectAssignedToLoopVariable.ts, 0, 51)) + +export function run(): never { +>run : Symbol(run, Decl(asConstObjectAssignedToLoopVariable.ts, 0, 64)) + + let lastCandidate: Candidate | null = null; +>lastCandidate : Symbol(lastCandidate, Decl(asConstObjectAssignedToLoopVariable.ts, 3, 7)) +>Candidate : Symbol(Candidate, Decl(asConstObjectAssignedToLoopVariable.ts, 0, 0)) + + while (true) { + const candidate: Candidate = { +>candidate : Symbol(candidate, Decl(asConstObjectAssignedToLoopVariable.ts, 5, 13)) +>Candidate : Symbol(Candidate, Decl(asConstObjectAssignedToLoopVariable.ts, 0, 0)) + + mode: "a", +>mode : Symbol(mode, Decl(asConstObjectAssignedToLoopVariable.ts, 5, 38)) + + output: lastCandidate, +>output : Symbol(output, Decl(asConstObjectAssignedToLoopVariable.ts, 6, 22)) +>lastCandidate : Symbol(lastCandidate, Decl(asConstObjectAssignedToLoopVariable.ts, 3, 7)) + + } as const; +>const : Symbol(const) + + lastCandidate = candidate; +>lastCandidate : Symbol(lastCandidate, Decl(asConstObjectAssignedToLoopVariable.ts, 3, 7)) +>candidate : Symbol(candidate, Decl(asConstObjectAssignedToLoopVariable.ts, 5, 13)) + } +} + diff --git a/tsc/testdata/baselines/reference/compiler/asConstObjectAssignedToLoopVariable.types b/tsc/testdata/baselines/reference/compiler/asConstObjectAssignedToLoopVariable.types new file mode 100644 index 0000000000000..c19cf0598d4ed --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/asConstObjectAssignedToLoopVariable.types @@ -0,0 +1,39 @@ +//// [tests/cases/compiler/asConstObjectAssignedToLoopVariable.ts] //// + +=== asConstObjectAssignedToLoopVariable.ts === +type Candidate = { mode: "a"; output: unknown } | { mode: "b" }; +>Candidate : Candidate +>mode : "a" +>output : unknown +>mode : "b" + +export function run(): never { +>run : () => never + + let lastCandidate: Candidate | null = null; +>lastCandidate : Candidate | null + + while (true) { +>true : true + + const candidate: Candidate = { +>candidate : Candidate +>{ mode: "a", output: lastCandidate, } as const : { readonly mode: "a"; readonly output: { mode: "a"; output: unknown; } | null; } +>{ mode: "a", output: lastCandidate, } : { readonly mode: "a"; readonly output: { mode: "a"; output: unknown; } | null; } + + mode: "a", +>mode : "a" +>"a" : "a" + + output: lastCandidate, +>output : { mode: "a"; output: unknown; } | null +>lastCandidate : { mode: "a"; output: unknown; } | null + + } as const; + lastCandidate = candidate; +>lastCandidate = candidate : { mode: "a"; output: unknown; } +>lastCandidate : Candidate | null +>candidate : { mode: "a"; output: unknown; } + } +} + 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 } } diff --git a/tsc/testdata/tests/cases/compiler/asConstObjectAssignedToLoopVariable.ts b/tsc/testdata/tests/cases/compiler/asConstObjectAssignedToLoopVariable.ts new file mode 100644 index 0000000000000..cb88281a5700f --- /dev/null +++ b/tsc/testdata/tests/cases/compiler/asConstObjectAssignedToLoopVariable.ts @@ -0,0 +1,15 @@ +// @strict: true +// @noEmit: true + +type Candidate = { mode: "a"; output: unknown } | { mode: "b" }; + +export function run(): never { + let lastCandidate: Candidate | null = null; + while (true) { + const candidate: Candidate = { + mode: "a", + output: lastCandidate, + } as const; + lastCandidate = candidate; + } +}