diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index f61aa3ab900eb..7a6aaa1861859 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -837,7 +837,8 @@ namespace ts { function isNarrowableReference(expr: Expression): boolean { return expr.kind === SyntaxKind.Identifier || expr.kind === SyntaxKind.ThisKeyword || expr.kind === SyntaxKind.SuperKeyword || (isPropertyAccessExpression(expr) || isNonNullExpression(expr) || isParenthesizedExpression(expr)) && isNarrowableReference(expr.expression) || - isElementAccessExpression(expr) && isStringOrNumericLiteralLike(expr.argumentExpression) && isNarrowableReference(expr.expression); + isElementAccessExpression(expr) && isStringOrNumericLiteralLike(expr.argumentExpression) && isNarrowableReference(expr.expression) || + isAssignmentExpression(expr) && isNarrowableReference(expr.left); } function containsNarrowableReference(expr: Expression): boolean { diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 5c31002cf2773..01b594f870b32 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -20131,6 +20131,8 @@ namespace ts { case SyntaxKind.ParenthesizedExpression: case SyntaxKind.NonNullExpression: return isMatchingReference(source, (target as NonNullExpression | ParenthesizedExpression).expression); + case SyntaxKind.BinaryExpression: + return isAssignmentExpression(target) && isMatchingReference(source, target.left); } switch (source.kind) { case SyntaxKind.Identifier: diff --git a/src/debug/dbg.ts b/src/debug/dbg.ts index fc8a5cdafc87d..7e743bf491c39 100644 --- a/src/debug/dbg.ts +++ b/src/debug/dbg.ts @@ -11,6 +11,10 @@ namespace Debug { type MethodDeclaration = Node; type Expression = Node; type SourceFile = Node; + type VariableDeclaration = Node; + type BindingElement = Node; + type CallExpression = Node; + type BinaryExpression = Node; interface SwitchStatement extends Node { caseBlock: CaseBlock; @@ -59,8 +63,6 @@ namespace Debug { } type FlowNode = - | AfterFinallyFlow - | PreFinallyFlow | FlowStart | FlowLabel | FlowAssignment @@ -76,14 +78,6 @@ namespace Debug { id?: number; } - interface AfterFinallyFlow extends FlowNodeBase { - antecedent: FlowNode; - } - - interface PreFinallyFlow extends FlowNodeBase { - antecedent: FlowNode; - } - interface FlowStart extends FlowNodeBase { node?: FunctionExpression | ArrowFunction | MethodDeclaration; } @@ -93,12 +87,12 @@ namespace Debug { } interface FlowAssignment extends FlowNodeBase { - node: Expression; + node: Expression | VariableDeclaration | BindingElement; antecedent: FlowNode; } interface FlowCall extends FlowNodeBase { - node: Expression; + node: CallExpression; antecedent: FlowNode; } @@ -115,7 +109,7 @@ namespace Debug { } interface FlowArrayMutation extends FlowNodeBase { - node: Expression; + node: CallExpression | BinaryExpression; antecedent: FlowNode; } @@ -192,6 +186,7 @@ namespace Debug { lane: number; endLane: number; level: number; + circular: boolean | "circularity"; } interface FlowGraphEdge { @@ -217,8 +212,9 @@ namespace Debug { const links: Record = Object.create(/*o*/ null); // eslint-disable-line no-null/no-null const nodes: FlowGraphNode[] = []; const edges: FlowGraphEdge[] = []; - const root = buildGraphNode(flowNode); + const root = buildGraphNode(flowNode, new Set()); for (const node of nodes) { + node.text = renderFlowNode(node.flowNode, node.circular); computeLevel(node); } @@ -263,26 +259,43 @@ namespace Debug { return parents; } - function buildGraphNode(flowNode: FlowNode) { + function buildGraphNode(flowNode: FlowNode, seen: Set): FlowGraphNode { const id = getDebugFlowNodeId(flowNode); let graphNode = links[id]; + if (graphNode && seen.has(flowNode)) { + graphNode.circular = true; + graphNode = { + id: -1, + flowNode, + edges: [], + text: "", + lane: -1, + endLane: -1, + level: -1, + circular: "circularity" + }; + nodes.push(graphNode); + return graphNode; + } + seen.add(flowNode); if (!graphNode) { - links[id] = graphNode = { id, flowNode, edges: [], text: renderFlowNode(flowNode), lane: -1, endLane: -1, level: -1 }; + links[id] = graphNode = { id, flowNode, edges: [], text: "", lane: -1, endLane: -1, level: -1, circular: false }; nodes.push(graphNode); if (hasAntecedents(flowNode)) { for (const antecedent of flowNode.antecedents) { - buildGraphEdge(graphNode, antecedent); + buildGraphEdge(graphNode, antecedent, seen); } } else if (hasAntecedent(flowNode)) { - buildGraphEdge(graphNode, flowNode.antecedent); + buildGraphEdge(graphNode, flowNode.antecedent, seen); } } + seen.delete(flowNode); return graphNode; } - function buildGraphEdge(source: FlowGraphNode, antecedent: FlowNode) { - const target = buildGraphNode(antecedent); + function buildGraphEdge(source: FlowGraphNode, antecedent: FlowNode, seen: Set) { + const target = buildGraphNode(antecedent, seen); const edge: FlowGraphEdge = { source, target }; edges.push(edge); source.edges.push(edge); @@ -353,8 +366,11 @@ namespace Debug { return getSourceTextOfNodeFromSourceFile(sourceFile, node, /*includeTrivia*/ false); } - function renderFlowNode(flowNode: FlowNode) { + function renderFlowNode(flowNode: FlowNode, circular: boolean | "circularity") { let text = getHeader(flowNode.flags); + if (circular) { + text = `${text}#${getDebugFlowNodeId(flowNode)}`; + } if (hasNode(flowNode)) { if (flowNode.node) { text += ` (${getNodeText(flowNode.node)})`; @@ -373,7 +389,7 @@ namespace Debug { } text += ` (${clauses.join(", ")})`; } - return text; + return circular === "circularity" ? `Circular(${text})` : text; } function renderGraph() { diff --git a/tests/baselines/reference/controlFlowAssignmentExpression.js b/tests/baselines/reference/controlFlowAssignmentExpression.js index cf8d70b92deb0..19c9210ea7aa4 100644 --- a/tests/baselines/reference/controlFlowAssignmentExpression.js +++ b/tests/baselines/reference/controlFlowAssignmentExpression.js @@ -9,7 +9,14 @@ x; // number x = true; (x = "", obj).foo = (x = x.length); x; // number - + +// https://github.com/microsoft/TypeScript/issues/35484 +type D = { done: true, value: 1 } | { done: false, value: 2 }; +declare function fn(): D; +let o: D; +if ((o = fn()).done) { + const y: 1 = o.value; +} //// [controlFlowAssignmentExpression.js] var x; @@ -20,3 +27,7 @@ x; // number x = true; (x = "", obj).foo = (x = x.length); x; // number +var o; +if ((o = fn()).done) { + var y = o.value; +} diff --git a/tests/baselines/reference/controlFlowAssignmentExpression.symbols b/tests/baselines/reference/controlFlowAssignmentExpression.symbols index 73aa8bd7850d1..b6269056b748d 100644 --- a/tests/baselines/reference/controlFlowAssignmentExpression.symbols +++ b/tests/baselines/reference/controlFlowAssignmentExpression.symbols @@ -31,3 +31,31 @@ x = true; x; // number >x : Symbol(x, Decl(controlFlowAssignmentExpression.ts, 0, 3)) +// https://github.com/microsoft/TypeScript/issues/35484 +type D = { done: true, value: 1 } | { done: false, value: 2 }; +>D : Symbol(D, Decl(controlFlowAssignmentExpression.ts, 9, 2)) +>done : Symbol(done, Decl(controlFlowAssignmentExpression.ts, 12, 10)) +>value : Symbol(value, Decl(controlFlowAssignmentExpression.ts, 12, 22)) +>done : Symbol(done, Decl(controlFlowAssignmentExpression.ts, 12, 37)) +>value : Symbol(value, Decl(controlFlowAssignmentExpression.ts, 12, 50)) + +declare function fn(): D; +>fn : Symbol(fn, Decl(controlFlowAssignmentExpression.ts, 12, 62)) +>D : Symbol(D, Decl(controlFlowAssignmentExpression.ts, 9, 2)) + +let o: D; +>o : Symbol(o, Decl(controlFlowAssignmentExpression.ts, 14, 3)) +>D : Symbol(D, Decl(controlFlowAssignmentExpression.ts, 9, 2)) + +if ((o = fn()).done) { +>(o = fn()).done : Symbol(done, Decl(controlFlowAssignmentExpression.ts, 12, 10), Decl(controlFlowAssignmentExpression.ts, 12, 37)) +>o : Symbol(o, Decl(controlFlowAssignmentExpression.ts, 14, 3)) +>fn : Symbol(fn, Decl(controlFlowAssignmentExpression.ts, 12, 62)) +>done : Symbol(done, Decl(controlFlowAssignmentExpression.ts, 12, 10), Decl(controlFlowAssignmentExpression.ts, 12, 37)) + + const y: 1 = o.value; +>y : Symbol(y, Decl(controlFlowAssignmentExpression.ts, 16, 9)) +>o.value : Symbol(value, Decl(controlFlowAssignmentExpression.ts, 12, 22)) +>o : Symbol(o, Decl(controlFlowAssignmentExpression.ts, 14, 3)) +>value : Symbol(value, Decl(controlFlowAssignmentExpression.ts, 12, 22)) +} diff --git a/tests/baselines/reference/controlFlowAssignmentExpression.types b/tests/baselines/reference/controlFlowAssignmentExpression.types index 4cc2c52cc1b53..d56aee7c76727 100644 --- a/tests/baselines/reference/controlFlowAssignmentExpression.types +++ b/tests/baselines/reference/controlFlowAssignmentExpression.types @@ -45,3 +45,34 @@ x = true; x; // number >x : number +// https://github.com/microsoft/TypeScript/issues/35484 +type D = { done: true, value: 1 } | { done: false, value: 2 }; +>D : D +>done : true +>true : true +>value : 1 +>done : false +>false : false +>value : 2 + +declare function fn(): D; +>fn : () => D + +let o: D; +>o : D + +if ((o = fn()).done) { +>(o = fn()).done : boolean +>(o = fn()) : D +>o = fn() : D +>o : D +>fn() : D +>fn : () => D +>done : boolean + + const y: 1 = o.value; +>y : 1 +>o.value : 1 +>o : { done: true; value: 1; } +>value : 1 +} diff --git a/tests/cases/conformance/controlFlow/controlFlowAssignmentExpression.ts b/tests/cases/conformance/controlFlow/controlFlowAssignmentExpression.ts index 83bf75ab94d0c..44c583a8eea8d 100644 --- a/tests/cases/conformance/controlFlow/controlFlowAssignmentExpression.ts +++ b/tests/cases/conformance/controlFlow/controlFlowAssignmentExpression.ts @@ -8,3 +8,11 @@ x; // number x = true; (x = "", obj).foo = (x = x.length); x; // number + +// https://github.com/microsoft/TypeScript/issues/35484 +type D = { done: true, value: 1 } | { done: false, value: 2 }; +declare function fn(): D; +let o: D; +if ((o = fn()).done) { + const y: 1 = o.value; +} \ No newline at end of file