Skip to content

Commit 00501f2

Browse files
committed
Tighten bounds on what kinds of functions can be inferred predicates
1 parent 3ab6fae commit 00501f2

File tree

2 files changed

+22
-36
lines changed

2 files changed

+22
-36
lines changed

src/compiler/checker.ts

Lines changed: 19 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -37400,33 +37400,30 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3740037400
}
3740137401

3740237402
function getTypePredicateFromBody(func: FunctionLikeDeclaration): TypePredicate | undefined {
37403-
switch (func.kind) {
37404-
case SyntaxKind.Constructor:
37405-
case SyntaxKind.GetAccessor:
37406-
case SyntaxKind.SetAccessor:
37407-
return undefined;
37403+
if (func.kind === SyntaxKind.Constructor || func.kind === SyntaxKind.SetAccessor) {
37404+
return undefined;
3740837405
}
37409-
const functionFlags = getFunctionFlags(func);
37410-
if (functionFlags !== FunctionFlags.Normal || func.parameters.length === 0) return undefined;
3741137406

37412-
// Only attempt to infer a type predicate if there's exactly one return.
37413-
let singleReturn: Expression | undefined;
37414-
let singleReturnStatement: ReturnStatement | undefined;
37415-
if (func.body && func.body.kind !== SyntaxKind.Block) {
37416-
singleReturn = func.body; // arrow function
37417-
}
37418-
else {
37419-
if (functionHasImplicitReturn(func)) return undefined;
37407+
// Only a single-argument function can be inferred to be a type predicate
37408+
if (func.parameters.length !== 1) return undefined;
3742037409

37421-
const bailedEarly = forEachReturnStatement(func.body as Block, returnStatement => {
37422-
if (singleReturn || !returnStatement.expression) return true;
37423-
singleReturnStatement = returnStatement;
37424-
singleReturn = returnStatement.expression;
37425-
});
37426-
if (bailedEarly || !singleReturn) return undefined;
37410+
// The body must be an expression, or a block containing a lone return statement
37411+
if (!func.body) return undefined;
37412+
37413+
let returnExpression: Expression | undefined;
37414+
if (func.body.kind === SyntaxKind.Block) {
37415+
const body = func.body as Block;
37416+
if ((body.statements.length !== 1) || (body.statements[0].kind !== SyntaxKind.ReturnStatement)) return undefined;
37417+
returnExpression = (body.statements[0] as ReturnStatement).expression;
37418+
} else {
37419+
returnExpression = func.body;
3742737420
}
37421+
if (!returnExpression) return undefined;
3742837422

37429-
const predicate = checkIfExpressionRefinesAnyParameter(singleReturn);
37423+
const functionFlags = getFunctionFlags(func);
37424+
if (functionFlags !== FunctionFlags.Normal) return undefined;
37425+
37426+
const predicate = checkIfExpressionRefinesAnyParameter(returnExpression);
3743037427
if (predicate) {
3743137428
const [i, type] = predicate;
3743237429
const param = func.parameters[i];
@@ -37474,17 +37471,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3747437471
};
3747537472
const falseSubtype = getFlowTypeOfReference(param.name, trueType, trueType, func, falseCondition);
3747637473
if (!isTypeIdenticalTo(falseSubtype, neverType)) return undefined;
37477-
37478-
// the parameter type may already have been narrowed due to an assertion.
37479-
// There's no precise way to represent an assertion that's also a predicate. Best not to try.
37480-
// We do this check last since it's unlikely to filter out many possible predicates.
37481-
if (singleReturnStatement?.flowNode) {
37482-
const typeAtReturn = getFlowTypeOfReference(param.name, initType, initType, func, singleReturnStatement?.flowNode);
37483-
if (typeAtReturn !== initType) {
37484-
return undefined;
37485-
}
37486-
}
37487-
3748837474
return trueType;
3748937475
}
3749037476
}

tests/baselines/reference/inferTypePredicates.types

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ function isNonNull(x: number | null) {
156156

157157
// factoring out a boolean works thanks to aliased discriminants
158158
function isNonNullVar(x: number | null) {
159-
>isNonNullVar : (x: number | null) => x is number
159+
>isNonNullVar : (x: number | null) => boolean
160160
>x : number | null
161161

162162
const ok = x !== null;
@@ -647,7 +647,7 @@ function doubleReturn(x: string|number) {
647647
}
648648

649649
function guardsOneButNotOthers(a: string|number, b: string|number, c: string|number) {
650-
>guardsOneButNotOthers : (a: string | number, b: string | number, c: string | number) => b is string
650+
>guardsOneButNotOthers : (a: string | number, b: string | number, c: string | number) => boolean
651651
>a : string | number
652652
>b : string | number
653653
>c : string | number
@@ -919,7 +919,7 @@ if (assertAndPredicate(snd)) {
919919
}
920920

921921
function isNumberWithThis(this: Date, x: number | string) {
922-
>isNumberWithThis : (this: Date, x: number | string) => x is number
922+
>isNumberWithThis : (this: Date, x: number | string) => boolean
923923
>this : Date
924924
>x : string | number
925925

0 commit comments

Comments
 (0)