Fix fallthrough and default in new switch-true narrowing (#55991)

This commit is contained in:
Jake Bailey
2023-10-06 16:23:54 -07:00
committed by GitHub
parent 2e58032f06
commit 53a3d24b95
35 changed files with 3119 additions and 151 deletions
+31 -5
View File
@@ -27456,11 +27456,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
type = narrowTypeBySwitchOnTypeOf(type, flow.switchStatement, flow.clauseStart, flow.clauseEnd);
}
else if (expr.kind === SyntaxKind.TrueKeyword) {
const clause = flow.switchStatement.caseBlock.clauses.find((_, index) => index === flow.clauseStart);
const clauseExpression = clause && clause.kind === SyntaxKind.CaseClause ? clause.expression : undefined;
if (clauseExpression) {
type = narrowType(type, clauseExpression, /*assumeTrue*/ true);
}
type = narrowTypeBySwitchOnTrue(type, flow.switchStatement, flow.clauseStart, flow.clauseEnd);
}
else {
if (strictNullChecks) {
@@ -28075,6 +28071,36 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
return getUnionType(map(clauseWitnesses, text => text ? narrowTypeByTypeName(type, text) : neverType));
}
function narrowTypeBySwitchOnTrue(type: Type, switchStatement: SwitchStatement, clauseStart: number, clauseEnd: number): Type {
const defaultIndex = findIndex(switchStatement.caseBlock.clauses, clause => clause.kind === SyntaxKind.DefaultClause);
const hasDefaultClause = clauseStart === clauseEnd || (defaultIndex >= clauseStart && defaultIndex < clauseEnd);
// First, narrow away all of the cases that preceded this set of cases.
for (let i = 0; i < clauseStart; i++) {
const clause = switchStatement.caseBlock.clauses[i];
if (clause.kind === SyntaxKind.CaseClause) {
type = narrowType(type, clause.expression, /*assumeTrue*/ false);
}
}
// If our current set has a default, then none the other cases were hit either.
// There's no point in narrowing by the the other cases in the set, since we can
// get here through other paths.
if (hasDefaultClause) {
for (let i = clauseEnd; i < switchStatement.caseBlock.clauses.length; i++) {
const clause = switchStatement.caseBlock.clauses[i];
if (clause.kind === SyntaxKind.CaseClause) {
type = narrowType(type, clause.expression, /*assumeTrue*/ false);
}
}
return type;
}
// Now, narrow based on the cases in this set.
const clauses = switchStatement.caseBlock.clauses.slice(clauseStart, clauseEnd);
return getUnionType(map(clauses, clause => clause.kind === SyntaxKind.CaseClause ? narrowType(type, clause.expression, /*assumeTrue*/ true) : neverType));
}
function isMatchingConstructorReference(expr: Expression) {
return (isPropertyAccessExpression(expr) && idText(expr.name) === "constructor" ||
isElementAccessExpression(expr) && isStringLiteralLike(expr.argumentExpression) && expr.argumentExpression.text === "constructor") &&