diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index cfeb76827bf..39dca4070a7 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -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(node); break; + case SyntaxKind.PrefixUnaryExpression: + bindPrefixUnaryExpressionFlow(node); + break; case SyntaxKind.BinaryExpression: bindBinaryExpressionFlow(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 { 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 (parent).expression === node; + case SyntaxKind.ForStatement: + case SyntaxKind.ConditionalExpression: + return (parent).condition === node; + } + return false; + } + + function isLogicalExpression(node: Node) { + while (true) { + if (node.kind === SyntaxKind.ParenthesizedExpression) { + node = (node).expression; + } + else if (node.kind === SyntaxKind.PrefixUnaryExpression && (node).operator === SyntaxKind.ExclamationToken) { + node = (node).operand; + } + else { + return node.kind === SyntaxKind.BinaryExpression && ( + (node).operatorToken.kind === SyntaxKind.AmpersandAmpersandToken || + (node).operatorToken.kind === SyntaxKind.BarBarToken); + } + } + } + + function isTopLevelLogicalExpression(node: Node): boolean { + while (node.parent.kind === SyntaxKind.ParenthesizedExpression || + node.parent.kind === SyntaxKind.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(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 && (node).operatorToken.kind === SyntaxKind.EqualsToken) { + bindAssignmentTargetFlow((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 (node).elements) { + if (e.kind === SyntaxKind.SpreadElementExpression) { + bindAssignmentTargetFlow((e).expression); + } + else { + bindDestructuringTargetFlow(e); + } + } + } + else if (node.kind === SyntaxKind.ObjectLiteralExpression) { + for (const p of (node).properties) { + if (p.kind === SyntaxKind.PropertyAssignment) { + bindDestructuringTargetFlow((p).initializer); + } + else if (p.kind === SyntaxKind.ShorthandPropertyAssignment) { + bindAssignmentTargetFlow((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); } } diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 6fc60c5c78a..2393b236648 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -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(node.parent); + const elementType = checkIteratedTypeOrElementType(arrayLikeType, node, /*allowStringInput*/ false); + return createArrayType(elementType); + } + + function getAssignedTypeOfPropertyAssignment(node: PropertyAssignment): Type { + const objectType = getAssignedType(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((parent).expression); + case SyntaxKind.BinaryExpression: + return getAssignedTypeOfBinaryExpression(parent); + case SyntaxKind.ArrayLiteralExpression: + return getAssignedTypeOfArrayLiteralElement(parent, node); + case SyntaxKind.SpreadElementExpression: + return getAssignedTypeOfSpreadElement(parent); + case SyntaxKind.PropertyAssignment: + return getAssignedTypeOfPropertyAssignment(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(reference) === getSymbolOfNode(node)) { - return getAssignmentReducedType(declaredType, checkExpressionCached((node).initializer)); - } - return undefined; - } - - function getTypeAtForInOrForOfStatement(node: ForInStatement | ForOfStatement) { - if (node.initializer.kind === SyntaxKind.VariableDeclarationList) { - if (reference.kind === SyntaxKind.Identifier) { - const variable = (node.initializer).declarations[0]; - if (variable && !isBindingPattern(variable.name) && getResolvedSymbol(reference) === getSymbolOfNode(variable)) { - return declaredType; - } - } - } - else { - if (isMatchingReference(reference, node.initializer)) { - const type = node.kind === SyntaxKind.ForOfStatement ? checkRightHandSideOfForOf(node.expression) : stringType; - return getAssignmentReducedType(declaredType, type); - } - if (reference.kind === SyntaxKind.PropertyAccessExpression && - containsMatchingReference((reference).expression, node.initializer)) { - return declaredType; + function getTypeAtVariableDeclaration(node: VariableDeclaration | BindingElement) { + if (reference.kind === SyntaxKind.Identifier && getResolvedSymbol(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, (node).left)) { - return getAssignmentReducedType(declaredType, checkExpressionCached((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((reference).expression, (node).left)) { - return declaredType; - } - break; - case SyntaxKind.VariableDeclaration: - return getTypeAtVariableDeclaration(node); - case SyntaxKind.ForInStatement: - case SyntaxKind.ForOfStatement: - return getTypeAtForInOrForOfStatement(node); + if (node.kind === SyntaxKind.VariableDeclaration || node.kind === SyntaxKind.BindingElement) { + return getTypeAtVariableDeclaration(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(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((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 && - (parent).operatorToken.kind === SyntaxKind.EqualsToken && - (parent).left === node || - (parent.kind === SyntaxKind.ForInStatement || parent.kind === SyntaxKind.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. diff --git a/src/compiler/types.ts b/src/compiler/types.ts index ed82d65d5dd..7deee80476b 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -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; } diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index da33c18dfb8..45567538dcd 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -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 && + (parent).operatorToken.kind === SyntaxKind.EqualsToken && + (parent).left === node || + (parent.kind === SyntaxKind.ForInStatement || parent.kind === SyntaxKind.ForOfStatement) && + (parent).initializer === node; + } + } + export function isNodeDescendentOf(node: Node, ancestor: Node): boolean { while (node) { if (node === ancestor) return true;