From e89acb6358845b310753e132f5ae692c72d5bb6a Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Wed, 31 Jul 2019 16:30:54 -0700 Subject: [PATCH] Reflect effects of assertion calls in control flow analysis --- src/compiler/binder.ts | 22 +++++++++++++ src/compiler/checker.ts | 71 +++++++++++++++++++++++++++++++++++++++++ src/compiler/types.ts | 19 +++++++---- 3 files changed, 106 insertions(+), 6 deletions(-) diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index d18ab45a347..16dffe46aad 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -706,6 +706,9 @@ namespace ts { case SyntaxKind.CaseClause: bindCaseClause(node); break; + case SyntaxKind.ExpressionStatement: + bindExpressionStatement(node); + break; case SyntaxKind.LabeledStatement: bindLabeledStatement(node); break; @@ -896,6 +899,11 @@ namespace ts { return flowNodeCreated({ flags: FlowFlags.Assignment, antecedent, node }); } + function createFlowCall(antecedent: FlowNode, node: CallExpression): FlowNode { + setFlowNodeReferenced(antecedent); + return flowNodeCreated({ flags: FlowFlags.Call, antecedent, node }); + } + function createFlowArrayMutation(antecedent: FlowNode, node: CallExpression | BinaryExpression): FlowNode { setFlowNodeReferenced(antecedent); const res: FlowArrayMutation = flowNodeCreated({ flags: FlowFlags.ArrayMutation, antecedent, node }); @@ -1276,6 +1284,20 @@ namespace ts { activeLabels!.pop(); } + function isDottedName(node: Expression) { + return node.kind === SyntaxKind.Identifier || node.kind === SyntaxKind.PropertyAccessExpression && isQualifiedName((node).expression); + } + + function bindExpressionStatement(node: ExpressionStatement): void { + bind(node.expression); + if (node.expression.kind === SyntaxKind.CallExpression) { + const call = node.expression; + if (isDottedName(call.expression) && call.arguments.length >= 1) { + currentFlow = createFlowCall(currentFlow, call); + } + } + } + function bindLabeledStatement(node: LabeledStatement): void { const preStatementLabel = createLoopLabel(); const postStatementLabel = createBranchLabel(); diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 3fa9c2da66e..2ed815e8978 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -16864,6 +16864,44 @@ namespace ts { return false; } + function getTypeOfDottedName(node: Expression) { + if (node.kind === SyntaxKind.Identifier) { + const symbol = getResolvedSymbol(node); + const nonAliasSymbol = symbol.flags & SymbolFlags.Alias ? resolveAlias(symbol) : symbol; + return nonAliasSymbol.flags & (SymbolFlags.Function | SymbolFlags.Class | SymbolFlags.ValueModule) ? getTypeOfSymbol(nonAliasSymbol) : undefined; + } + if (node.kind === SyntaxKind.PropertyAccessExpression) { + const type = getTypeOfDottedName((node).expression); + if (type) { + const prop = getPropertyOfType(type, (node).name.escapedText); + return prop && prop.flags & (SymbolFlags.Function | SymbolFlags.Method | SymbolFlags.Class | SymbolFlags.ValueModule) ? getTypeOfSymbol(prop) : undefined; + } + } + } + + function getIsAssertCall(node: CallExpression) { + const type = getTypeOfDottedName(node.expression); + if (type) { + const signature = getSingleCallSignature(type); + if (signature && signature.declaration) { + const typeNode = getEffectiveReturnTypeNode(signature.declaration); + if (typeNode && typeNode.kind === SyntaxKind.UnionType) { + const types = (typeNode).types; + return types.length === 2 && types[0].kind === SyntaxKind.VoidKeyword && types[1].kind === SyntaxKind.NeverKeyword; + } + } + } + return false; + } + + function isAssertCall(node: CallExpression) { + const links = getNodeLinks(node); + if (links.isAssertCall === undefined) { + links.isAssertCall = getIsAssertCall(node); + } + return links.isAssertCall; + } + function reportFlowControlError(node: Node) { const block = findAncestor(node, isFunctionOrModuleBlock); const sourceFile = getSourceFileOfNode(node); @@ -16962,6 +17000,13 @@ namespace ts { } } } + else if (flags & FlowFlags.Call) { + type = getTypeAtFlowCall(flow); + if (!type) { + flow = (flow).antecedent; + continue; + } + } else if (flags & FlowFlags.Condition) { type = getTypeAtFlowCondition(flow); } @@ -17057,6 +17102,32 @@ namespace ts { return undefined; } + function narrowTypeByAssertion(type: Type, expr: Expression): Type { + const node = skipParentheses(expr); + if (node.kind === SyntaxKind.BinaryExpression) { + if ((node).operatorToken.kind === SyntaxKind.AmpersandAmpersandToken) { + return narrowTypeByAssertion(narrowTypeByAssertion(type, (node).left), (node).right); + } + if ((node).operatorToken.kind === SyntaxKind.BarBarToken) { + return getUnionType([narrowTypeByAssertion(type, (node).left), narrowTypeByAssertion(type, (node).right)]); + } + } + return narrowType(type, node, /*assumeTrue*/ true); + } + + function getTypeAtFlowCall(flow: FlowCall): FlowType | undefined { + if (isAssertCall(flow.node)) { + const flowType = getTypeAtFlowNode(flow.antecedent); + const type = getTypeFromFlowType(flowType); + const narrowedType = narrowTypeByAssertion(type, flow.node.arguments[0]); + if (narrowedType === type) { + return flowType; + } + return createFlowType(narrowedType, isIncomplete(flowType)); + } + return undefined; + } + function getTypeAtFlowArrayMutation(flow: FlowArrayMutation): FlowType | undefined { if (declaredType === autoType || declaredType === autoArrayType) { const node = flow.node; diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 099f7705f84..aec65b03367 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2550,12 +2550,13 @@ namespace ts { FalseCondition = 1 << 6, // Condition known to be false SwitchClause = 1 << 7, // Switch statement clause ArrayMutation = 1 << 8, // Potential array mutation - Referenced = 1 << 9, // Referenced as antecedent once - Shared = 1 << 10, // Referenced as antecedent more than once - PreFinally = 1 << 11, // Injected edge that links pre-finally label and pre-try flow - AfterFinally = 1 << 12, // Injected edge that links post-finally flow with the rest of the graph + Call = 1 << 9, // Potential assertion call + Referenced = 1 << 10, // Referenced as antecedent once + Shared = 1 << 11, // Referenced as antecedent more than once + PreFinally = 1 << 12, // Injected edge that links pre-finally label and pre-try flow + AfterFinally = 1 << 13, // Injected edge that links post-finally flow with the rest of the graph /** @internal */ - Cached = 1 << 13, // Indicates that at least one cross-call cache entry exists for this node, even if not a loop participant + Cached = 1 << 14, // Indicates that at least one cross-call cache entry exists for this node, even if not a loop participant Label = BranchLabel | LoopLabel, Condition = TrueCondition | FalseCondition } @@ -2574,7 +2575,7 @@ namespace ts { } export type FlowNode = - | AfterFinallyFlow | PreFinallyFlow | FlowStart | FlowLabel | FlowAssignment | FlowCondition | FlowSwitchClause | FlowArrayMutation; + | AfterFinallyFlow | PreFinallyFlow | FlowStart | FlowLabel | FlowAssignment | FlowCall | FlowCondition | FlowSwitchClause | FlowArrayMutation; export interface FlowNodeBase { flags: FlowFlags; id?: number; // Node id used by flow type cache in checker @@ -2599,6 +2600,11 @@ namespace ts { antecedent: FlowNode; } + export interface FlowCall extends FlowNodeBase { + node: CallExpression; + antecedent: FlowNode; + } + // FlowCondition represents a condition that is known to be true or false at the // node's location in the control flow. export interface FlowCondition extends FlowNodeBase { @@ -3902,6 +3908,7 @@ namespace ts { resolvedSymbol?: Symbol; // Cached name resolution result resolvedIndexInfo?: IndexInfo; // Cached indexing info resolution result maybeTypePredicate?: boolean; // Cached check whether call expression might reference a type predicate + isAssertCall?: boolean; enumMemberValue?: string | number; // Constant value of enum member isVisible?: boolean; // Is this node visible containsArgumentsReference?: boolean; // Whether a function-like declaration contains an 'arguments' reference