Skip to content

Commit e89acb6

Browse files
committed
Reflect effects of assertion calls in control flow analysis
1 parent 4cc6618 commit e89acb6

File tree

3 files changed

+106
-6
lines changed

3 files changed

+106
-6
lines changed

src/compiler/binder.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -706,6 +706,9 @@ namespace ts {
706706
case SyntaxKind.CaseClause:
707707
bindCaseClause(<CaseClause>node);
708708
break;
709+
case SyntaxKind.ExpressionStatement:
710+
bindExpressionStatement(<ExpressionStatement>node);
711+
break;
709712
case SyntaxKind.LabeledStatement:
710713
bindLabeledStatement(<LabeledStatement>node);
711714
break;
@@ -896,6 +899,11 @@ namespace ts {
896899
return flowNodeCreated({ flags: FlowFlags.Assignment, antecedent, node });
897900
}
898901

902+
function createFlowCall(antecedent: FlowNode, node: CallExpression): FlowNode {
903+
setFlowNodeReferenced(antecedent);
904+
return flowNodeCreated({ flags: FlowFlags.Call, antecedent, node });
905+
}
906+
899907
function createFlowArrayMutation(antecedent: FlowNode, node: CallExpression | BinaryExpression): FlowNode {
900908
setFlowNodeReferenced(antecedent);
901909
const res: FlowArrayMutation = flowNodeCreated({ flags: FlowFlags.ArrayMutation, antecedent, node });
@@ -1276,6 +1284,20 @@ namespace ts {
12761284
activeLabels!.pop();
12771285
}
12781286

1287+
function isDottedName(node: Expression) {
1288+
return node.kind === SyntaxKind.Identifier || node.kind === SyntaxKind.PropertyAccessExpression && isQualifiedName((<PropertyAccessExpression>node).expression);
1289+
}
1290+
1291+
function bindExpressionStatement(node: ExpressionStatement): void {
1292+
bind(node.expression);
1293+
if (node.expression.kind === SyntaxKind.CallExpression) {
1294+
const call = <CallExpression>node.expression;
1295+
if (isDottedName(call.expression) && call.arguments.length >= 1) {
1296+
currentFlow = createFlowCall(currentFlow, call);
1297+
}
1298+
}
1299+
}
1300+
12791301
function bindLabeledStatement(node: LabeledStatement): void {
12801302
const preStatementLabel = createLoopLabel();
12811303
const postStatementLabel = createBranchLabel();

src/compiler/checker.ts

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16864,6 +16864,44 @@ namespace ts {
1686416864
return false;
1686516865
}
1686616866

16867+
function getTypeOfDottedName(node: Expression) {
16868+
if (node.kind === SyntaxKind.Identifier) {
16869+
const symbol = getResolvedSymbol(<Identifier>node);
16870+
const nonAliasSymbol = symbol.flags & SymbolFlags.Alias ? resolveAlias(symbol) : symbol;
16871+
return nonAliasSymbol.flags & (SymbolFlags.Function | SymbolFlags.Class | SymbolFlags.ValueModule) ? getTypeOfSymbol(nonAliasSymbol) : undefined;
16872+
}
16873+
if (node.kind === SyntaxKind.PropertyAccessExpression) {
16874+
const type = getTypeOfDottedName((<PropertyAccessExpression>node).expression);
16875+
if (type) {
16876+
const prop = getPropertyOfType(type, (<PropertyAccessExpression>node).name.escapedText);
16877+
return prop && prop.flags & (SymbolFlags.Function | SymbolFlags.Method | SymbolFlags.Class | SymbolFlags.ValueModule) ? getTypeOfSymbol(prop) : undefined;
16878+
}
16879+
}
16880+
}
16881+
16882+
function getIsAssertCall(node: CallExpression) {
16883+
const type = getTypeOfDottedName(node.expression);
16884+
if (type) {
16885+
const signature = getSingleCallSignature(type);
16886+
if (signature && signature.declaration) {
16887+
const typeNode = getEffectiveReturnTypeNode(signature.declaration);
16888+
if (typeNode && typeNode.kind === SyntaxKind.UnionType) {
16889+
const types = (<UnionTypeNode>typeNode).types;
16890+
return types.length === 2 && types[0].kind === SyntaxKind.VoidKeyword && types[1].kind === SyntaxKind.NeverKeyword;
16891+
}
16892+
}
16893+
}
16894+
return false;
16895+
}
16896+
16897+
function isAssertCall(node: CallExpression) {
16898+
const links = getNodeLinks(node);
16899+
if (links.isAssertCall === undefined) {
16900+
links.isAssertCall = getIsAssertCall(node);
16901+
}
16902+
return links.isAssertCall;
16903+
}
16904+
1686716905
function reportFlowControlError(node: Node) {
1686816906
const block = <Block | ModuleBlock | SourceFile>findAncestor(node, isFunctionOrModuleBlock);
1686916907
const sourceFile = getSourceFileOfNode(node);
@@ -16962,6 +17000,13 @@ namespace ts {
1696217000
}
1696317001
}
1696417002
}
17003+
else if (flags & FlowFlags.Call) {
17004+
type = getTypeAtFlowCall(<FlowCall>flow);
17005+
if (!type) {
17006+
flow = (<FlowCall>flow).antecedent;
17007+
continue;
17008+
}
17009+
}
1696517010
else if (flags & FlowFlags.Condition) {
1696617011
type = getTypeAtFlowCondition(<FlowCondition>flow);
1696717012
}
@@ -17057,6 +17102,32 @@ namespace ts {
1705717102
return undefined;
1705817103
}
1705917104

17105+
function narrowTypeByAssertion(type: Type, expr: Expression): Type {
17106+
const node = skipParentheses(expr);
17107+
if (node.kind === SyntaxKind.BinaryExpression) {
17108+
if ((<BinaryExpression>node).operatorToken.kind === SyntaxKind.AmpersandAmpersandToken) {
17109+
return narrowTypeByAssertion(narrowTypeByAssertion(type, (<BinaryExpression>node).left), (<BinaryExpression>node).right);
17110+
}
17111+
if ((<BinaryExpression>node).operatorToken.kind === SyntaxKind.BarBarToken) {
17112+
return getUnionType([narrowTypeByAssertion(type, (<BinaryExpression>node).left), narrowTypeByAssertion(type, (<BinaryExpression>node).right)]);
17113+
}
17114+
}
17115+
return narrowType(type, node, /*assumeTrue*/ true);
17116+
}
17117+
17118+
function getTypeAtFlowCall(flow: FlowCall): FlowType | undefined {
17119+
if (isAssertCall(flow.node)) {
17120+
const flowType = getTypeAtFlowNode(flow.antecedent);
17121+
const type = getTypeFromFlowType(flowType);
17122+
const narrowedType = narrowTypeByAssertion(type, flow.node.arguments[0]);
17123+
if (narrowedType === type) {
17124+
return flowType;
17125+
}
17126+
return createFlowType(narrowedType, isIncomplete(flowType));
17127+
}
17128+
return undefined;
17129+
}
17130+
1706017131
function getTypeAtFlowArrayMutation(flow: FlowArrayMutation): FlowType | undefined {
1706117132
if (declaredType === autoType || declaredType === autoArrayType) {
1706217133
const node = flow.node;

src/compiler/types.ts

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2550,12 +2550,13 @@ namespace ts {
25502550
FalseCondition = 1 << 6, // Condition known to be false
25512551
SwitchClause = 1 << 7, // Switch statement clause
25522552
ArrayMutation = 1 << 8, // Potential array mutation
2553-
Referenced = 1 << 9, // Referenced as antecedent once
2554-
Shared = 1 << 10, // Referenced as antecedent more than once
2555-
PreFinally = 1 << 11, // Injected edge that links pre-finally label and pre-try flow
2556-
AfterFinally = 1 << 12, // Injected edge that links post-finally flow with the rest of the graph
2553+
Call = 1 << 9, // Potential assertion call
2554+
Referenced = 1 << 10, // Referenced as antecedent once
2555+
Shared = 1 << 11, // Referenced as antecedent more than once
2556+
PreFinally = 1 << 12, // Injected edge that links pre-finally label and pre-try flow
2557+
AfterFinally = 1 << 13, // Injected edge that links post-finally flow with the rest of the graph
25572558
/** @internal */
2558-
Cached = 1 << 13, // Indicates that at least one cross-call cache entry exists for this node, even if not a loop participant
2559+
Cached = 1 << 14, // Indicates that at least one cross-call cache entry exists for this node, even if not a loop participant
25592560
Label = BranchLabel | LoopLabel,
25602561
Condition = TrueCondition | FalseCondition
25612562
}
@@ -2574,7 +2575,7 @@ namespace ts {
25742575
}
25752576

25762577
export type FlowNode =
2577-
| AfterFinallyFlow | PreFinallyFlow | FlowStart | FlowLabel | FlowAssignment | FlowCondition | FlowSwitchClause | FlowArrayMutation;
2578+
| AfterFinallyFlow | PreFinallyFlow | FlowStart | FlowLabel | FlowAssignment | FlowCall | FlowCondition | FlowSwitchClause | FlowArrayMutation;
25782579
export interface FlowNodeBase {
25792580
flags: FlowFlags;
25802581
id?: number; // Node id used by flow type cache in checker
@@ -2599,6 +2600,11 @@ namespace ts {
25992600
antecedent: FlowNode;
26002601
}
26012602

2603+
export interface FlowCall extends FlowNodeBase {
2604+
node: CallExpression;
2605+
antecedent: FlowNode;
2606+
}
2607+
26022608
// FlowCondition represents a condition that is known to be true or false at the
26032609
// node's location in the control flow.
26042610
export interface FlowCondition extends FlowNodeBase {
@@ -3902,6 +3908,7 @@ namespace ts {
39023908
resolvedSymbol?: Symbol; // Cached name resolution result
39033909
resolvedIndexInfo?: IndexInfo; // Cached indexing info resolution result
39043910
maybeTypePredicate?: boolean; // Cached check whether call expression might reference a type predicate
3911+
isAssertCall?: boolean;
39053912
enumMemberValue?: string | number; // Constant value of enum member
39063913
isVisible?: boolean; // Is this node visible
39073914
containsArgumentsReference?: boolean; // Whether a function-like declaration contains an 'arguments' reference

0 commit comments

Comments
 (0)