Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 29 additions & 1 deletion internal/checker/flow.go
Original file line number Diff line number Diff line change
Expand Up @@ -1738,7 +1738,24 @@ func (c *Checker) tryGetNameFromEntityNameExpression(node *ast.Node) (string, bo
if initializer != nil {
var initializerType *Type
if ast.IsBindingPattern(declaration.Parent) {
// Guard against circularity. When destructuring loop variables have default
// values (e.g. `const { children, index = 0 } = node`), evaluating the binding
// element type to compute a property name for flow analysis can circularly trigger
// flow analysis for the same reference again. This can't be caught by the existing
// pushTypeResolution in getTypeOfVariableOrParameterOrPropertyWorker because that
// path is never entered — the destructuring source variable (`node`) has an explicit
// type annotation, so getTypeOfSymbol returns immediately without entering type
// resolution. The cycle is entirely within flow analysis: isMatchingReference needs
// the binding element type to compute a property name, which triggers parent type
// inference, which evaluates the initializer, which triggers flow analysis again.
// See: https://github.com/microsoft/TypeScript/issues/63192
links := c.nodeLinks.Get(declaration)
if links.flags&NodeCheckFlagsResolvingInitialType != 0 {
return "", false
}
links.flags |= NodeCheckFlagsResolvingInitialType
initializerType = c.getTypeForBindingElement(declaration)
links.flags &^= NodeCheckFlagsResolvingInitialType
} else {
initializerType = c.getTypeOfExpression(initializer)
}
Expand Down Expand Up @@ -2216,7 +2233,18 @@ func (c *Checker) getInitialType(node *ast.Node) *Type {

func (c *Checker) getInitialTypeOfVariableDeclaration(node *ast.Node) *Type {
if node.Initializer() != nil {
return c.getTypeOfInitializer(node.Initializer())
links := c.nodeLinks.Get(node)
if links.flags&NodeCheckFlagsResolvingInitialType != 0 {
// Circularity: we're already computing this variable declaration's initial type.
// This occurs when a binding element's type depends on the destructuring source
// through flow analysis (e.g. destructuring loop variables with default values).
// See: https://github.com/microsoft/TypeScript/issues/63192
return c.errorType
}
links.flags |= NodeCheckFlagsResolvingInitialType
result := c.getTypeOfInitializer(node.Initializer())
links.flags &^= NodeCheckFlagsResolvingInitialType
return result
}
if ast.IsForInStatement(node.Parent.Parent) {
return c.stringType
Expand Down
1 change: 1 addition & 0 deletions internal/checker/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,7 @@ const (
NodeCheckFlagsPartiallyTypeChecked NodeCheckFlags = 1 << 23 // Node has been partially type checked
NodeCheckFlagsInitializerIsUndefined NodeCheckFlags = 1 << 24
NodeCheckFlagsInitializerIsUndefinedComputed NodeCheckFlags = 1 << 25
NodeCheckFlagsResolvingInitialType NodeCheckFlags = 1 << 26
)

// Common links
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
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 (2 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;
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];
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
//// [tests/cases/compiler/infiniteRecursionDestructuringLoop.ts] ////

//// [infiniteRecursionDestructuringLoop.ts]
// 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;
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;
node = children[index];
}
}


//// [infiniteRecursionDestructuringLoop.js]
"use strict";
// Repro from https://github.com/microsoft/TypeScript/issues/63192
function IterateNodes(data) {
let node = data.node;
while (node) {
const { children, index = -1 } = node;
const activeNode = index != -1 && children ? children[index] : undefined;
node = activeNode;
}
}
function f(init) {
let node = init;
while (node) {
const { children, index = 0 } = node;
node = children[index];
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
//// [tests/cases/compiler/infiniteRecursionDestructuringLoop.ts] ////

=== infiniteRecursionDestructuringLoop.ts ===
// Repro from https://github.com/microsoft/TypeScript/issues/63192

interface Node {
>Node : Symbol(Node, Decl(lib.dom.d.ts, --, --), Decl(lib.dom.d.ts, --, --), Decl(infiniteRecursionDestructuringLoop.ts, 0, 0))

children?: readonly Node[];
>children : Symbol(Node.children, Decl(infiniteRecursionDestructuringLoop.ts, 2, 16))
>Node : Symbol(Node, Decl(lib.dom.d.ts, --, --), Decl(lib.dom.d.ts, --, --), Decl(infiniteRecursionDestructuringLoop.ts, 0, 0))

index?: number;
>index : Symbol(Node.index, Decl(infiniteRecursionDestructuringLoop.ts, 3, 31))
}

function IterateNodes(data: { node: Node }) {
>IterateNodes : Symbol(IterateNodes, Decl(infiniteRecursionDestructuringLoop.ts, 5, 1))
>data : Symbol(data, Decl(infiniteRecursionDestructuringLoop.ts, 7, 22))
>node : Symbol(node, Decl(infiniteRecursionDestructuringLoop.ts, 7, 29))
>Node : Symbol(Node, Decl(lib.dom.d.ts, --, --), Decl(lib.dom.d.ts, --, --), Decl(infiniteRecursionDestructuringLoop.ts, 0, 0))

let node: Node | undefined = data.node;
>node : Symbol(node, Decl(infiniteRecursionDestructuringLoop.ts, 8, 7))
>Node : Symbol(Node, Decl(lib.dom.d.ts, --, --), Decl(lib.dom.d.ts, --, --), Decl(infiniteRecursionDestructuringLoop.ts, 0, 0))
>data.node : Symbol(node, Decl(infiniteRecursionDestructuringLoop.ts, 7, 29))
>data : Symbol(data, Decl(infiniteRecursionDestructuringLoop.ts, 7, 22))
>node : Symbol(node, Decl(infiniteRecursionDestructuringLoop.ts, 7, 29))

while (node) {
>node : Symbol(node, Decl(infiniteRecursionDestructuringLoop.ts, 8, 7))

const { children, index = -1 } = node;
>children : Symbol(children, Decl(infiniteRecursionDestructuringLoop.ts, 10, 15))
>index : Symbol(index, Decl(infiniteRecursionDestructuringLoop.ts, 10, 25))
>node : Symbol(node, Decl(infiniteRecursionDestructuringLoop.ts, 8, 7))

const activeNode: Node | undefined = index != -1 && children ? children[index] : undefined;
>activeNode : Symbol(activeNode, Decl(infiniteRecursionDestructuringLoop.ts, 11, 13))
>Node : Symbol(Node, Decl(lib.dom.d.ts, --, --), Decl(lib.dom.d.ts, --, --), Decl(infiniteRecursionDestructuringLoop.ts, 0, 0))
>index : Symbol(index, Decl(infiniteRecursionDestructuringLoop.ts, 10, 25))
>children : Symbol(children, Decl(infiniteRecursionDestructuringLoop.ts, 10, 15))
>children : Symbol(children, Decl(infiniteRecursionDestructuringLoop.ts, 10, 15))
>index : Symbol(index, Decl(infiniteRecursionDestructuringLoop.ts, 10, 25))
>undefined : Symbol(undefined)

node = activeNode;
>node : Symbol(node, Decl(infiniteRecursionDestructuringLoop.ts, 8, 7))
>activeNode : Symbol(activeNode, Decl(infiniteRecursionDestructuringLoop.ts, 11, 13))
}
}

// Simplified repro
interface MyNode {
>MyNode : Symbol(MyNode, Decl(infiniteRecursionDestructuringLoop.ts, 15, 1))

children: MyNode[];
>children : Symbol(MyNode.children, Decl(infiniteRecursionDestructuringLoop.ts, 18, 18))
>MyNode : Symbol(MyNode, Decl(infiniteRecursionDestructuringLoop.ts, 15, 1))

index?: number;
>index : Symbol(MyNode.index, Decl(infiniteRecursionDestructuringLoop.ts, 19, 23))
}

function f(init: MyNode) {
>f : Symbol(f, Decl(infiniteRecursionDestructuringLoop.ts, 21, 1))
>init : Symbol(init, Decl(infiniteRecursionDestructuringLoop.ts, 23, 11))
>MyNode : Symbol(MyNode, Decl(infiniteRecursionDestructuringLoop.ts, 15, 1))

let node: MyNode | undefined = init;
>node : Symbol(node, Decl(infiniteRecursionDestructuringLoop.ts, 24, 7))
>MyNode : Symbol(MyNode, Decl(infiniteRecursionDestructuringLoop.ts, 15, 1))
>init : Symbol(init, Decl(infiniteRecursionDestructuringLoop.ts, 23, 11))

while (node) {
>node : Symbol(node, Decl(infiniteRecursionDestructuringLoop.ts, 24, 7))

const { children, index = 0 } = node;
>children : Symbol(children, Decl(infiniteRecursionDestructuringLoop.ts, 26, 15))
>index : Symbol(index, Decl(infiniteRecursionDestructuringLoop.ts, 26, 25))
>node : Symbol(node, Decl(infiniteRecursionDestructuringLoop.ts, 24, 7))

node = children[index];
>node : Symbol(node, Decl(infiniteRecursionDestructuringLoop.ts, 24, 7))
>children : Symbol(children, Decl(infiniteRecursionDestructuringLoop.ts, 26, 15))
>index : Symbol(index, Decl(infiniteRecursionDestructuringLoop.ts, 26, 25))
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
//// [tests/cases/compiler/infiniteRecursionDestructuringLoop.ts] ////

=== infiniteRecursionDestructuringLoop.ts ===
// Repro from https://github.com/microsoft/TypeScript/issues/63192

interface Node {
children?: readonly Node[];
>children : readonly Node[] | undefined

index?: number;
>index : number | undefined
}

function IterateNodes(data: { node: Node }) {
>IterateNodes : (data: { node: Node; }) => void
>data : { node: Node; }
>node : Node

let node: Node | undefined = data.node;
>node : Node | undefined
>data.node : Node
>data : { node: Node; }
>node : Node

while (node) {
>node : Node | undefined

const { children, index = -1 } = node;
>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 : Node | undefined
>index != -1 && children : false | readonly Node[] | undefined
>index != -1 : boolean
>index : number
>-1 : -1
>1 : 1
>children : readonly Node[] | undefined
>children[index] : Node
>children : readonly Node[]
>index : number
>undefined : undefined

node = activeNode;
>node = activeNode : Node | undefined
>node : Node | undefined
>activeNode : Node | undefined
}
}

// Simplified repro
interface MyNode {
children: MyNode[];
>children : MyNode[]

index?: number;
>index : number | undefined
}

function f(init: MyNode) {
>f : (init: MyNode) => void
>init : MyNode

let node: MyNode | undefined = init;
>node : MyNode | undefined
>init : MyNode

while (node) {
>node : MyNode | undefined

const { children, index = 0 } = node;
>children : any
>index : any
>0 : 0
>node : MyNode

node = children[index];
>node = children[index] : any
>node : MyNode | undefined
>children[index] : any
>children : any
>index : any
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// @strict: true

// 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;
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;
node = children[index];
}
}
Loading