Reflect effects of assertion calls in control flow analysis

This commit is contained in:
Anders Hejlsberg 2019-07-31 16:30:54 -07:00
parent 4cc6618fc2
commit e89acb6358
3 changed files with 106 additions and 6 deletions

View File

@ -706,6 +706,9 @@ namespace ts {
case SyntaxKind.CaseClause:
bindCaseClause(<CaseClause>node);
break;
case SyntaxKind.ExpressionStatement:
bindExpressionStatement(<ExpressionStatement>node);
break;
case SyntaxKind.LabeledStatement:
bindLabeledStatement(<LabeledStatement>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((<PropertyAccessExpression>node).expression);
}
function bindExpressionStatement(node: ExpressionStatement): void {
bind(node.expression);
if (node.expression.kind === SyntaxKind.CallExpression) {
const call = <CallExpression>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();

View File

@ -16864,6 +16864,44 @@ namespace ts {
return false;
}
function getTypeOfDottedName(node: Expression) {
if (node.kind === SyntaxKind.Identifier) {
const symbol = getResolvedSymbol(<Identifier>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((<PropertyAccessExpression>node).expression);
if (type) {
const prop = getPropertyOfType(type, (<PropertyAccessExpression>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 = (<UnionTypeNode>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 = <Block | ModuleBlock | SourceFile>findAncestor(node, isFunctionOrModuleBlock);
const sourceFile = getSourceFileOfNode(node);
@ -16962,6 +17000,13 @@ namespace ts {
}
}
}
else if (flags & FlowFlags.Call) {
type = getTypeAtFlowCall(<FlowCall>flow);
if (!type) {
flow = (<FlowCall>flow).antecedent;
continue;
}
}
else if (flags & FlowFlags.Condition) {
type = getTypeAtFlowCondition(<FlowCondition>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 ((<BinaryExpression>node).operatorToken.kind === SyntaxKind.AmpersandAmpersandToken) {
return narrowTypeByAssertion(narrowTypeByAssertion(type, (<BinaryExpression>node).left), (<BinaryExpression>node).right);
}
if ((<BinaryExpression>node).operatorToken.kind === SyntaxKind.BarBarToken) {
return getUnionType([narrowTypeByAssertion(type, (<BinaryExpression>node).left), narrowTypeByAssertion(type, (<BinaryExpression>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;

View File

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