Skip to content

Commit c90b0fe

Browse files
committed
No implicit returns following exhaustive switch statements
1 parent ce15646 commit c90b0fe

3 files changed

Lines changed: 44 additions & 4 deletions

File tree

src/compiler/binder.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -650,6 +650,11 @@ namespace ts {
650650
return isNarrowableReference(expr);
651651
}
652652

653+
function isNarrowingSwitchStatement(switchStatement: SwitchStatement) {
654+
const expr = switchStatement.expression;
655+
return expr.kind === SyntaxKind.PropertyAccessExpression && isNarrowableReference((<PropertyAccessExpression>expr).expression);
656+
}
657+
653658
function createBranchLabel(): FlowLabel {
654659
return {
655660
flags: FlowFlags.BranchLabel,
@@ -699,8 +704,7 @@ namespace ts {
699704
}
700705

701706
function createFlowSwitchClause(antecedent: FlowNode, switchStatement: SwitchStatement, clauseStart: number, clauseEnd: number): FlowNode {
702-
const expr = switchStatement.expression;
703-
if (expr.kind !== SyntaxKind.PropertyAccessExpression || !isNarrowableReference((<PropertyAccessExpression>expr).expression)) {
707+
if (!isNarrowingSwitchStatement(switchStatement)) {
704708
return antecedent;
705709
}
706710
setFlowNodeReferenced(antecedent);
@@ -939,6 +943,9 @@ namespace ts {
939943
bind(node.caseBlock);
940944
addAntecedent(postSwitchLabel, currentFlow);
941945
const hasDefault = forEach(node.caseBlock.clauses, c => c.kind === SyntaxKind.DefaultClause);
946+
// We mark a switch statement as possibly exhaustive if it has no default clause and if all
947+
// case clauses have unreachable end points (e.g. they all return).
948+
node.possiblyExhaustive = !hasDefault && !postSwitchLabel.antecedents;
942949
if (!hasDefault) {
943950
addAntecedent(postSwitchLabel, createFlowSwitchClause(preSwitchCaseFlow, node, 0, 0));
944951
}

src/compiler/checker.ts

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11808,10 +11808,42 @@ namespace ts {
1180811808
return aggregatedTypes;
1180911809
}
1181011810

11811+
function isExhaustiveSwitchStatement(node: SwitchStatement): boolean {
11812+
const expr = node.expression;
11813+
if (!node.possiblyExhaustive || expr.kind !== SyntaxKind.PropertyAccessExpression) {
11814+
return false;
11815+
}
11816+
const type = checkExpression((<PropertyAccessExpression>expr).expression);
11817+
if (!(type.flags & TypeFlags.Union)) {
11818+
return false;
11819+
}
11820+
const propName = (<PropertyAccessExpression>expr).name.text;
11821+
const propType = getTypeOfPropertyOfType(type, propName);
11822+
if (!propType || !isStringLiteralUnionType(propType)) {
11823+
return false;
11824+
}
11825+
const switchTypes = getSwitchClauseTypes(node);
11826+
if (!switchTypes.length) {
11827+
return false;
11828+
}
11829+
return eachTypeContainedIn(propType, switchTypes);
11830+
}
11831+
11832+
function functionHasImplicitReturn(func: FunctionLikeDeclaration) {
11833+
if (!(func.flags & NodeFlags.HasImplicitReturn)) {
11834+
return false;
11835+
}
11836+
const lastStatement = lastOrUndefined((<Block>func.body).statements);
11837+
if (lastStatement && lastStatement.kind === SyntaxKind.SwitchStatement && isExhaustiveSwitchStatement(<SwitchStatement>lastStatement)) {
11838+
return false;
11839+
}
11840+
return true;
11841+
}
11842+
1181111843
function checkAndAggregateReturnExpressionTypes(func: FunctionLikeDeclaration, contextualMapper: TypeMapper): Type[] {
1181211844
const isAsync = isAsyncFunctionLike(func);
1181311845
const aggregatedTypes: Type[] = [];
11814-
let hasReturnWithNoExpression = !!(func.flags & NodeFlags.HasImplicitReturn);
11846+
let hasReturnWithNoExpression = functionHasImplicitReturn(func);
1181511847
let hasReturnOfTypeNever = false;
1181611848
forEachReturnStatement(<Block>func.body, returnStatement => {
1181711849
const expr = returnStatement.expression;
@@ -11868,7 +11900,7 @@ namespace ts {
1186811900

1186911901
// If all we have is a function signature, or an arrow function with an expression body, then there is nothing to check.
1187011902
// also if HasImplicitReturn flag is not set this means that all codepaths in function body end with return or throw
11871-
if (nodeIsMissing(func.body) || func.body.kind !== SyntaxKind.Block || !(func.flags & NodeFlags.HasImplicitReturn)) {
11903+
if (nodeIsMissing(func.body) || func.body.kind !== SyntaxKind.Block || !functionHasImplicitReturn(func)) {
1187211904
return;
1187311905
}
1187411906

src/compiler/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1188,6 +1188,7 @@ namespace ts {
11881188
export interface SwitchStatement extends Statement {
11891189
expression: Expression;
11901190
caseBlock: CaseBlock;
1191+
possiblyExhaustive?: boolean;
11911192
}
11921193

11931194
// @kind(SyntaxKind.CaseBlock)

0 commit comments

Comments
 (0)