Allow assignments to a narrowable reference to be considered narrowable

This commit is contained in:
Ron Buckton 2020-07-29 16:37:11 -07:00
parent 03b658035d
commit b9db6413bf
7 changed files with 124 additions and 24 deletions

View file

@ -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 {

View file

@ -20131,6 +20131,11 @@ namespace ts {
case SyntaxKind.ParenthesizedExpression:
case SyntaxKind.NonNullExpression:
return isMatchingReference(source, (target as NonNullExpression | ParenthesizedExpression).expression);
case SyntaxKind.BinaryExpression:
if (isAssignmentExpression(target)) {
return isMatchingReference(source, target.left);
}
break;
}
switch (source.kind) {
case SyntaxKind.Identifier:

View file

@ -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<number, FlowGraphNode> = 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<FlowNode>): 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<FlowNode>) {
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() {

View file

@ -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;
}

View file

@ -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))
}

View file

@ -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
}

View file

@ -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;
}