@@ -6456,6 +6456,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
6456
6456
function createNodeBuilder() {
6457
6457
return {
6458
6458
typeToTypeNode: (type: Type, enclosingDeclaration?: Node, flags?: NodeBuilderFlags, tracker?: SymbolTracker) => withContext(enclosingDeclaration, flags, tracker, context => typeToTypeNodeHelper(type, context)),
6459
+ typePredicateToTypePredicateNode: (typePredicate: TypePredicate, enclosingDeclaration?: Node, flags?: NodeBuilderFlags, tracker?: SymbolTracker) => withContext(enclosingDeclaration, flags, tracker, context => typePredicateToTypePredicateNodeHelper(typePredicate, context)),
6459
6460
indexInfoToIndexSignatureDeclaration: (indexInfo: IndexInfo, enclosingDeclaration?: Node, flags?: NodeBuilderFlags, tracker?: SymbolTracker) => withContext(enclosingDeclaration, flags, tracker, context => indexInfoToIndexSignatureDeclarationHelper(indexInfo, context, /*typeNode*/ undefined)),
6460
6461
signatureToSignatureDeclaration: (signature: Signature, kind: SignatureDeclaration["kind"], enclosingDeclaration?: Node, flags?: NodeBuilderFlags, tracker?: SymbolTracker) => withContext(enclosingDeclaration, flags, tracker, context => signatureToSignatureDeclarationHelper(signature, kind, context)),
6461
6462
symbolToEntityName: (symbol: Symbol, meaning: SymbolFlags, enclosingDeclaration?: Node, flags?: NodeBuilderFlags, tracker?: SymbolTracker) => withContext(enclosingDeclaration, flags, tracker, context => symbolToName(symbol, context, meaning, /*expectsIdentifier*/ false)),
@@ -7704,14 +7705,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
7704
7705
let returnTypeNode: TypeNode | undefined;
7705
7706
const typePredicate = getTypePredicateOfSignature(signature);
7706
7707
if (typePredicate) {
7707
- const assertsModifier = typePredicate.kind === TypePredicateKind.AssertsThis || typePredicate.kind === TypePredicateKind.AssertsIdentifier ?
7708
- factory.createToken(SyntaxKind.AssertsKeyword) :
7709
- undefined;
7710
- const parameterName = typePredicate.kind === TypePredicateKind.Identifier || typePredicate.kind === TypePredicateKind.AssertsIdentifier ?
7711
- setEmitFlags(factory.createIdentifier(typePredicate.parameterName), EmitFlags.NoAsciiEscaping) :
7712
- factory.createThisTypeNode();
7713
- const typeNode = typePredicate.type && typeToTypeNodeHelper(typePredicate.type, context);
7714
- returnTypeNode = factory.createTypePredicateNode(assertsModifier, parameterName, typeNode);
7708
+ returnTypeNode = typePredicateToTypePredicateNodeHelper(typePredicate, context);
7715
7709
}
7716
7710
else {
7717
7711
const returnType = getReturnTypeOfSignature(signature);
@@ -7790,6 +7784,17 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
7790
7784
return typeParameterToDeclarationWithConstraint(type, context, constraintNode);
7791
7785
}
7792
7786
7787
+ function typePredicateToTypePredicateNodeHelper(typePredicate: TypePredicate, context: NodeBuilderContext): TypePredicateNode {
7788
+ const assertsModifier = typePredicate.kind === TypePredicateKind.AssertsThis || typePredicate.kind === TypePredicateKind.AssertsIdentifier ?
7789
+ factory.createToken(SyntaxKind.AssertsKeyword) :
7790
+ undefined;
7791
+ const parameterName = typePredicate.kind === TypePredicateKind.Identifier || typePredicate.kind === TypePredicateKind.AssertsIdentifier ?
7792
+ setEmitFlags(factory.createIdentifier(typePredicate.parameterName), EmitFlags.NoAsciiEscaping) :
7793
+ factory.createThisTypeNode();
7794
+ const typeNode = typePredicate.type && typeToTypeNodeHelper(typePredicate.type, context);
7795
+ return factory.createTypePredicateNode(assertsModifier, parameterName, typeNode);
7796
+ }
7797
+
7793
7798
function getEffectiveParameterDeclaration(parameterSymbol: Symbol): ParameterDeclaration | JSDocParameterTag | undefined {
7794
7799
const parameterDeclaration: ParameterDeclaration | JSDocParameterTag | undefined = getDeclarationOfKind<ParameterDeclaration>(parameterSymbol, SyntaxKind.Parameter);
7795
7800
if (parameterDeclaration) {
@@ -10309,11 +10314,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
10309
10314
return writer ? typePredicateToStringWorker(writer).getText() : usingSingleLineStringWriter(typePredicateToStringWorker);
10310
10315
10311
10316
function typePredicateToStringWorker(writer: EmitTextWriter) {
10312
- const predicate = factory.createTypePredicateNode(
10313
- typePredicate.kind === TypePredicateKind.AssertsThis || typePredicate.kind === TypePredicateKind.AssertsIdentifier ? factory.createToken(SyntaxKind.AssertsKeyword) : undefined,
10314
- typePredicate.kind === TypePredicateKind.Identifier || typePredicate.kind === TypePredicateKind.AssertsIdentifier ? factory.createIdentifier(typePredicate.parameterName) : factory.createThisTypeNode(),
10315
- typePredicate.type && nodeBuilder.typeToTypeNode(typePredicate.type, enclosingDeclaration, toNodeBuilderFlags(flags) | NodeBuilderFlags.IgnoreErrors | NodeBuilderFlags.WriteTypeParametersInQualifiedName)!, // TODO: GH#18217
10316
- );
10317
+ const nodeBuilderFlags = toNodeBuilderFlags(flags) | NodeBuilderFlags.IgnoreErrors | NodeBuilderFlags.WriteTypeParametersInQualifiedName;
10318
+ const predicate = nodeBuilder.typePredicateToTypePredicateNode(typePredicate, enclosingDeclaration, nodeBuilderFlags)!; // TODO: GH#18217
10317
10319
const printer = createPrinterWithRemoveComments();
10318
10320
const sourceFile = enclosingDeclaration && getSourceFileOfNode(enclosingDeclaration);
10319
10321
printer.writeNode(EmitHint.Unspecified, predicate, /*sourceFile*/ sourceFile, writer);
@@ -15476,9 +15478,19 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
15476
15478
jsdocPredicate = getTypePredicateOfSignature(jsdocSignature);
15477
15479
}
15478
15480
}
15479
- signature.resolvedTypePredicate = type && isTypePredicateNode(type) ?
15480
- createTypePredicateFromTypePredicateNode(type, signature) :
15481
- jsdocPredicate || noTypePredicate;
15481
+ if (type || jsdocPredicate) {
15482
+ signature.resolvedTypePredicate = type && isTypePredicateNode(type) ?
15483
+ createTypePredicateFromTypePredicateNode(type, signature) :
15484
+ jsdocPredicate || noTypePredicate;
15485
+ }
15486
+ else if (signature.declaration && isFunctionLikeDeclaration(signature.declaration) && (!signature.resolvedReturnType || signature.resolvedReturnType.flags & TypeFlags.Boolean) && getParameterCount(signature) > 0) {
15487
+ const { declaration } = signature;
15488
+ signature.resolvedTypePredicate = noTypePredicate; // avoid infinite loop
15489
+ signature.resolvedTypePredicate = getTypePredicateFromBody(declaration) || noTypePredicate;
15490
+ }
15491
+ else {
15492
+ signature.resolvedTypePredicate = noTypePredicate;
15493
+ }
15482
15494
}
15483
15495
Debug.assert(!!signature.resolvedTypePredicate);
15484
15496
}
@@ -37450,6 +37462,72 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
37450
37462
}
37451
37463
}
37452
37464
37465
+ function getTypePredicateFromBody(func: FunctionLikeDeclaration): TypePredicate | undefined {
37466
+ switch (func.kind) {
37467
+ case SyntaxKind.Constructor:
37468
+ case SyntaxKind.GetAccessor:
37469
+ case SyntaxKind.SetAccessor:
37470
+ return undefined;
37471
+ }
37472
+ const functionFlags = getFunctionFlags(func);
37473
+ if (functionFlags !== FunctionFlags.Normal) return undefined;
37474
+
37475
+ // Only attempt to infer a type predicate if there's exactly one return.
37476
+ let singleReturn: Expression | undefined;
37477
+ if (func.body && func.body.kind !== SyntaxKind.Block) {
37478
+ singleReturn = func.body; // arrow function
37479
+ }
37480
+ else {
37481
+ const bailedEarly = forEachReturnStatement(func.body as Block, returnStatement => {
37482
+ if (singleReturn || !returnStatement.expression) return true;
37483
+ singleReturn = returnStatement.expression;
37484
+ });
37485
+ if (bailedEarly || !singleReturn || functionHasImplicitReturn(func)) return undefined;
37486
+ }
37487
+ return checkIfExpressionRefinesAnyParameter(func, singleReturn);
37488
+ }
37489
+
37490
+ function checkIfExpressionRefinesAnyParameter(func: FunctionLikeDeclaration, expr: Expression): TypePredicate | undefined {
37491
+ expr = skipParentheses(expr, /*excludeJSDocTypeAssertions*/ true);
37492
+ const returnType = checkExpressionCached(expr);
37493
+ if (!(returnType.flags & TypeFlags.Boolean)) return undefined;
37494
+
37495
+ return forEach(func.parameters, (param, i) => {
37496
+ const initType = getTypeOfSymbol(param.symbol);
37497
+ if (!initType || initType.flags & TypeFlags.Boolean || !isIdentifier(param.name) || isSymbolAssigned(param.symbol) || isRestParameter(param)) {
37498
+ // Refining "x: boolean" to "x is true" or "x is false" isn't useful.
37499
+ return;
37500
+ }
37501
+ const trueType = checkIfExpressionRefinesParameter(func, expr, param, initType);
37502
+ if (trueType) {
37503
+ return createTypePredicate(TypePredicateKind.Identifier, unescapeLeadingUnderscores(param.name.escapedText), i, trueType);
37504
+ }
37505
+ });
37506
+ }
37507
+
37508
+ function checkIfExpressionRefinesParameter(func: FunctionLikeDeclaration, expr: Expression, param: ParameterDeclaration, initType: Type): Type | undefined {
37509
+ const antecedent = (expr as Expression & { flowNode?: FlowNode; }).flowNode ||
37510
+ expr.parent.kind === SyntaxKind.ReturnStatement && (expr.parent as ReturnStatement).flowNode ||
37511
+ { flags: FlowFlags.Start };
37512
+ const trueCondition: FlowCondition = {
37513
+ flags: FlowFlags.TrueCondition,
37514
+ node: expr,
37515
+ antecedent,
37516
+ };
37517
+
37518
+ const trueType = getFlowTypeOfReference(param.name, initType, initType, func, trueCondition);
37519
+ if (trueType === initType) return undefined;
37520
+
37521
+ // "x is T" means that x is T if and only if it returns true. If it returns false then x is not T.
37522
+ // This means that if the function is called with an argument of type trueType, there can't be anything left in the `else` branch. It must reduce to `never`.
37523
+ const falseCondition: FlowCondition = {
37524
+ ...trueCondition,
37525
+ flags: FlowFlags.FalseCondition,
37526
+ };
37527
+ const falseSubtype = getFlowTypeOfReference(param.name, trueType, trueType, func, falseCondition);
37528
+ return falseSubtype.flags & TypeFlags.Never ? trueType : undefined;
37529
+ }
37530
+
37453
37531
/**
37454
37532
* TypeScript Specification 1.0 (6.3) - July 2014
37455
37533
* An explicitly typed function whose return type isn't the Void type,
@@ -48511,6 +48589,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
48511
48589
return factory.createToken(SyntaxKind.AnyKeyword) as KeywordTypeNode;
48512
48590
}
48513
48591
const signature = getSignatureFromDeclaration(signatureDeclaration);
48592
+ const typePredicate = getTypePredicateOfSignature(signature);
48593
+ if (typePredicate) {
48594
+ // Inferred type predicates
48595
+ return nodeBuilder.typePredicateToTypePredicateNode(typePredicate, enclosingDeclaration, flags | NodeBuilderFlags.MultilineObjectLiterals, tracker);
48596
+ }
48514
48597
return nodeBuilder.typeToTypeNode(getReturnTypeOfSignature(signature), enclosingDeclaration, flags | NodeBuilderFlags.MultilineObjectLiterals, tracker);
48515
48598
}
48516
48599
0 commit comments