Flow analysis of &&, ||, and destructuring assignments

This commit is contained in:
Anders Hejlsberg 2016-04-08 09:13:47 -07:00
parent 354fd10a2e
commit 5179dd6ada
4 changed files with 305 additions and 190 deletions

View file

@ -105,8 +105,10 @@ namespace ts {
// state used by reachability checks
let hasExplicitReturn: boolean;
let currentFlow: FlowNode;
let breakTarget: FlowLabel;
let continueTarget: FlowLabel;
let currentBreakTarget: FlowLabel;
let currentContinueTarget: FlowLabel;
let currentTrueTarget: FlowLabel;
let currentFalseTarget: FlowLabel;
let preSwitchCaseFlow: FlowNode;
let activeLabels: ActiveLabel[];
@ -151,8 +153,10 @@ namespace ts {
seenThisKeyword = false;
hasExplicitReturn = false;
currentFlow = undefined;
breakTarget = undefined;
continueTarget = undefined;
currentBreakTarget = undefined;
currentContinueTarget = undefined;
currentTrueTarget = undefined;
currentFalseTarget = undefined;
activeLabels = undefined;
hasClassExtends = false;
hasAsyncFunctions = false;
@ -437,6 +441,8 @@ namespace ts {
let savedCurrentFlow: FlowNode;
let savedBreakTarget: FlowLabel;
let savedContinueTarget: FlowLabel;
let savedTrueTarget: FlowLabel;
let savedFalseTarget: FlowLabel;
let savedActiveLabels: ActiveLabel[];
const kind = node.kind;
@ -456,14 +462,14 @@ namespace ts {
if (saveState) {
savedHasExplicitReturn = hasExplicitReturn;
savedCurrentFlow = currentFlow;
savedBreakTarget = breakTarget;
savedContinueTarget = continueTarget;
savedBreakTarget = currentBreakTarget;
savedContinueTarget = currentContinueTarget;
savedActiveLabels = activeLabels;
hasExplicitReturn = false;
currentFlow = { kind: FlowKind.Start };
breakTarget = undefined;
continueTarget = undefined;
currentBreakTarget = undefined;
currentContinueTarget = undefined;
activeLabels = undefined;
}
@ -502,11 +508,11 @@ namespace ts {
node.flags = flags;
if (saveState) {
activeLabels = savedActiveLabels;
continueTarget = savedContinueTarget;
breakTarget = savedBreakTarget;
currentFlow = savedCurrentFlow;
hasExplicitReturn = savedHasExplicitReturn;
currentFlow = savedCurrentFlow;
currentBreakTarget = savedBreakTarget;
currentContinueTarget = savedContinueTarget;
activeLabels = savedActiveLabels;
}
container = saveContainer;
@ -561,6 +567,9 @@ namespace ts {
case SyntaxKind.LabeledStatement:
bindLabeledStatement(<LabeledStatement>node);
break;
case SyntaxKind.PrefixUnaryExpression:
bindPrefixUnaryExpressionFlow(<PrefixUnaryExpression>node);
break;
case SyntaxKind.BinaryExpression:
bindBinaryExpressionFlow(<BinaryExpression>node);
break;
@ -613,9 +622,6 @@ namespace ts {
return true;
}
return false;
case SyntaxKind.AmpersandAmpersandToken:
case SyntaxKind.BarBarToken:
return isNarrowingExpression(expr.left) || isNarrowingExpression(expr.right);
case SyntaxKind.InstanceOfKeyword:
return isNarrowingExpression(expr.left);
}
@ -656,7 +662,7 @@ namespace ts {
};
}
function createFlowAssignment(antecedent: FlowNode, node: BinaryExpression | VariableDeclaration | ForInStatement | ForOfStatement): FlowNode {
function createFlowAssignment(antecedent: FlowNode, node: Expression | VariableDeclaration | BindingElement): FlowNode {
return <FlowAssignment>{
kind: FlowKind.Assignment,
antecedent,
@ -678,21 +684,78 @@ namespace ts {
return flow;
}
function isStatementCondition(node: Node) {
const parent = node.parent;
switch (parent.kind) {
case SyntaxKind.IfStatement:
case SyntaxKind.WhileStatement:
case SyntaxKind.DoStatement:
return (<IfStatement | WhileStatement | DoStatement>parent).expression === node;
case SyntaxKind.ForStatement:
case SyntaxKind.ConditionalExpression:
return (<ForStatement | ConditionalExpression>parent).condition === node;
}
return false;
}
function isLogicalExpression(node: Node) {
while (true) {
if (node.kind === SyntaxKind.ParenthesizedExpression) {
node = (<ParenthesizedExpression>node).expression;
}
else if (node.kind === SyntaxKind.PrefixUnaryExpression && (<PrefixUnaryExpression>node).operator === SyntaxKind.ExclamationToken) {
node = (<PrefixUnaryExpression>node).operand;
}
else {
return node.kind === SyntaxKind.BinaryExpression && (
(<BinaryExpression>node).operatorToken.kind === SyntaxKind.AmpersandAmpersandToken ||
(<BinaryExpression>node).operatorToken.kind === SyntaxKind.BarBarToken);
}
}
}
function isTopLevelLogicalExpression(node: Node): boolean {
while (node.parent.kind === SyntaxKind.ParenthesizedExpression ||
node.parent.kind === SyntaxKind.PrefixUnaryExpression &&
(<PrefixUnaryExpression>node.parent).operator === SyntaxKind.ExclamationToken) {
node = node.parent;
}
return !isStatementCondition(node) && !isLogicalExpression(node.parent);
}
function bindCondition(node: Expression, trueTarget: FlowLabel, falseTarget: FlowLabel) {
const saveTrueTarget = currentTrueTarget;
const saveFalseTarget = currentFalseTarget;
currentTrueTarget = trueTarget;
currentFalseTarget = falseTarget;
bind(node);
currentTrueTarget = saveTrueTarget;
currentFalseTarget = saveFalseTarget;
if (!node || !isLogicalExpression(node)) {
addAntecedent(trueTarget, createFlowCondition(currentFlow, node, /*assumeTrue*/ true));
addAntecedent(falseTarget, createFlowCondition(currentFlow, node, /*assumeTrue*/ false));
}
}
function bindIterativeStatement(node: Statement, breakTarget: FlowLabel, continueTarget: FlowLabel): void {
const saveBreakTarget = currentBreakTarget;
const saveContinueTarget = currentContinueTarget;
currentBreakTarget = breakTarget;
currentContinueTarget = continueTarget;
bind(node);
currentBreakTarget = saveBreakTarget;
currentContinueTarget = saveContinueTarget;
}
function bindWhileStatement(node: WhileStatement): void {
const preWhileLabel = createFlowLabel();
const preBodyLabel = createFlowLabel();
const postWhileLabel = createFlowLabel();
addAntecedent(preWhileLabel, currentFlow);
currentFlow = preWhileLabel;
bind(node.expression);
addAntecedent(postWhileLabel, createFlowCondition(currentFlow, node.expression, /*assumeTrue*/ false));
currentFlow = createFlowCondition(currentFlow, node.expression, /*assumeTrue*/ true);
const saveBreakTarget = breakTarget;
const saveContinueTarget = continueTarget;
breakTarget = postWhileLabel;
continueTarget = preWhileLabel;
bind(node.statement);
breakTarget = saveBreakTarget;
continueTarget = saveContinueTarget;
bindCondition(node.expression, preBodyLabel, postWhileLabel);
currentFlow = finishFlow(preBodyLabel);
bindIterativeStatement(node.statement, postWhileLabel, preWhileLabel);
addAntecedent(preWhileLabel, currentFlow);
currentFlow = finishFlow(postWhileLabel);
}
@ -703,38 +766,24 @@ namespace ts {
const postDoLabel = createFlowLabel();
addAntecedent(preDoLabel, currentFlow);
currentFlow = preDoLabel;
const saveBreakTarget = breakTarget;
const saveContinueTarget = continueTarget;
breakTarget = postDoLabel;
continueTarget = preConditionLabel;
bind(node.statement);
breakTarget = saveBreakTarget;
continueTarget = saveContinueTarget;
bindIterativeStatement(node.statement, postDoLabel, preConditionLabel);
addAntecedent(preConditionLabel, currentFlow);
currentFlow = finishFlow(preConditionLabel);
bind(node.expression);
addAntecedent(preDoLabel, createFlowCondition(currentFlow, node.expression, /*assumeTrue*/ true));
addAntecedent(postDoLabel, createFlowCondition(currentFlow, node.expression, /*assumeTrue*/ false));
bindCondition(node.expression, preDoLabel, postDoLabel);
currentFlow = finishFlow(postDoLabel);
}
function bindForStatement(node: ForStatement): void {
const preLoopLabel = createFlowLabel();
const preBodyLabel = createFlowLabel();
const postLoopLabel = createFlowLabel();
bind(node.initializer);
addAntecedent(preLoopLabel, currentFlow);
currentFlow = preLoopLabel;
bind(node.condition);
addAntecedent(postLoopLabel, createFlowCondition(currentFlow, node.condition, /*assumeTrue*/ false));
currentFlow = createFlowCondition(currentFlow, node.condition, /*assumeTrue*/ true);
const saveBreakTarget = breakTarget;
const saveContinueTarget = continueTarget;
breakTarget = postLoopLabel;
continueTarget = preLoopLabel;
bind(node.statement);
bindCondition(node.condition, preBodyLabel, postLoopLabel);
currentFlow = finishFlow(preBodyLabel);
bindIterativeStatement(node.statement, postLoopLabel, preLoopLabel);
bind(node.incrementor);
breakTarget = saveBreakTarget;
continueTarget = saveContinueTarget;
addAntecedent(preLoopLabel, currentFlow);
currentFlow = finishFlow(postLoopLabel);
}
@ -742,31 +791,28 @@ namespace ts {
function bindForInOrForOfStatement(node: ForInStatement | ForOfStatement): void {
const preLoopLabel = createFlowLabel();
const postLoopLabel = createFlowLabel();
bind(node.initializer);
addAntecedent(preLoopLabel, currentFlow);
currentFlow = preLoopLabel;
bind(node.expression);
addAntecedent(postLoopLabel, currentFlow);
const saveBreakTarget = breakTarget;
const saveContinueTarget = continueTarget;
breakTarget = postLoopLabel;
continueTarget = preLoopLabel;
currentFlow = createFlowAssignment(currentFlow, node);
bind(node.statement);
breakTarget = saveBreakTarget;
continueTarget = saveContinueTarget;
bind(node.initializer);
if (node.initializer.kind !== SyntaxKind.VariableDeclarationList) {
bindAssignmentTargetFlow(<Expression>node.initializer);
}
bindIterativeStatement(node.statement, postLoopLabel, preLoopLabel);
addAntecedent(preLoopLabel, currentFlow);
currentFlow = finishFlow(postLoopLabel);
}
function bindIfStatement(node: IfStatement): void {
const thenLabel = createFlowLabel();
const elseLabel = createFlowLabel();
const postIfLabel = createFlowLabel();
bind(node.expression);
const postConditionFlow = currentFlow;
currentFlow = createFlowCondition(currentFlow, node.expression, /*assumeTrue*/ true);
bindCondition(node.expression, thenLabel, elseLabel);
currentFlow = finishFlow(thenLabel);
bind(node.thenStatement);
addAntecedent(postIfLabel, currentFlow);
currentFlow = createFlowCondition(postConditionFlow, node.expression, /*assumeTrue*/ false);
currentFlow = finishFlow(elseLabel);
bind(node.elseStatement);
addAntecedent(postIfLabel, currentFlow);
currentFlow = finishFlow(postIfLabel);
@ -809,7 +855,7 @@ namespace ts {
}
}
else {
bindbreakOrContinueFlow(node, breakTarget, continueTarget);
bindbreakOrContinueFlow(node, currentBreakTarget, currentContinueTarget);
}
}
@ -834,9 +880,9 @@ namespace ts {
function bindSwitchStatement(node: SwitchStatement): void {
const postSwitchLabel = createFlowLabel();
bind(node.expression);
const saveBreakTarget = breakTarget;
const saveBreakTarget = currentBreakTarget;
const savePreSwitchCaseFlow = preSwitchCaseFlow;
breakTarget = postSwitchLabel;
currentBreakTarget = postSwitchLabel;
preSwitchCaseFlow = currentFlow;
bind(node.caseBlock);
addAntecedent(postSwitchLabel, currentFlow);
@ -844,7 +890,7 @@ namespace ts {
if (!hasDefault) {
addAntecedent(postSwitchLabel, preSwitchCaseFlow);
}
breakTarget = saveBreakTarget;
currentBreakTarget = saveBreakTarget;
preSwitchCaseFlow = savePreSwitchCaseFlow;
currentFlow = finishFlow(postSwitchLabel);
}
@ -904,43 +950,118 @@ namespace ts {
currentFlow = finishFlow(postStatementLabel);
}
function bindBinaryExpressionFlow(node: BinaryExpression) {
const operator = node.operatorToken.kind;
if (operator === SyntaxKind.AmpersandAmpersandToken || operator === SyntaxKind.BarBarToken) {
const postExpressionLabel = createFlowLabel();
bind(node.left);
bind(node.operatorToken);
addAntecedent(postExpressionLabel, currentFlow);
currentFlow = createFlowCondition(currentFlow, node.left, /*assumeTrue*/ operator === SyntaxKind.AmpersandAmpersandToken);
bind(node.right);
addAntecedent(postExpressionLabel, currentFlow);
currentFlow = finishFlow(postExpressionLabel);
function bindDestructuringTargetFlow(node: Expression) {
if (node.kind === SyntaxKind.BinaryExpression && (<BinaryExpression>node).operatorToken.kind === SyntaxKind.EqualsToken) {
bindAssignmentTargetFlow((<BinaryExpression>node).left);
}
else {
bindAssignmentTargetFlow(node);
}
}
function bindAssignmentTargetFlow(node: Expression) {
if (isNarrowableReference(node)) {
currentFlow = createFlowAssignment(currentFlow, node);
}
else if (node.kind === SyntaxKind.ArrayLiteralExpression) {
for (const e of (<ArrayLiteralExpression>node).elements) {
if (e.kind === SyntaxKind.SpreadElementExpression) {
bindAssignmentTargetFlow((<SpreadElementExpression>e).expression);
}
else {
bindDestructuringTargetFlow(e);
}
}
}
else if (node.kind === SyntaxKind.ObjectLiteralExpression) {
for (const p of (<ObjectLiteralExpression>node).properties) {
if (p.kind === SyntaxKind.PropertyAssignment) {
bindDestructuringTargetFlow((<PropertyAssignment>p).initializer);
}
else if (p.kind === SyntaxKind.ShorthandPropertyAssignment) {
bindAssignmentTargetFlow((<ShorthandPropertyAssignment>p).name);
}
}
}
}
function bindLogicalExpression(node: BinaryExpression, trueTarget: FlowLabel, falseTarget: FlowLabel) {
const preRightLabel = createFlowLabel();
if (node.operatorToken.kind === SyntaxKind.AmpersandAmpersandToken) {
bindCondition(node.left, preRightLabel, falseTarget);
}
else {
bindCondition(node.left, trueTarget, preRightLabel);
}
currentFlow = finishFlow(preRightLabel);
bind(node.operatorToken);
bindCondition(node.right, trueTarget, falseTarget);
}
function bindPrefixUnaryExpressionFlow(node: PrefixUnaryExpression) {
if (node.operator === SyntaxKind.ExclamationToken) {
const saveTrueTarget = currentTrueTarget;
currentTrueTarget = currentFalseTarget;
currentFalseTarget = saveTrueTarget;
forEachChild(node, bind);
currentFalseTarget = currentTrueTarget;
currentTrueTarget = saveTrueTarget;
}
else {
forEachChild(node, bind);
if (operator === SyntaxKind.EqualsToken) {
currentFlow = createFlowAssignment(currentFlow, node);
}
}
function bindBinaryExpressionFlow(node: BinaryExpression) {
const operator = node.operatorToken.kind;
if (operator === SyntaxKind.AmpersandAmpersandToken || operator === SyntaxKind.BarBarToken) {
if (isTopLevelLogicalExpression(node)) {
const postExpressionLabel = createFlowLabel();
bindLogicalExpression(node, postExpressionLabel, postExpressionLabel);
currentFlow = finishFlow(postExpressionLabel);
}
else {
bindLogicalExpression(node, currentTrueTarget, currentFalseTarget);
}
}
else {
forEachChild(node, bind);
if (operator === SyntaxKind.EqualsToken && !isAssignmentTarget(node)) {
bindAssignmentTargetFlow(node.left);
}
}
}
function bindConditionalExpressionFlow(node: ConditionalExpression) {
const trueLabel = createFlowLabel();
const falseLabel = createFlowLabel();
const postExpressionLabel = createFlowLabel();
bind(node.condition);
const postConditionFlow = currentFlow;
currentFlow = createFlowCondition(currentFlow, node.condition, /*assumeTrue*/ true);
bindCondition(node.condition, trueLabel, falseLabel);
currentFlow = finishFlow(trueLabel);
bind(node.whenTrue);
addAntecedent(postExpressionLabel, currentFlow);
currentFlow = createFlowCondition(postConditionFlow, node.condition, /*assumeTrue*/ false);
currentFlow = finishFlow(falseLabel);
bind(node.whenFalse);
addAntecedent(postExpressionLabel, currentFlow);
currentFlow = finishFlow(postExpressionLabel);
}
function bindInitializedVariableFlow(node: VariableDeclaration | BindingElement) {
const name = node.name;
if (isBindingPattern(name)) {
for (const child of name.elements) {
bindInitializedVariableFlow(child);
}
}
else {
currentFlow = createFlowAssignment(currentFlow, node);
}
}
function bindVariableDeclarationFlow(node: VariableDeclaration) {
forEachChild(node, bind);
if (node.initializer) {
currentFlow = createFlowAssignment(currentFlow, node);
if (node.initializer || node.parent.parent.kind === SyntaxKind.ForInStatement || node.parent.parent.kind === SyntaxKind.ForOfStatement) {
bindInitializedVariableFlow(node);
}
}

View file

@ -7335,6 +7335,59 @@ namespace ts {
return firstType ? types ? getUnionType(types, /*noSubtypeReduction*/ true) : firstType : emptyUnionType;
}
function getAssignedTypeOfBinaryExpression(node: BinaryExpression): Type {
const type = checkExpressionCached(node.right);
const parent = node.parent;
if (parent.kind === SyntaxKind.ArrayLiteralExpression || parent.kind === SyntaxKind.PropertyAssignment) {
const assignedType = getAssignedType(node);
return getUnionType([getTypeWithFacts(assignedType, TypeFacts.NEUndefined), type]);
}
return type;
}
function getAssignedTypeOfArrayLiteralElement(node: ArrayLiteralExpression, element: Expression): Type {
const arrayLikeType = getAssignedType(node);
const elementType = checkIteratedTypeOrElementType(arrayLikeType, node, /*allowStringInput*/ false);
const propName = "" + indexOf(node.elements, element);
return isTupleLikeType(arrayLikeType) && getTypeOfPropertyOfType(arrayLikeType, propName) || elementType;
}
function getAssignedTypeOfSpreadElement(node: SpreadElementExpression): Type {
const arrayLikeType = getAssignedType(<ArrayLiteralExpression>node.parent);
const elementType = checkIteratedTypeOrElementType(arrayLikeType, node, /*allowStringInput*/ false);
return createArrayType(elementType);
}
function getAssignedTypeOfPropertyAssignment(node: PropertyAssignment): Type {
const objectType = getAssignedType(<ObjectLiteralExpression>node.parent);
const text = getTextOfPropertyName(node.name);
return getTypeOfPropertyOfType(objectType, text) ||
isNumericLiteralName(text) && getIndexTypeOfType(objectType, IndexKind.Number) ||
getIndexTypeOfType(objectType, IndexKind.String) ||
unknownType;
}
function getAssignedType(node: Expression): Type {
const parent = node.parent;
switch (parent.kind) {
case SyntaxKind.ForInStatement:
return stringType;
case SyntaxKind.ForOfStatement:
return checkRightHandSideOfForOf((<ForOfStatement>parent).expression);
case SyntaxKind.BinaryExpression:
return getAssignedTypeOfBinaryExpression(<BinaryExpression>parent);
case SyntaxKind.ArrayLiteralExpression:
return getAssignedTypeOfArrayLiteralElement(<ArrayLiteralExpression>parent, node);
case SyntaxKind.SpreadElementExpression:
return getAssignedTypeOfSpreadElement(<SpreadElementExpression>parent);
case SyntaxKind.PropertyAssignment:
return getAssignedTypeOfPropertyAssignment(<PropertyAssignment>parent);
case SyntaxKind.ShorthandPropertyAssignment:
break; // !!! TODO
}
return unknownType;
}
function getNarrowedTypeOfReference(type: Type, reference: Node) {
if (!(type.flags & TypeFlags.Narrowable) || !isNarrowableReference(reference)) {
return type;
@ -7384,58 +7437,34 @@ namespace ts {
}
}
function getTypeAtVariableDeclaration(node: VariableDeclaration) {
if (reference.kind === SyntaxKind.Identifier && !isBindingPattern(node.name) && getResolvedSymbol(<Identifier>reference) === getSymbolOfNode(node)) {
return getAssignmentReducedType(declaredType, checkExpressionCached((<VariableDeclaration>node).initializer));
}
return undefined;
}
function getTypeAtForInOrForOfStatement(node: ForInStatement | ForOfStatement) {
if (node.initializer.kind === SyntaxKind.VariableDeclarationList) {
if (reference.kind === SyntaxKind.Identifier) {
const variable = (<VariableDeclarationList>node.initializer).declarations[0];
if (variable && !isBindingPattern(variable.name) && getResolvedSymbol(<Identifier>reference) === getSymbolOfNode(variable)) {
return declaredType;
}
}
}
else {
if (isMatchingReference(reference, <Expression>node.initializer)) {
const type = node.kind === SyntaxKind.ForOfStatement ? checkRightHandSideOfForOf(node.expression) : stringType;
return getAssignmentReducedType(declaredType, type);
}
if (reference.kind === SyntaxKind.PropertyAccessExpression &&
containsMatchingReference((<PropertyAccessExpression>reference).expression, <Expression>node.initializer)) {
return declaredType;
function getTypeAtVariableDeclaration(node: VariableDeclaration | BindingElement) {
if (reference.kind === SyntaxKind.Identifier && getResolvedSymbol(<Identifier>reference) === getSymbolOfNode(node)) {
if (node.initializer) {
return getAssignmentReducedType(declaredType, checkExpressionCached(node.initializer));
}
return declaredType; // !!! TODO
}
return undefined;
}
function getTypeAtFlowAssignment(flow: FlowAssignment) {
const node = flow.node;
switch (node.kind) {
case SyntaxKind.BinaryExpression:
// If reference matches left hand side and type on right is properly assignable,
// return type on right. Otherwise default to the declared type.
if (isMatchingReference(reference, (<BinaryExpression>node).left)) {
return getAssignmentReducedType(declaredType, checkExpressionCached((<BinaryExpression>node).right));
}
// We didn't have a direct match. However, if the reference is a dotted name, this
// may be an assignment to a left hand part of the reference. For example, for a
// reference 'x.y.z', we may be at an assignment to 'x.y' or 'x'. In that case,
// return the declared type.
if (reference.kind === SyntaxKind.PropertyAccessExpression &&
containsMatchingReference((<PropertyAccessExpression>reference).expression, (<BinaryExpression>node).left)) {
return declaredType;
}
break;
case SyntaxKind.VariableDeclaration:
return getTypeAtVariableDeclaration(<VariableDeclaration>node);
case SyntaxKind.ForInStatement:
case SyntaxKind.ForOfStatement:
return getTypeAtForInOrForOfStatement(<ForInStatement | ForOfStatement>node);
if (node.kind === SyntaxKind.VariableDeclaration || node.kind === SyntaxKind.BindingElement) {
return getTypeAtVariableDeclaration(<VariableDeclaration | BindingElement>node);
}
// If the node is not a variable declaration or binding element, it is an identifier
// or a dotted name that is the target of an assignment. If we have a match, reduce
// the declared type by the assigned type.
if (isMatchingReference(reference, node)) {
return getAssignmentReducedType(declaredType, getAssignedType(<Expression>node));
}
// We didn't have a direct match. However, if the reference is a dotted name, this
// may be an assignment to a left hand part of the reference. For example, for a
// reference 'x.y.z', we may be at an assignment to 'x.y' or 'x'. In that case,
// return the declared type.
if (reference.kind === SyntaxKind.PropertyAccessExpression &&
containsMatchingReference((<PropertyAccessExpression>reference).expression, node)) {
return declaredType;
}
// Assignment doesn't affect reference
return undefined;
@ -7503,10 +7532,6 @@ namespace ts {
return narrowTypeByTypeof(type, expr, assumeTrue);
}
break;
case SyntaxKind.AmpersandAmpersandToken:
return narrowTypeByAnd(type, expr, assumeTrue);
case SyntaxKind.BarBarToken:
return narrowTypeByOr(type, expr, assumeTrue);
case SyntaxKind.InstanceOfKeyword:
return narrowTypeByInstanceof(type, expr, assumeTrue);
}
@ -7558,36 +7583,6 @@ namespace ts {
return getTypeWithFacts(type, facts);
}
function narrowTypeByAnd(type: Type, expr: BinaryExpression, assumeTrue: boolean): Type {
if (assumeTrue) {
// The assumed result is true, therefore we narrow assuming each operand to be true.
return narrowType(narrowType(type, expr.left, /*assumeTrue*/ true), expr.right, /*assumeTrue*/ true);
}
else {
// The assumed result is false. This means either the first operand was false, or the first operand was true
// and the second operand was false. We narrow with those assumptions and union the two resulting types.
return getUnionType([
narrowType(type, expr.left, /*assumeTrue*/ false),
narrowType(type, expr.right, /*assumeTrue*/ false)
]);
}
}
function narrowTypeByOr(type: Type, expr: BinaryExpression, assumeTrue: boolean): Type {
if (assumeTrue) {
// The assumed result is true. This means either the first operand was true, or the first operand was false
// and the second operand was true. We narrow with those assumptions and union the two resulting types.
return getUnionType([
narrowType(type, expr.left, /*assumeTrue*/ true),
narrowType(type, expr.right, /*assumeTrue*/ true)
]);
}
else {
// The assumed result is false, therefore we narrow assuming each operand to be false.
return narrowType(narrowType(type, expr.left, /*assumeTrue*/ false), expr.right, /*assumeTrue*/ false);
}
}
function narrowTypeByInstanceof(type: Type, expr: BinaryExpression, assumeTrue: boolean): Type {
// Check that type is not any, assumed result is true, and we have variable symbol on the left
if (isTypeAny(type) || !isMatchingReference(expr.left, reference)) {
@ -8718,32 +8713,6 @@ namespace ts {
return mapper && mapper.context;
}
// A node is an assignment target if it is on the left hand side of an '=' token, if it is parented by a property
// assignment in an object literal that is an assignment target, or if it is parented by an array literal that is
// an assignment target. Examples include 'a = xxx', '{ p: a } = xxx', '[{ p: a}] = xxx'.
function isAssignmentTarget(node: Node): boolean {
while (node.parent.kind === SyntaxKind.ParenthesizedExpression) {
node = node.parent;
}
while (true) {
if (node.parent.kind === SyntaxKind.PropertyAssignment) {
node = node.parent.parent;
}
else if (node.parent.kind === SyntaxKind.ArrayLiteralExpression) {
node = node.parent;
}
else {
break;
}
}
const parent = node.parent;
return parent.kind === SyntaxKind.BinaryExpression &&
(<BinaryExpression>parent).operatorToken.kind === SyntaxKind.EqualsToken &&
(<BinaryExpression>parent).left === node ||
(parent.kind === SyntaxKind.ForInStatement || parent.kind === SyntaxKind.ForOfStatement) &&
(<ForInStatement | ForOfStatement>parent).initializer === node;
}
function checkSpreadElementExpression(node: SpreadElementExpression, contextualMapper?: TypeMapper): Type {
// It is usually not safe to call checkExpressionCached if we can be contextually typing.
// You can tell that we are contextually typing because of the contextualMapper parameter.

View file

@ -1537,10 +1537,10 @@ namespace ts {
antecedents: FlowNode[];
}
// FlowAssignment represents a node that possibly assigns a value to one or more
// references.
// FlowAssignment represents a node that assigns a value to a narrowable reference,
// i.e. an identifier or a dotted name that starts with an identifier or 'this'.
export interface FlowAssignment extends FlowNode {
node: BinaryExpression | VariableDeclaration | ForInStatement | ForOfStatement;
node: Expression | VariableDeclaration | BindingElement;
antecedent: FlowNode;
}

View file

@ -1316,6 +1316,31 @@ namespace ts {
return !!node && (node.kind === SyntaxKind.ArrayBindingPattern || node.kind === SyntaxKind.ObjectBindingPattern);
}
// A node is an assignment target if it is on the left hand side of an '=' token, if it is parented by a property
// assignment in an object literal that is an assignment target, or if it is parented by an array literal that is
// an assignment target. Examples include 'a = xxx', '{ p: a } = xxx', '[{ p: a}] = xxx'.
export function isAssignmentTarget(node: Node): boolean {
while (node.parent.kind === SyntaxKind.ParenthesizedExpression) {
node = node.parent;
}
while (true) {
const parent = node.parent;
if (parent.kind === SyntaxKind.ArrayLiteralExpression || parent.kind === SyntaxKind.SpreadElementExpression) {
node = parent;
continue;
}
if (parent.kind === SyntaxKind.PropertyAssignment) {
node = parent.parent;
continue;
}
return parent.kind === SyntaxKind.BinaryExpression &&
(<BinaryExpression>parent).operatorToken.kind === SyntaxKind.EqualsToken &&
(<BinaryExpression>parent).left === node ||
(parent.kind === SyntaxKind.ForInStatement || parent.kind === SyntaxKind.ForOfStatement) &&
(<ForInStatement | ForOfStatement>parent).initializer === node;
}
}
export function isNodeDescendentOf(node: Node, ancestor: Node): boolean {
while (node) {
if (node === ancestor) return true;