Skip to content

Commit df02ad6

Browse files
committed
Reflect control flow effects of calls to never-returning functions
1 parent 1c55e5d commit df02ad6

File tree

3 files changed

+39
-41
lines changed

3 files changed

+39
-41
lines changed

src/compiler/binder.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1295,7 +1295,7 @@ namespace ts {
12951295
// is potentially an assertion and is therefore included in the control flow.
12961296
if (node.expression.kind === SyntaxKind.CallExpression) {
12971297
const call = <CallExpression>node.expression;
1298-
if (isDottedName(call.expression) && call.arguments.length >= 1) {
1298+
if (isDottedName(call.expression)) {
12991299
currentFlow = createFlowCall(currentFlow, call);
13001300
}
13011301
}

src/compiler/checker.ts

Lines changed: 37 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -16853,31 +16853,23 @@ namespace ts {
1685316853
}
1685416854
}
1685516855

16856-
function getTypePredicateForCall(node: CallExpression) {
16856+
function isCallWithEffects(node: CallExpression) {
1685716857
const links = getNodeLinks(node);
16858-
if (links.resolvedTypePredicate === undefined) {
16859-
links.resolvedTypePredicate = computeTypePredicateForCall(node) || noTypePredicate;
16860-
}
16861-
return links.resolvedTypePredicate === noTypePredicate ? undefined : links.resolvedTypePredicate;
16862-
}
16863-
16864-
function computeTypePredicateForCall(node: CallExpression) {
16865-
// A call expression parented by an expression statement is a potential assertion. Other call
16866-
// expressions are potential type predicate function calls.
16867-
const funcType = node.parent.kind === SyntaxKind.ExpressionStatement ? getTypeOfDottedName(node.expression) :
16868-
node.expression.kind !== SyntaxKind.SuperKeyword ? checkNonNullExpression(node.expression) :
16869-
undefined;
16870-
if (funcType && funcType !== silentNeverType) {
16871-
const apparentType = getApparentType(funcType);
16872-
if (some(getSignaturesOfType(apparentType, SignatureKind.Call), hasTypePredicate)) {
16873-
return getTypePredicateOfSignature(getResolvedSignature(node));
16874-
}
16858+
if (links.isCallWithEffects === undefined) {
16859+
// A call expression parented by an expression statement is a potential assertion. Other call
16860+
// expressions are potential type predicate function calls.
16861+
const funcType = node.parent.kind === SyntaxKind.ExpressionStatement ? getTypeOfDottedName(node.expression) :
16862+
node.expression.kind !== SyntaxKind.SuperKeyword ? checkNonNullExpression(node.expression) :
16863+
undefined;
16864+
const apparentType = funcType && getApparentType(funcType) || unknownType;
16865+
links.isCallWithEffects = some(getSignaturesOfType(apparentType, SignatureKind.Call), hasTypePredicateOrNeverReturnType);
1687516866
}
16876-
return undefined;
16867+
return links.isCallWithEffects;
1687716868
}
1687816869

16879-
function hasTypePredicate(signature: Signature) {
16880-
return !!getTypePredicateOfSignature(signature);
16870+
function hasTypePredicateOrNeverReturnType(signature: Signature) {
16871+
return !!(getTypePredicateOfSignature(signature) ||
16872+
signature.declaration && (getReturnTypeFromAnnotation(signature.declaration) || unknownType).flags & TypeFlags.Never);
1688116873
}
1688216874

1688316875
function reportFlowControlError(node: Node) {
@@ -17094,14 +17086,20 @@ namespace ts {
1709417086
}
1709517087

1709617088
function getTypeAtFlowCall(flow: FlowCall): FlowType | undefined {
17097-
const predicate = getTypePredicateForCall(flow.node);
17098-
if (predicate && predicate.kind === TypePredicateKind.Assertion) {
17099-
const flowType = getTypeAtFlowNode(flow.antecedent);
17100-
const type = getTypeFromFlowType(flowType);
17101-
const narrowedType = predicate.type ?
17102-
narrowTypeByTypePredicate(type, predicate, flow.node, /*assumeTrue*/ true) :
17103-
narrowTypeByAssertion(type, flow.node.arguments[predicate.parameterIndex]);
17104-
return narrowedType === type ? flowType : createFlowType(narrowedType, isIncomplete(flowType));
17089+
if (isCallWithEffects(flow.node)) {
17090+
const signature = getResolvedSignature(flow.node);
17091+
const predicate = getTypePredicateOfSignature(signature);
17092+
if (predicate && predicate.kind === TypePredicateKind.Assertion) {
17093+
const flowType = getTypeAtFlowNode(flow.antecedent);
17094+
const type = getTypeFromFlowType(flowType);
17095+
const narrowedType = predicate.type ?
17096+
narrowTypeByTypePredicate(type, predicate, flow.node, /*assumeTrue*/ true) :
17097+
narrowTypeByAssertion(type, flow.node.arguments[predicate.parameterIndex]);
17098+
return narrowedType === type ? flowType : createFlowType(narrowedType, isIncomplete(flowType));
17099+
}
17100+
if (getReturnTypeOfSignature(signature).flags & TypeFlags.Never) {
17101+
return neverType;
17102+
}
1710517103
}
1710617104
return undefined;
1710717105
}
@@ -17690,8 +17688,9 @@ namespace ts {
1769017688
}
1769117689

1769217690
function narrowTypeByCallExpression(type: Type, callExpression: CallExpression, assumeTrue: boolean): Type {
17693-
if (hasMatchingArgument(callExpression, reference)) {
17694-
const predicate = getTypePredicateForCall(callExpression);
17691+
if (hasMatchingArgument(callExpression, reference) && isCallWithEffects(callExpression)) {
17692+
const signature = getResolvedSignature(callExpression);
17693+
const predicate = getTypePredicateOfSignature(signature);
1769517694
if (predicate && predicate.kind !== TypePredicateKind.Assertion) {
1769617695
return narrowTypeByTypePredicate(type, predicate, callExpression, assumeTrue);
1769717696
}
@@ -23652,15 +23651,14 @@ namespace ts {
2365223651
return eachTypeContainedIn(mapType(type, getRegularTypeOfLiteralType), switchTypes);
2365323652
}
2365423653

23655-
function functionHasImplicitReturn(func: FunctionLikeDeclaration) {
23656-
if (!(func.flags & NodeFlags.HasImplicitReturn)) {
23657-
return false;
23658-
}
23654+
function isNeverFunctionCall(expr: Expression) {
23655+
return expr.kind === SyntaxKind.CallExpression && isCallWithEffects(<CallExpression>expr) && !!(getTypeOfExpression(expr).flags & TypeFlags.Never);
23656+
}
2365923657

23660-
if (some((<Block>func.body).statements, statement => statement.kind === SyntaxKind.SwitchStatement && isExhaustiveSwitchStatement(<SwitchStatement>statement))) {
23661-
return false;
23662-
}
23663-
return true;
23658+
function functionHasImplicitReturn(func: FunctionLikeDeclaration) {
23659+
return !!(func.flags & NodeFlags.HasImplicitReturn) && !some((<Block>func.body).statements, statement =>
23660+
statement.kind === SyntaxKind.SwitchStatement && isExhaustiveSwitchStatement(<SwitchStatement>statement) ||
23661+
statement.kind === SyntaxKind.ExpressionStatement && isNeverFunctionCall((<ExpressionStatement>statement).expression));
2366423662
}
2366523663

2366623664
/** NOTE: Return value of `[]` means a different thing than `undefined`. `[]` means func returns `void`, `undefined` means it returns `never`. */

src/compiler/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3918,7 +3918,7 @@ namespace ts {
39183918
resolvedSignature?: Signature; // Cached signature of signature node or call expression
39193919
resolvedSymbol?: Symbol; // Cached name resolution result
39203920
resolvedIndexInfo?: IndexInfo; // Cached indexing info resolution result
3921-
resolvedTypePredicate?: TypePredicate; // Cached type predicate for call expression
3921+
isCallWithEffects?: boolean; // Is call expression with possible control flow effects?
39223922
enumMemberValue?: string | number; // Constant value of enum member
39233923
isVisible?: boolean; // Is this node visible
39243924
containsArgumentsReference?: boolean; // Whether a function-like declaration contains an 'arguments' reference

0 commit comments

Comments
 (0)