Flow analysis of &&, ||, and destructuring assignments
This commit is contained in:
parent
354fd10a2e
commit
5179dd6ada
4 changed files with 305 additions and 190 deletions
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in a new issue