Infer type predicates from function bodies using control flow analysis (#57465)

Co-authored-by: Anders Hejlsberg <andersh@microsoft.com>
This commit is contained in:
Dan Vanderkam 2024-03-15 14:42:55 -04:00 committed by GitHub
parent 60cf79127a
commit e5bf594753
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 3229 additions and 97 deletions

View File

@ -1094,7 +1094,7 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void {
inAssignmentPattern = saveInAssignmentPattern;
return;
}
if (node.kind >= SyntaxKind.FirstStatement && node.kind <= SyntaxKind.LastStatement && !options.allowUnreachableCode) {
if (node.kind >= SyntaxKind.FirstStatement && node.kind <= SyntaxKind.LastStatement && (!options.allowUnreachableCode || node.kind === SyntaxKind.ReturnStatement)) {
(node as HasFlowNode).flowNode = currentFlow;
}
switch (node.kind) {

View File

@ -6456,6 +6456,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
function createNodeBuilder() {
return {
typeToTypeNode: (type: Type, enclosingDeclaration?: Node, flags?: NodeBuilderFlags, tracker?: SymbolTracker) => withContext(enclosingDeclaration, flags, tracker, context => typeToTypeNodeHelper(type, context)),
typePredicateToTypePredicateNode: (typePredicate: TypePredicate, enclosingDeclaration?: Node, flags?: NodeBuilderFlags, tracker?: SymbolTracker) => withContext(enclosingDeclaration, flags, tracker, context => typePredicateToTypePredicateNodeHelper(typePredicate, context)),
indexInfoToIndexSignatureDeclaration: (indexInfo: IndexInfo, enclosingDeclaration?: Node, flags?: NodeBuilderFlags, tracker?: SymbolTracker) => withContext(enclosingDeclaration, flags, tracker, context => indexInfoToIndexSignatureDeclarationHelper(indexInfo, context, /*typeNode*/ undefined)),
signatureToSignatureDeclaration: (signature: Signature, kind: SignatureDeclaration["kind"], enclosingDeclaration?: Node, flags?: NodeBuilderFlags, tracker?: SymbolTracker) => withContext(enclosingDeclaration, flags, tracker, context => signatureToSignatureDeclarationHelper(signature, kind, context)),
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 {
let returnTypeNode: TypeNode | undefined;
const typePredicate = getTypePredicateOfSignature(signature);
if (typePredicate) {
const assertsModifier = typePredicate.kind === TypePredicateKind.AssertsThis || typePredicate.kind === TypePredicateKind.AssertsIdentifier ?
factory.createToken(SyntaxKind.AssertsKeyword) :
undefined;
const parameterName = typePredicate.kind === TypePredicateKind.Identifier || typePredicate.kind === TypePredicateKind.AssertsIdentifier ?
setEmitFlags(factory.createIdentifier(typePredicate.parameterName), EmitFlags.NoAsciiEscaping) :
factory.createThisTypeNode();
const typeNode = typePredicate.type && typeToTypeNodeHelper(typePredicate.type, context);
returnTypeNode = factory.createTypePredicateNode(assertsModifier, parameterName, typeNode);
returnTypeNode = typePredicateToTypePredicateNodeHelper(typePredicate, context);
}
else {
const returnType = getReturnTypeOfSignature(signature);
@ -7790,6 +7784,17 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
return typeParameterToDeclarationWithConstraint(type, context, constraintNode);
}
function typePredicateToTypePredicateNodeHelper(typePredicate: TypePredicate, context: NodeBuilderContext): TypePredicateNode {
const assertsModifier = typePredicate.kind === TypePredicateKind.AssertsThis || typePredicate.kind === TypePredicateKind.AssertsIdentifier ?
factory.createToken(SyntaxKind.AssertsKeyword) :
undefined;
const parameterName = typePredicate.kind === TypePredicateKind.Identifier || typePredicate.kind === TypePredicateKind.AssertsIdentifier ?
setEmitFlags(factory.createIdentifier(typePredicate.parameterName), EmitFlags.NoAsciiEscaping) :
factory.createThisTypeNode();
const typeNode = typePredicate.type && typeToTypeNodeHelper(typePredicate.type, context);
return factory.createTypePredicateNode(assertsModifier, parameterName, typeNode);
}
function getEffectiveParameterDeclaration(parameterSymbol: Symbol): ParameterDeclaration | JSDocParameterTag | undefined {
const parameterDeclaration: ParameterDeclaration | JSDocParameterTag | undefined = getDeclarationOfKind<ParameterDeclaration>(parameterSymbol, SyntaxKind.Parameter);
if (parameterDeclaration) {
@ -10309,11 +10314,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
return writer ? typePredicateToStringWorker(writer).getText() : usingSingleLineStringWriter(typePredicateToStringWorker);
function typePredicateToStringWorker(writer: EmitTextWriter) {
const predicate = factory.createTypePredicateNode(
typePredicate.kind === TypePredicateKind.AssertsThis || typePredicate.kind === TypePredicateKind.AssertsIdentifier ? factory.createToken(SyntaxKind.AssertsKeyword) : undefined,
typePredicate.kind === TypePredicateKind.Identifier || typePredicate.kind === TypePredicateKind.AssertsIdentifier ? factory.createIdentifier(typePredicate.parameterName) : factory.createThisTypeNode(),
typePredicate.type && nodeBuilder.typeToTypeNode(typePredicate.type, enclosingDeclaration, toNodeBuilderFlags(flags) | NodeBuilderFlags.IgnoreErrors | NodeBuilderFlags.WriteTypeParametersInQualifiedName)!, // TODO: GH#18217
);
const nodeBuilderFlags = toNodeBuilderFlags(flags) | NodeBuilderFlags.IgnoreErrors | NodeBuilderFlags.WriteTypeParametersInQualifiedName;
const predicate = nodeBuilder.typePredicateToTypePredicateNode(typePredicate, enclosingDeclaration, nodeBuilderFlags)!; // TODO: GH#18217
const printer = createPrinterWithRemoveComments();
const sourceFile = enclosingDeclaration && getSourceFileOfNode(enclosingDeclaration);
printer.writeNode(EmitHint.Unspecified, predicate, /*sourceFile*/ sourceFile, writer);
@ -15476,9 +15478,19 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
jsdocPredicate = getTypePredicateOfSignature(jsdocSignature);
}
}
signature.resolvedTypePredicate = type && isTypePredicateNode(type) ?
createTypePredicateFromTypePredicateNode(type, signature) :
jsdocPredicate || noTypePredicate;
if (type || jsdocPredicate) {
signature.resolvedTypePredicate = type && isTypePredicateNode(type) ?
createTypePredicateFromTypePredicateNode(type, signature) :
jsdocPredicate || noTypePredicate;
}
else if (signature.declaration && isFunctionLikeDeclaration(signature.declaration) && (!signature.resolvedReturnType || signature.resolvedReturnType.flags & TypeFlags.Boolean) && getParameterCount(signature) > 0) {
const { declaration } = signature;
signature.resolvedTypePredicate = noTypePredicate; // avoid infinite loop
signature.resolvedTypePredicate = getTypePredicateFromBody(declaration) || noTypePredicate;
}
else {
signature.resolvedTypePredicate = noTypePredicate;
}
}
Debug.assert(!!signature.resolvedTypePredicate);
}
@ -37450,6 +37462,72 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}
}
function getTypePredicateFromBody(func: FunctionLikeDeclaration): TypePredicate | undefined {
switch (func.kind) {
case SyntaxKind.Constructor:
case SyntaxKind.GetAccessor:
case SyntaxKind.SetAccessor:
return undefined;
}
const functionFlags = getFunctionFlags(func);
if (functionFlags !== FunctionFlags.Normal) return undefined;
// Only attempt to infer a type predicate if there's exactly one return.
let singleReturn: Expression | undefined;
if (func.body && func.body.kind !== SyntaxKind.Block) {
singleReturn = func.body; // arrow function
}
else {
const bailedEarly = forEachReturnStatement(func.body as Block, returnStatement => {
if (singleReturn || !returnStatement.expression) return true;
singleReturn = returnStatement.expression;
});
if (bailedEarly || !singleReturn || functionHasImplicitReturn(func)) return undefined;
}
return checkIfExpressionRefinesAnyParameter(func, singleReturn);
}
function checkIfExpressionRefinesAnyParameter(func: FunctionLikeDeclaration, expr: Expression): TypePredicate | undefined {
expr = skipParentheses(expr, /*excludeJSDocTypeAssertions*/ true);
const returnType = checkExpressionCached(expr);
if (!(returnType.flags & TypeFlags.Boolean)) return undefined;
return forEach(func.parameters, (param, i) => {
const initType = getTypeOfSymbol(param.symbol);
if (!initType || initType.flags & TypeFlags.Boolean || !isIdentifier(param.name) || isSymbolAssigned(param.symbol) || isRestParameter(param)) {
// Refining "x: boolean" to "x is true" or "x is false" isn't useful.
return;
}
const trueType = checkIfExpressionRefinesParameter(func, expr, param, initType);
if (trueType) {
return createTypePredicate(TypePredicateKind.Identifier, unescapeLeadingUnderscores(param.name.escapedText), i, trueType);
}
});
}
function checkIfExpressionRefinesParameter(func: FunctionLikeDeclaration, expr: Expression, param: ParameterDeclaration, initType: Type): Type | undefined {
const antecedent = (expr as Expression & { flowNode?: FlowNode; }).flowNode ||
expr.parent.kind === SyntaxKind.ReturnStatement && (expr.parent as ReturnStatement).flowNode ||
{ flags: FlowFlags.Start };
const trueCondition: FlowCondition = {
flags: FlowFlags.TrueCondition,
node: expr,
antecedent,
};
const trueType = getFlowTypeOfReference(param.name, initType, initType, func, trueCondition);
if (trueType === initType) return undefined;
// "x is T" means that x is T if and only if it returns true. If it returns false then x is not T.
// 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`.
const falseCondition: FlowCondition = {
...trueCondition,
flags: FlowFlags.FalseCondition,
};
const falseSubtype = getFlowTypeOfReference(param.name, trueType, trueType, func, falseCondition);
return falseSubtype.flags & TypeFlags.Never ? trueType : undefined;
}
/**
* TypeScript Specification 1.0 (6.3) - July 2014
* An explicitly typed function whose return type isn't the Void type,
@ -48511,6 +48589,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
return factory.createToken(SyntaxKind.AnyKeyword) as KeywordTypeNode;
}
const signature = getSignatureFromDeclaration(signatureDeclaration);
const typePredicate = getTypePredicateOfSignature(signature);
if (typePredicate) {
// Inferred type predicates
return nodeBuilder.typePredicateToTypePredicateNode(typePredicate, enclosingDeclaration, flags | NodeBuilderFlags.MultilineObjectLiterals, tracker);
}
return nodeBuilder.typeToTypeNode(getReturnTypeOfSignature(signature), enclosingDeclaration, flags | NodeBuilderFlags.MultilineObjectLiterals, tracker);
}

View File

@ -0,0 +1,40 @@
//// [tests/cases/compiler/circularConstructorWithReturn.ts] ////
//// [circularConstructorWithReturn.ts]
// This should not be a circularity error. See
// https://github.com/microsoft/TypeScript/pull/57465#issuecomment-1960271216
export type Client = ReturnType<typeof getPrismaClient> extends new () => infer T ? T : never
export function getPrismaClient(options?: any) {
class PrismaClient {
self: Client;
constructor(options?: any) {
return (this.self = applyModelsAndClientExtensions(this));
}
}
return PrismaClient
}
export function applyModelsAndClientExtensions(client: Client) {
return client;
}
//// [circularConstructorWithReturn.js]
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getPrismaClient = getPrismaClient;
exports.applyModelsAndClientExtensions = applyModelsAndClientExtensions;
function getPrismaClient(options) {
var PrismaClient = /** @class */ (function () {
function PrismaClient(options) {
return (this.self = applyModelsAndClientExtensions(this));
}
return PrismaClient;
}());
return PrismaClient;
}
function applyModelsAndClientExtensions(client) {
return client;
}

View File

@ -0,0 +1,48 @@
//// [tests/cases/compiler/circularConstructorWithReturn.ts] ////
=== circularConstructorWithReturn.ts ===
// This should not be a circularity error. See
// https://github.com/microsoft/TypeScript/pull/57465#issuecomment-1960271216
export type Client = ReturnType<typeof getPrismaClient> extends new () => infer T ? T : never
>Client : Symbol(Client, Decl(circularConstructorWithReturn.ts, 0, 0))
>ReturnType : Symbol(ReturnType, Decl(lib.es5.d.ts, --, --))
>getPrismaClient : Symbol(getPrismaClient, Decl(circularConstructorWithReturn.ts, 2, 93))
>T : Symbol(T, Decl(circularConstructorWithReturn.ts, 2, 79))
>T : Symbol(T, Decl(circularConstructorWithReturn.ts, 2, 79))
export function getPrismaClient(options?: any) {
>getPrismaClient : Symbol(getPrismaClient, Decl(circularConstructorWithReturn.ts, 2, 93))
>options : Symbol(options, Decl(circularConstructorWithReturn.ts, 4, 32))
class PrismaClient {
>PrismaClient : Symbol(PrismaClient, Decl(circularConstructorWithReturn.ts, 4, 48))
self: Client;
>self : Symbol(PrismaClient.self, Decl(circularConstructorWithReturn.ts, 5, 22))
>Client : Symbol(Client, Decl(circularConstructorWithReturn.ts, 0, 0))
constructor(options?: any) {
>options : Symbol(options, Decl(circularConstructorWithReturn.ts, 7, 16))
return (this.self = applyModelsAndClientExtensions(this));
>this.self : Symbol(PrismaClient.self, Decl(circularConstructorWithReturn.ts, 5, 22))
>this : Symbol(PrismaClient, Decl(circularConstructorWithReturn.ts, 4, 48))
>self : Symbol(PrismaClient.self, Decl(circularConstructorWithReturn.ts, 5, 22))
>applyModelsAndClientExtensions : Symbol(applyModelsAndClientExtensions, Decl(circularConstructorWithReturn.ts, 13, 1))
>this : Symbol(PrismaClient, Decl(circularConstructorWithReturn.ts, 4, 48))
}
}
return PrismaClient
>PrismaClient : Symbol(PrismaClient, Decl(circularConstructorWithReturn.ts, 4, 48))
}
export function applyModelsAndClientExtensions(client: Client) {
>applyModelsAndClientExtensions : Symbol(applyModelsAndClientExtensions, Decl(circularConstructorWithReturn.ts, 13, 1))
>client : Symbol(client, Decl(circularConstructorWithReturn.ts, 15, 47))
>Client : Symbol(Client, Decl(circularConstructorWithReturn.ts, 0, 0))
return client;
>client : Symbol(client, Decl(circularConstructorWithReturn.ts, 15, 47))
}

View File

@ -0,0 +1,46 @@
//// [tests/cases/compiler/circularConstructorWithReturn.ts] ////
=== circularConstructorWithReturn.ts ===
// This should not be a circularity error. See
// https://github.com/microsoft/TypeScript/pull/57465#issuecomment-1960271216
export type Client = ReturnType<typeof getPrismaClient> extends new () => infer T ? T : never
>Client : PrismaClient
>getPrismaClient : (options?: any) => typeof PrismaClient
export function getPrismaClient(options?: any) {
>getPrismaClient : (options?: any) => typeof PrismaClient
>options : any
class PrismaClient {
>PrismaClient : PrismaClient
self: Client;
>self : PrismaClient
constructor(options?: any) {
>options : any
return (this.self = applyModelsAndClientExtensions(this));
>(this.self = applyModelsAndClientExtensions(this)) : PrismaClient
>this.self = applyModelsAndClientExtensions(this) : PrismaClient
>this.self : PrismaClient
>this : this
>self : PrismaClient
>applyModelsAndClientExtensions(this) : PrismaClient
>applyModelsAndClientExtensions : (client: PrismaClient) => PrismaClient
>this : this
}
}
return PrismaClient
>PrismaClient : typeof PrismaClient
}
export function applyModelsAndClientExtensions(client: Client) {
>applyModelsAndClientExtensions : (client: Client) => PrismaClient
>client : PrismaClient
return client;
>client : PrismaClient
}

View File

@ -3,12 +3,12 @@
=== findLast.ts ===
const itemNumber: number | undefined = [0].findLast((item) => item === 0);
>itemNumber : number
>[0].findLast((item) => item === 0) : number
>[0].findLast((item) => item === 0) : 0
>[0].findLast : { <S extends number>(predicate: (value: number, index: number, array: number[]) => value is S, thisArg?: any): S; (predicate: (value: number, index: number, array: number[]) => unknown, thisArg?: any): number; }
>[0] : number[]
>0 : 0
>findLast : { <S extends number>(predicate: (value: number, index: number, array: number[]) => value is S, thisArg?: any): S; (predicate: (value: number, index: number, array: number[]) => unknown, thisArg?: any): number; }
>(item) => item === 0 : (item: number) => boolean
>(item) => item === 0 : (item: number) => item is 0
>item : number
>item === 0 : boolean
>item : number
@ -16,120 +16,120 @@ const itemNumber: number | undefined = [0].findLast((item) => item === 0);
const itemString: string | undefined = ["string"].findLast((item) => item === "string");
>itemString : string
>["string"].findLast((item) => item === "string") : string
>["string"].findLast((item) => item === "string") : "string"
>["string"].findLast : { <S extends string>(predicate: (value: string, index: number, array: string[]) => value is S, thisArg?: any): S; (predicate: (value: string, index: number, array: string[]) => unknown, thisArg?: any): string; }
>["string"] : string[]
>"string" : "string"
>findLast : { <S extends string>(predicate: (value: string, index: number, array: string[]) => value is S, thisArg?: any): S; (predicate: (value: string, index: number, array: string[]) => unknown, thisArg?: any): string; }
>(item) => item === "string" : (item: string) => boolean
>(item) => item === "string" : (item: string) => item is "string"
>item : string
>item === "string" : boolean
>item : string
>"string" : "string"
new Int8Array().findLast((item) => item === 0);
>new Int8Array().findLast((item) => item === 0) : number
>new Int8Array().findLast((item) => item === 0) : 0
>new Int8Array().findLast : { <S extends number>(predicate: (value: number, index: number, array: Int8Array) => value is S, thisArg?: any): S; (predicate: (value: number, index: number, array: Int8Array) => unknown, thisArg?: any): number; }
>new Int8Array() : Int8Array
>Int8Array : Int8ArrayConstructor
>findLast : { <S extends number>(predicate: (value: number, index: number, array: Int8Array) => value is S, thisArg?: any): S; (predicate: (value: number, index: number, array: Int8Array) => unknown, thisArg?: any): number; }
>(item) => item === 0 : (item: number) => boolean
>(item) => item === 0 : (item: number) => item is 0
>item : number
>item === 0 : boolean
>item : number
>0 : 0
new Uint8Array().findLast((item) => item === 0);
>new Uint8Array().findLast((item) => item === 0) : number
>new Uint8Array().findLast((item) => item === 0) : 0
>new Uint8Array().findLast : { <S extends number>(predicate: (value: number, index: number, array: Uint8Array) => value is S, thisArg?: any): S; (predicate: (value: number, index: number, array: Uint8Array) => unknown, thisArg?: any): number; }
>new Uint8Array() : Uint8Array
>Uint8Array : Uint8ArrayConstructor
>findLast : { <S extends number>(predicate: (value: number, index: number, array: Uint8Array) => value is S, thisArg?: any): S; (predicate: (value: number, index: number, array: Uint8Array) => unknown, thisArg?: any): number; }
>(item) => item === 0 : (item: number) => boolean
>(item) => item === 0 : (item: number) => item is 0
>item : number
>item === 0 : boolean
>item : number
>0 : 0
new Uint8ClampedArray().findLast((item) => item === 0);
>new Uint8ClampedArray().findLast((item) => item === 0) : number
>new Uint8ClampedArray().findLast((item) => item === 0) : 0
>new Uint8ClampedArray().findLast : { <S extends number>(predicate: (value: number, index: number, array: Uint8ClampedArray) => value is S, thisArg?: any): S; (predicate: (value: number, index: number, array: Uint8ClampedArray) => unknown, thisArg?: any): number; }
>new Uint8ClampedArray() : Uint8ClampedArray
>Uint8ClampedArray : Uint8ClampedArrayConstructor
>findLast : { <S extends number>(predicate: (value: number, index: number, array: Uint8ClampedArray) => value is S, thisArg?: any): S; (predicate: (value: number, index: number, array: Uint8ClampedArray) => unknown, thisArg?: any): number; }
>(item) => item === 0 : (item: number) => boolean
>(item) => item === 0 : (item: number) => item is 0
>item : number
>item === 0 : boolean
>item : number
>0 : 0
new Int16Array().findLast((item) => item === 0);
>new Int16Array().findLast((item) => item === 0) : number
>new Int16Array().findLast((item) => item === 0) : 0
>new Int16Array().findLast : { <S extends number>(predicate: (value: number, index: number, array: Int16Array) => value is S, thisArg?: any): S; (predicate: (value: number, index: number, array: Int16Array) => unknown, thisArg?: any): number; }
>new Int16Array() : Int16Array
>Int16Array : Int16ArrayConstructor
>findLast : { <S extends number>(predicate: (value: number, index: number, array: Int16Array) => value is S, thisArg?: any): S; (predicate: (value: number, index: number, array: Int16Array) => unknown, thisArg?: any): number; }
>(item) => item === 0 : (item: number) => boolean
>(item) => item === 0 : (item: number) => item is 0
>item : number
>item === 0 : boolean
>item : number
>0 : 0
new Uint16Array().findLast((item) => item === 0);
>new Uint16Array().findLast((item) => item === 0) : number
>new Uint16Array().findLast((item) => item === 0) : 0
>new Uint16Array().findLast : { <S extends number>(predicate: (value: number, index: number, array: Uint16Array) => value is S, thisArg?: any): S; (predicate: (value: number, index: number, array: Uint16Array) => unknown, thisArg?: any): number; }
>new Uint16Array() : Uint16Array
>Uint16Array : Uint16ArrayConstructor
>findLast : { <S extends number>(predicate: (value: number, index: number, array: Uint16Array) => value is S, thisArg?: any): S; (predicate: (value: number, index: number, array: Uint16Array) => unknown, thisArg?: any): number; }
>(item) => item === 0 : (item: number) => boolean
>(item) => item === 0 : (item: number) => item is 0
>item : number
>item === 0 : boolean
>item : number
>0 : 0
new Int32Array().findLast((item) => item === 0);
>new Int32Array().findLast((item) => item === 0) : number
>new Int32Array().findLast((item) => item === 0) : 0
>new Int32Array().findLast : { <S extends number>(predicate: (value: number, index: number, array: Int32Array) => value is S, thisArg?: any): S; (predicate: (value: number, index: number, array: Int32Array) => unknown, thisArg?: any): number; }
>new Int32Array() : Int32Array
>Int32Array : Int32ArrayConstructor
>findLast : { <S extends number>(predicate: (value: number, index: number, array: Int32Array) => value is S, thisArg?: any): S; (predicate: (value: number, index: number, array: Int32Array) => unknown, thisArg?: any): number; }
>(item) => item === 0 : (item: number) => boolean
>(item) => item === 0 : (item: number) => item is 0
>item : number
>item === 0 : boolean
>item : number
>0 : 0
new Uint32Array().findLast((item) => item === 0);
>new Uint32Array().findLast((item) => item === 0) : number
>new Uint32Array().findLast((item) => item === 0) : 0
>new Uint32Array().findLast : { <S extends number>(predicate: (value: number, index: number, array: Uint32Array) => value is S, thisArg?: any): S; (predicate: (value: number, index: number, array: Uint32Array) => unknown, thisArg?: any): number; }
>new Uint32Array() : Uint32Array
>Uint32Array : Uint32ArrayConstructor
>findLast : { <S extends number>(predicate: (value: number, index: number, array: Uint32Array) => value is S, thisArg?: any): S; (predicate: (value: number, index: number, array: Uint32Array) => unknown, thisArg?: any): number; }
>(item) => item === 0 : (item: number) => boolean
>(item) => item === 0 : (item: number) => item is 0
>item : number
>item === 0 : boolean
>item : number
>0 : 0
new Float32Array().findLast((item) => item === 0);
>new Float32Array().findLast((item) => item === 0) : number
>new Float32Array().findLast((item) => item === 0) : 0
>new Float32Array().findLast : { <S extends number>(predicate: (value: number, index: number, array: Float32Array) => value is S, thisArg?: any): S; (predicate: (value: number, index: number, array: Float32Array) => unknown, thisArg?: any): number; }
>new Float32Array() : Float32Array
>Float32Array : Float32ArrayConstructor
>findLast : { <S extends number>(predicate: (value: number, index: number, array: Float32Array) => value is S, thisArg?: any): S; (predicate: (value: number, index: number, array: Float32Array) => unknown, thisArg?: any): number; }
>(item) => item === 0 : (item: number) => boolean
>(item) => item === 0 : (item: number) => item is 0
>item : number
>item === 0 : boolean
>item : number
>0 : 0
new Float64Array().findLast((item) => item === 0);
>new Float64Array().findLast((item) => item === 0) : number
>new Float64Array().findLast((item) => item === 0) : 0
>new Float64Array().findLast : { <S extends number>(predicate: (value: number, index: number, array: Float64Array) => value is S, thisArg?: any): S; (predicate: (value: number, index: number, array: Float64Array) => unknown, thisArg?: any): number; }
>new Float64Array() : Float64Array
>Float64Array : Float64ArrayConstructor
>findLast : { <S extends number>(predicate: (value: number, index: number, array: Float64Array) => value is S, thisArg?: any): S; (predicate: (value: number, index: number, array: Float64Array) => unknown, thisArg?: any): number; }
>(item) => item === 0 : (item: number) => boolean
>(item) => item === 0 : (item: number) => item is 0
>item : number
>item === 0 : boolean
>item : number
@ -170,7 +170,7 @@ const indexNumber: number = [0].findLastIndex((item) => item === 0);
>[0] : number[]
>0 : 0
>findLastIndex : (predicate: (value: number, index: number, array: number[]) => unknown, thisArg?: any) => number
>(item) => item === 0 : (item: number) => boolean
>(item) => item === 0 : (item: number) => item is 0
>item : number
>item === 0 : boolean
>item : number
@ -183,7 +183,7 @@ const indexString: number = ["string"].findLastIndex((item) => item === "string"
>["string"] : string[]
>"string" : "string"
>findLastIndex : (predicate: (value: string, index: number, array: string[]) => unknown, thisArg?: any) => number
>(item) => item === "string" : (item: string) => boolean
>(item) => item === "string" : (item: string) => item is "string"
>item : string
>item === "string" : boolean
>item : string
@ -195,7 +195,7 @@ new Int8Array().findLastIndex((item) => item === 0);
>new Int8Array() : Int8Array
>Int8Array : Int8ArrayConstructor
>findLastIndex : (predicate: (value: number, index: number, array: Int8Array) => unknown, thisArg?: any) => number
>(item) => item === 0 : (item: number) => boolean
>(item) => item === 0 : (item: number) => item is 0
>item : number
>item === 0 : boolean
>item : number
@ -207,7 +207,7 @@ new Uint8Array().findLastIndex((item) => item === 0);
>new Uint8Array() : Uint8Array
>Uint8Array : Uint8ArrayConstructor
>findLastIndex : (predicate: (value: number, index: number, array: Uint8Array) => unknown, thisArg?: any) => number
>(item) => item === 0 : (item: number) => boolean
>(item) => item === 0 : (item: number) => item is 0
>item : number
>item === 0 : boolean
>item : number
@ -219,7 +219,7 @@ new Uint8ClampedArray().findLastIndex((item) => item === 0);
>new Uint8ClampedArray() : Uint8ClampedArray
>Uint8ClampedArray : Uint8ClampedArrayConstructor
>findLastIndex : (predicate: (value: number, index: number, array: Uint8ClampedArray) => unknown, thisArg?: any) => number
>(item) => item === 0 : (item: number) => boolean
>(item) => item === 0 : (item: number) => item is 0
>item : number
>item === 0 : boolean
>item : number
@ -231,7 +231,7 @@ new Int16Array().findLastIndex((item) => item === 0);
>new Int16Array() : Int16Array
>Int16Array : Int16ArrayConstructor
>findLastIndex : (predicate: (value: number, index: number, array: Int16Array) => unknown, thisArg?: any) => number
>(item) => item === 0 : (item: number) => boolean
>(item) => item === 0 : (item: number) => item is 0
>item : number
>item === 0 : boolean
>item : number
@ -243,7 +243,7 @@ new Uint16Array().findLastIndex((item) => item === 0);
>new Uint16Array() : Uint16Array
>Uint16Array : Uint16ArrayConstructor
>findLastIndex : (predicate: (value: number, index: number, array: Uint16Array) => unknown, thisArg?: any) => number
>(item) => item === 0 : (item: number) => boolean
>(item) => item === 0 : (item: number) => item is 0
>item : number
>item === 0 : boolean
>item : number
@ -255,7 +255,7 @@ new Int32Array().findLastIndex((item) => item === 0);
>new Int32Array() : Int32Array
>Int32Array : Int32ArrayConstructor
>findLastIndex : (predicate: (value: number, index: number, array: Int32Array) => unknown, thisArg?: any) => number
>(item) => item === 0 : (item: number) => boolean
>(item) => item === 0 : (item: number) => item is 0
>item : number
>item === 0 : boolean
>item : number
@ -267,7 +267,7 @@ new Uint32Array().findLastIndex((item) => item === 0);
>new Uint32Array() : Uint32Array
>Uint32Array : Uint32ArrayConstructor
>findLastIndex : (predicate: (value: number, index: number, array: Uint32Array) => unknown, thisArg?: any) => number
>(item) => item === 0 : (item: number) => boolean
>(item) => item === 0 : (item: number) => item is 0
>item : number
>item === 0 : boolean
>item : number
@ -279,7 +279,7 @@ new Float32Array().findLastIndex((item) => item === 0);
>new Float32Array() : Float32Array
>Float32Array : Float32ArrayConstructor
>findLastIndex : (predicate: (value: number, index: number, array: Float32Array) => unknown, thisArg?: any) => number
>(item) => item === 0 : (item: number) => boolean
>(item) => item === 0 : (item: number) => item is 0
>item : number
>item === 0 : boolean
>item : number
@ -291,7 +291,7 @@ new Float64Array().findLastIndex((item) => item === 0);
>new Float64Array() : Float64Array
>Float64Array : Float64ArrayConstructor
>findLastIndex : (predicate: (value: number, index: number, array: Float64Array) => unknown, thisArg?: any) => number
>(item) => item === 0 : (item: number) => boolean
>(item) => item === 0 : (item: number) => item is 0
>item : number
>item === 0 : boolean
>item : number

View File

@ -22,7 +22,7 @@ declare var dec: any;
>this : this
g(u) { return #x in u; }
>g : (u: any) => boolean
>g : (u: any) => u is A
>u : any
>#x in u : boolean
>#x : any

View File

@ -79,7 +79,7 @@ function f1(x: unknown) {
}
function f2<T>(x: T) {
>f2 : <T>(x: T) => boolean
>f2 : <T>(x: T) => x is T & Object & Record<"a", unknown>
>x : T
return x && x instanceof Object && 'a' in x;
@ -95,7 +95,7 @@ function f2<T>(x: T) {
}
function f3(x: {}) {
>f3 : (x: {}) => boolean
>f3 : (x: {}) => x is Object & Record<"a", unknown>
>x : {}
return x instanceof Object && 'a' in x;
@ -109,7 +109,7 @@ function f3(x: {}) {
}
function f4<T extends {}>(x: T) {
>f4 : <T extends {}>(x: T) => boolean
>f4 : <T extends {}>(x: T) => x is T & Object & Record<"a", unknown>
>x : T
return x instanceof Object && 'a' in x;
@ -123,7 +123,7 @@ function f4<T extends {}>(x: T) {
}
function f5<T>(x: T & {}) {
>f5 : <T>(x: T & {}) => boolean
>f5 : <T>(x: T & {}) => x is T & Object & Record<"a", unknown>
>x : T & {}
return x instanceof Object && 'a' in x;
@ -137,7 +137,7 @@ function f5<T>(x: T & {}) {
}
function f6<T extends {}>(x: T & {}) {
>f6 : <T extends {}>(x: T & {}) => boolean
>f6 : <T extends {}>(x: T & {}) => x is T & Object & Record<"a", unknown>
>x : T
return x instanceof Object && 'a' in x;
@ -151,7 +151,7 @@ function f6<T extends {}>(x: T & {}) {
}
function f7<T extends object>(x: T & {}) {
>f7 : <T extends object>(x: T & {}) => boolean
>f7 : <T extends object>(x: T & {}) => x is T & Record<"a", unknown>
>x : T
return x instanceof Object && 'a' in x;

View File

@ -0,0 +1,318 @@
inferTypePredicates.ts(4,7): error TS2322: Type '(number | null)[]' is not assignable to type 'number[]'.
Type 'number | null' is not assignable to type 'number'.
Type 'null' is not assignable to type 'number'.
inferTypePredicates.ts(7,7): error TS2322: Type '(number | null)[]' is not assignable to type 'number[]'.
inferTypePredicates.ts(14,7): error TS2322: Type '(number | null)[]' is not assignable to type 'number[]'.
inferTypePredicates.ts(52,17): error TS18048: 'arr' is possibly 'undefined'.
inferTypePredicates.ts(54,28): error TS2345: Argument of type 'string | undefined' is not assignable to parameter of type 'string'.
Type 'undefined' is not assignable to type 'string'.
inferTypePredicates.ts(65,28): error TS2345: Argument of type 'string | undefined' is not assignable to parameter of type 'string'.
Type 'undefined' is not assignable to type 'string'.
inferTypePredicates.ts(90,8): error TS2345: Argument of type 'number' is not assignable to parameter of type 'string'.
inferTypePredicates.ts(113,7): error TS2322: Type 'string | number' is not assignable to type 'string'.
Type 'number' is not assignable to type 'string'.
inferTypePredicates.ts(115,7): error TS2322: Type 'string | number' is not assignable to type 'number'.
Type 'string' is not assignable to type 'number'.
inferTypePredicates.ts(133,7): error TS2740: Type '{}' is missing the following properties from type 'Date': toDateString, toTimeString, toLocaleDateString, toLocaleTimeString, and 37 more.
inferTypePredicates.ts(205,7): error TS2741: Property 'z' is missing in type 'C1' but required in type 'C2'.
==== inferTypePredicates.ts (11 errors) ====
// https://github.com/microsoft/TypeScript/issues/16069
const numsOrNull = [1, 2, 3, 4, null];
const filteredNumsTruthy: number[] = numsOrNull.filter(x => !!x); // should error
~~~~~~~~~~~~~~~~~~
!!! error TS2322: Type '(number | null)[]' is not assignable to type 'number[]'.
!!! error TS2322: Type 'number | null' is not assignable to type 'number'.
!!! error TS2322: Type 'null' is not assignable to type 'number'.
const filteredNumsNonNullish: number[] = numsOrNull.filter(x => x !== null); // should ok
const evenSquaresInline: number[] = // should error
~~~~~~~~~~~~~~~~~
!!! error TS2322: Type '(number | null)[]' is not assignable to type 'number[]'.
[1, 2, 3, 4]
.map(x => x % 2 === 0 ? x * x : null)
.filter(x => !!x); // tests truthiness, not non-nullishness
const isTruthy = (x: number | null) => !!x;
const evenSquares: number[] = // should error
~~~~~~~~~~~
!!! error TS2322: Type '(number | null)[]' is not assignable to type 'number[]'.
[1, 2, 3, 4]
.map(x => x % 2 === 0 ? x * x : null)
.filter(isTruthy);
const evenSquaresNonNull: number[] = // should ok
[1, 2, 3, 4]
.map(x => x % 2 === 0 ? x * x : null)
.filter(x => x !== null);
function isNonNull(x: number | null) {
return x !== null;
}
// factoring out a boolean works thanks to aliased discriminants
function isNonNullVar(x: number | null) {
const ok = x !== null;
return ok;
}
function isNonNullGeneric<T>(x: T) {
return x !== null;
}
// Type guards can flow between functions
const myGuard = (o: string | undefined): o is string => !!o;
const mySecondGuard = (o: string | undefined) => myGuard(o);
// https://github.com/microsoft/TypeScript/issues/16069#issuecomment-1327449914
// This doesn't work because the false condition prevents type guard inference.
// Breaking up the filters does work.
type MyObj = { data?: string };
type MyArray = { list?: MyObj[] }[];
const myArray: MyArray = [];
const result = myArray
.map((arr) => arr.list)
.filter((arr) => arr && arr.length)
.map((arr) => arr // should error
~~~
!!! error TS18048: 'arr' is possibly 'undefined'.
.filter((obj) => obj && obj.data)
.map(obj => JSON.parse(obj.data)) // should error
~~~~~~~~
!!! error TS2345: Argument of type 'string | undefined' is not assignable to parameter of type 'string'.
!!! error TS2345: Type 'undefined' is not assignable to type 'string'.
);
const result2 = myArray
.map((arr) => arr.list)
.filter((arr) => !!arr)
.filter(arr => arr.length)
.map((arr) => arr // should ok
.filter((obj) => obj)
// inferring a guard here would require https://github.com/microsoft/TypeScript/issues/42384
.filter(obj => !!obj.data)
.map(obj => JSON.parse(obj.data))
~~~~~~~~
!!! error TS2345: Argument of type 'string | undefined' is not assignable to parameter of type 'string'.
!!! error TS2345: Type 'undefined' is not assignable to type 'string'.
);
// https://github.com/microsoft/TypeScript/issues/16069#issuecomment-1183547889
type Foo = {
foo: string;
}
type Bar = Foo & {
bar: string;
}
const list: (Foo | Bar)[] = [];
const resultBars: Bar[] = list.filter((value) => 'bar' in value); // should ok
function isBarNonNull(x: Foo | Bar | null) {
return ('bar' in x!);
}
const fooOrBar = list[0];
if (isBarNonNull(fooOrBar)) {
const t: Bar = fooOrBar; // should ok
}
// https://github.com/microsoft/TypeScript/issues/38390#issuecomment-626019466
// Ryan's example (currently legal):
const a = [1, "foo", 2, "bar"].filter(x => typeof x === "string");
a.push(10);
~~
!!! error TS2345: Argument of type 'number' is not assignable to parameter of type 'string'.
// Defer to explicit type guards, even when they're incorrect.
function backwardsGuard(x: number|string): x is number {
return typeof x === 'string';
}
// Partition tests. The "false" case matters.
function isString(x: string | number) {
return typeof x === 'string';
}
declare let strOrNum: string | number;
if (isString(strOrNum)) {
let t: string = strOrNum; // should ok
} else {
let t: number = strOrNum; // should ok
}
function flakyIsString(x: string | number) {
return typeof x === 'string' && Math.random() > 0.5;
}
if (flakyIsString(strOrNum)) {
let t: string = strOrNum; // should error
~
!!! error TS2322: Type 'string | number' is not assignable to type 'string'.
!!! error TS2322: Type 'number' is not assignable to type 'string'.
} else {
let t: number = strOrNum; // should error
~
!!! error TS2322: Type 'string | number' is not assignable to type 'number'.
!!! error TS2322: Type 'string' is not assignable to type 'number'.
}
function isDate(x: object) {
return x instanceof Date;
}
function flakyIsDate(x: object) {
return x instanceof Date && Math.random() > 0.5;
}
declare let maybeDate: object;
if (isDate(maybeDate)) {
let t: Date = maybeDate; // should ok
} else {
let t: object = maybeDate; // should ok
}
if (flakyIsDate(maybeDate)) {
let t: Date = maybeDate; // should error
~
!!! error TS2740: Type '{}' is missing the following properties from type 'Date': toDateString, toTimeString, toLocaleDateString, toLocaleTimeString, and 37 more.
} else {
let t: object = maybeDate; // should ok
}
// This should not infer a type guard since the value on which we do the refinement
// is not related to the original parameter.
function irrelevantIsNumber(x: string | number) {
x = Math.random() < 0.5 ? "string" : 123;
return typeof x === 'string';
}
function irrelevantIsNumberDestructuring(x: string | number) {
[x] = [Math.random() < 0.5 ? "string" : 123];
return typeof x === 'string';
}
// Cannot infer a type guard for either param because of the false case.
function areBothNums(x: string|number, y: string|number) {
return typeof x === 'number' && typeof y === 'number';
}
// Could potentially infer a type guard here but it would require more bookkeeping.
function doubleReturn(x: string|number) {
if (typeof x === 'string') {
return true;
}
return false;
}
function guardsOneButNotOthers(a: string|number, b: string|number, c: string|number) {
return typeof b === 'string';
}
// Checks that there are no string escaping issues
function dunderguard(__x: number | string) {
return typeof __x === 'string';
}
// could infer a type guard here but it doesn't seem that helpful.
const booleanIdentity = (x: boolean) => x;
// we infer "x is number | true" which is accurate but of debatable utility.
const numOrBoolean = (x: number | boolean) => typeof x === 'number' || x;
// inferred guards in methods
interface NumberInferrer {
isNumber(x: number | string): x is number;
}
class Inferrer implements NumberInferrer {
isNumber(x: number | string) { // should ok
return typeof x === 'number';
}
}
declare let numOrStr: number | string;
const inf = new Inferrer();
if (inf.isNumber(numOrStr)) {
let t: number = numOrStr; // should ok
} else {
let t: string = numOrStr; // should ok
}
// Type predicates are not inferred on "this"
class C1 {
isC2() {
return this instanceof C2;
}
}
class C2 extends C1 {
z = 0;
}
declare let c: C1;
if (c.isC2()) {
let c2: C2 = c; // should error
~~
!!! error TS2741: Property 'z' is missing in type 'C1' but required in type 'C2'.
!!! related TS2728 inferTypePredicates.ts:201:3: 'z' is declared here.
}
function doNotRefineDestructuredParam({x, y}: {x: number | null, y: number}) {
return typeof x === 'number';
}
// The type predicate must remain valid when the function is called with subtypes.
function isShortString(x: unknown) {
return typeof x === "string" && x.length < 10;
}
declare let str: string;
if (isShortString(str)) {
str.charAt(0); // should ok
} else {
str.charAt(0); // should ok
}
function isStringFromUnknown(x: unknown) {
return typeof x === "string";
}
if (isStringFromUnknown(str)) {
str.charAt(0); // should OK
} else {
let t: never = str; // should OK
}
// infer a union type
function isNumOrStr(x: unknown) {
return (typeof x === "number" || typeof x === "string");
}
declare let unk: unknown;
if (isNumOrStr(unk)) {
let t: number | string = unk; // should ok
}
// A function can be a type predicate even if it throws.
function assertAndPredicate(x: string | number | Date) {
if (x instanceof Date) {
throw new Error();
}
return typeof x === 'string';
}
declare let snd: string | number | Date;
if (assertAndPredicate(snd)) {
let t: string = snd; // should error
}
function isNumberWithThis(this: Date, x: number | string) {
return typeof x === 'number';
}
function narrowFromAny(x: any) {
return typeof x === 'number';
}
const noInferenceFromRest = (...f: ["a" | "b"]) => f[0] === "a";
const noInferenceFromImpossibleRest = (...f: []) => typeof f === "undefined";
function inferWithRest(x: string | null, ...f: ["a", "b"]) {
return typeof x === 'string';
}

View File

@ -0,0 +1,607 @@
//// [tests/cases/compiler/inferTypePredicates.ts] ////
//// [inferTypePredicates.ts]
// https://github.com/microsoft/TypeScript/issues/16069
const numsOrNull = [1, 2, 3, 4, null];
const filteredNumsTruthy: number[] = numsOrNull.filter(x => !!x); // should error
const filteredNumsNonNullish: number[] = numsOrNull.filter(x => x !== null); // should ok
const evenSquaresInline: number[] = // should error
[1, 2, 3, 4]
.map(x => x % 2 === 0 ? x * x : null)
.filter(x => !!x); // tests truthiness, not non-nullishness
const isTruthy = (x: number | null) => !!x;
const evenSquares: number[] = // should error
[1, 2, 3, 4]
.map(x => x % 2 === 0 ? x * x : null)
.filter(isTruthy);
const evenSquaresNonNull: number[] = // should ok
[1, 2, 3, 4]
.map(x => x % 2 === 0 ? x * x : null)
.filter(x => x !== null);
function isNonNull(x: number | null) {
return x !== null;
}
// factoring out a boolean works thanks to aliased discriminants
function isNonNullVar(x: number | null) {
const ok = x !== null;
return ok;
}
function isNonNullGeneric<T>(x: T) {
return x !== null;
}
// Type guards can flow between functions
const myGuard = (o: string | undefined): o is string => !!o;
const mySecondGuard = (o: string | undefined) => myGuard(o);
// https://github.com/microsoft/TypeScript/issues/16069#issuecomment-1327449914
// This doesn't work because the false condition prevents type guard inference.
// Breaking up the filters does work.
type MyObj = { data?: string };
type MyArray = { list?: MyObj[] }[];
const myArray: MyArray = [];
const result = myArray
.map((arr) => arr.list)
.filter((arr) => arr && arr.length)
.map((arr) => arr // should error
.filter((obj) => obj && obj.data)
.map(obj => JSON.parse(obj.data)) // should error
);
const result2 = myArray
.map((arr) => arr.list)
.filter((arr) => !!arr)
.filter(arr => arr.length)
.map((arr) => arr // should ok
.filter((obj) => obj)
// inferring a guard here would require https://github.com/microsoft/TypeScript/issues/42384
.filter(obj => !!obj.data)
.map(obj => JSON.parse(obj.data))
);
// https://github.com/microsoft/TypeScript/issues/16069#issuecomment-1183547889
type Foo = {
foo: string;
}
type Bar = Foo & {
bar: string;
}
const list: (Foo | Bar)[] = [];
const resultBars: Bar[] = list.filter((value) => 'bar' in value); // should ok
function isBarNonNull(x: Foo | Bar | null) {
return ('bar' in x!);
}
const fooOrBar = list[0];
if (isBarNonNull(fooOrBar)) {
const t: Bar = fooOrBar; // should ok
}
// https://github.com/microsoft/TypeScript/issues/38390#issuecomment-626019466
// Ryan's example (currently legal):
const a = [1, "foo", 2, "bar"].filter(x => typeof x === "string");
a.push(10);
// Defer to explicit type guards, even when they're incorrect.
function backwardsGuard(x: number|string): x is number {
return typeof x === 'string';
}
// Partition tests. The "false" case matters.
function isString(x: string | number) {
return typeof x === 'string';
}
declare let strOrNum: string | number;
if (isString(strOrNum)) {
let t: string = strOrNum; // should ok
} else {
let t: number = strOrNum; // should ok
}
function flakyIsString(x: string | number) {
return typeof x === 'string' && Math.random() > 0.5;
}
if (flakyIsString(strOrNum)) {
let t: string = strOrNum; // should error
} else {
let t: number = strOrNum; // should error
}
function isDate(x: object) {
return x instanceof Date;
}
function flakyIsDate(x: object) {
return x instanceof Date && Math.random() > 0.5;
}
declare let maybeDate: object;
if (isDate(maybeDate)) {
let t: Date = maybeDate; // should ok
} else {
let t: object = maybeDate; // should ok
}
if (flakyIsDate(maybeDate)) {
let t: Date = maybeDate; // should error
} else {
let t: object = maybeDate; // should ok
}
// This should not infer a type guard since the value on which we do the refinement
// is not related to the original parameter.
function irrelevantIsNumber(x: string | number) {
x = Math.random() < 0.5 ? "string" : 123;
return typeof x === 'string';
}
function irrelevantIsNumberDestructuring(x: string | number) {
[x] = [Math.random() < 0.5 ? "string" : 123];
return typeof x === 'string';
}
// Cannot infer a type guard for either param because of the false case.
function areBothNums(x: string|number, y: string|number) {
return typeof x === 'number' && typeof y === 'number';
}
// Could potentially infer a type guard here but it would require more bookkeeping.
function doubleReturn(x: string|number) {
if (typeof x === 'string') {
return true;
}
return false;
}
function guardsOneButNotOthers(a: string|number, b: string|number, c: string|number) {
return typeof b === 'string';
}
// Checks that there are no string escaping issues
function dunderguard(__x: number | string) {
return typeof __x === 'string';
}
// could infer a type guard here but it doesn't seem that helpful.
const booleanIdentity = (x: boolean) => x;
// we infer "x is number | true" which is accurate but of debatable utility.
const numOrBoolean = (x: number | boolean) => typeof x === 'number' || x;
// inferred guards in methods
interface NumberInferrer {
isNumber(x: number | string): x is number;
}
class Inferrer implements NumberInferrer {
isNumber(x: number | string) { // should ok
return typeof x === 'number';
}
}
declare let numOrStr: number | string;
const inf = new Inferrer();
if (inf.isNumber(numOrStr)) {
let t: number = numOrStr; // should ok
} else {
let t: string = numOrStr; // should ok
}
// Type predicates are not inferred on "this"
class C1 {
isC2() {
return this instanceof C2;
}
}
class C2 extends C1 {
z = 0;
}
declare let c: C1;
if (c.isC2()) {
let c2: C2 = c; // should error
}
function doNotRefineDestructuredParam({x, y}: {x: number | null, y: number}) {
return typeof x === 'number';
}
// The type predicate must remain valid when the function is called with subtypes.
function isShortString(x: unknown) {
return typeof x === "string" && x.length < 10;
}
declare let str: string;
if (isShortString(str)) {
str.charAt(0); // should ok
} else {
str.charAt(0); // should ok
}
function isStringFromUnknown(x: unknown) {
return typeof x === "string";
}
if (isStringFromUnknown(str)) {
str.charAt(0); // should OK
} else {
let t: never = str; // should OK
}
// infer a union type
function isNumOrStr(x: unknown) {
return (typeof x === "number" || typeof x === "string");
}
declare let unk: unknown;
if (isNumOrStr(unk)) {
let t: number | string = unk; // should ok
}
// A function can be a type predicate even if it throws.
function assertAndPredicate(x: string | number | Date) {
if (x instanceof Date) {
throw new Error();
}
return typeof x === 'string';
}
declare let snd: string | number | Date;
if (assertAndPredicate(snd)) {
let t: string = snd; // should error
}
function isNumberWithThis(this: Date, x: number | string) {
return typeof x === 'number';
}
function narrowFromAny(x: any) {
return typeof x === 'number';
}
const noInferenceFromRest = (...f: ["a" | "b"]) => f[0] === "a";
const noInferenceFromImpossibleRest = (...f: []) => typeof f === "undefined";
function inferWithRest(x: string | null, ...f: ["a", "b"]) {
return typeof x === 'string';
}
//// [inferTypePredicates.js]
// https://github.com/microsoft/TypeScript/issues/16069
var __extends = (this && this.__extends) || (function () {
var extendStatics = function (d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
return extendStatics(d, b);
};
return function (d, b) {
if (typeof b !== "function" && b !== null)
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
var numsOrNull = [1, 2, 3, 4, null];
var filteredNumsTruthy = numsOrNull.filter(function (x) { return !!x; }); // should error
var filteredNumsNonNullish = numsOrNull.filter(function (x) { return x !== null; }); // should ok
var evenSquaresInline = // should error
[1, 2, 3, 4]
.map(function (x) { return x % 2 === 0 ? x * x : null; })
.filter(function (x) { return !!x; }); // tests truthiness, not non-nullishness
var isTruthy = function (x) { return !!x; };
var evenSquares = // should error
[1, 2, 3, 4]
.map(function (x) { return x % 2 === 0 ? x * x : null; })
.filter(isTruthy);
var evenSquaresNonNull = // should ok
[1, 2, 3, 4]
.map(function (x) { return x % 2 === 0 ? x * x : null; })
.filter(function (x) { return x !== null; });
function isNonNull(x) {
return x !== null;
}
// factoring out a boolean works thanks to aliased discriminants
function isNonNullVar(x) {
var ok = x !== null;
return ok;
}
function isNonNullGeneric(x) {
return x !== null;
}
// Type guards can flow between functions
var myGuard = function (o) { return !!o; };
var mySecondGuard = function (o) { return myGuard(o); };
var myArray = [];
var result = myArray
.map(function (arr) { return arr.list; })
.filter(function (arr) { return arr && arr.length; })
.map(function (arr) { return arr // should error
.filter(function (obj) { return obj && obj.data; })
.map(function (obj) { return JSON.parse(obj.data); }); } // should error
);
var result2 = myArray
.map(function (arr) { return arr.list; })
.filter(function (arr) { return !!arr; })
.filter(function (arr) { return arr.length; })
.map(function (arr) { return arr // should ok
.filter(function (obj) { return obj; })
// inferring a guard here would require https://github.com/microsoft/TypeScript/issues/42384
.filter(function (obj) { return !!obj.data; })
.map(function (obj) { return JSON.parse(obj.data); }); });
var list = [];
var resultBars = list.filter(function (value) { return 'bar' in value; }); // should ok
function isBarNonNull(x) {
return ('bar' in x);
}
var fooOrBar = list[0];
if (isBarNonNull(fooOrBar)) {
var t = fooOrBar; // should ok
}
// https://github.com/microsoft/TypeScript/issues/38390#issuecomment-626019466
// Ryan's example (currently legal):
var a = [1, "foo", 2, "bar"].filter(function (x) { return typeof x === "string"; });
a.push(10);
// Defer to explicit type guards, even when they're incorrect.
function backwardsGuard(x) {
return typeof x === 'string';
}
// Partition tests. The "false" case matters.
function isString(x) {
return typeof x === 'string';
}
if (isString(strOrNum)) {
var t = strOrNum; // should ok
}
else {
var t = strOrNum; // should ok
}
function flakyIsString(x) {
return typeof x === 'string' && Math.random() > 0.5;
}
if (flakyIsString(strOrNum)) {
var t = strOrNum; // should error
}
else {
var t = strOrNum; // should error
}
function isDate(x) {
return x instanceof Date;
}
function flakyIsDate(x) {
return x instanceof Date && Math.random() > 0.5;
}
if (isDate(maybeDate)) {
var t = maybeDate; // should ok
}
else {
var t = maybeDate; // should ok
}
if (flakyIsDate(maybeDate)) {
var t = maybeDate; // should error
}
else {
var t = maybeDate; // should ok
}
// This should not infer a type guard since the value on which we do the refinement
// is not related to the original parameter.
function irrelevantIsNumber(x) {
x = Math.random() < 0.5 ? "string" : 123;
return typeof x === 'string';
}
function irrelevantIsNumberDestructuring(x) {
x = [Math.random() < 0.5 ? "string" : 123][0];
return typeof x === 'string';
}
// Cannot infer a type guard for either param because of the false case.
function areBothNums(x, y) {
return typeof x === 'number' && typeof y === 'number';
}
// Could potentially infer a type guard here but it would require more bookkeeping.
function doubleReturn(x) {
if (typeof x === 'string') {
return true;
}
return false;
}
function guardsOneButNotOthers(a, b, c) {
return typeof b === 'string';
}
// Checks that there are no string escaping issues
function dunderguard(__x) {
return typeof __x === 'string';
}
// could infer a type guard here but it doesn't seem that helpful.
var booleanIdentity = function (x) { return x; };
// we infer "x is number | true" which is accurate but of debatable utility.
var numOrBoolean = function (x) { return typeof x === 'number' || x; };
var Inferrer = /** @class */ (function () {
function Inferrer() {
}
Inferrer.prototype.isNumber = function (x) {
return typeof x === 'number';
};
return Inferrer;
}());
var inf = new Inferrer();
if (inf.isNumber(numOrStr)) {
var t = numOrStr; // should ok
}
else {
var t = numOrStr; // should ok
}
// Type predicates are not inferred on "this"
var C1 = /** @class */ (function () {
function C1() {
}
C1.prototype.isC2 = function () {
return this instanceof C2;
};
return C1;
}());
var C2 = /** @class */ (function (_super) {
__extends(C2, _super);
function C2() {
var _this = _super !== null && _super.apply(this, arguments) || this;
_this.z = 0;
return _this;
}
return C2;
}(C1));
if (c.isC2()) {
var c2 = c; // should error
}
function doNotRefineDestructuredParam(_a) {
var x = _a.x, y = _a.y;
return typeof x === 'number';
}
// The type predicate must remain valid when the function is called with subtypes.
function isShortString(x) {
return typeof x === "string" && x.length < 10;
}
if (isShortString(str)) {
str.charAt(0); // should ok
}
else {
str.charAt(0); // should ok
}
function isStringFromUnknown(x) {
return typeof x === "string";
}
if (isStringFromUnknown(str)) {
str.charAt(0); // should OK
}
else {
var t = str; // should OK
}
// infer a union type
function isNumOrStr(x) {
return (typeof x === "number" || typeof x === "string");
}
if (isNumOrStr(unk)) {
var t = unk; // should ok
}
// A function can be a type predicate even if it throws.
function assertAndPredicate(x) {
if (x instanceof Date) {
throw new Error();
}
return typeof x === 'string';
}
if (assertAndPredicate(snd)) {
var t = snd; // should error
}
function isNumberWithThis(x) {
return typeof x === 'number';
}
function narrowFromAny(x) {
return typeof x === 'number';
}
var noInferenceFromRest = function () {
var f = [];
for (var _i = 0; _i < arguments.length; _i++) {
f[_i] = arguments[_i];
}
return f[0] === "a";
};
var noInferenceFromImpossibleRest = function () {
var f = [];
for (var _i = 0; _i < arguments.length; _i++) {
f[_i] = arguments[_i];
}
return typeof f === "undefined";
};
function inferWithRest(x) {
var f = [];
for (var _i = 1; _i < arguments.length; _i++) {
f[_i - 1] = arguments[_i];
}
return typeof x === 'string';
}
//// [inferTypePredicates.d.ts]
declare const numsOrNull: (number | null)[];
declare const filteredNumsTruthy: number[];
declare const filteredNumsNonNullish: number[];
declare const evenSquaresInline: number[];
declare const isTruthy: (x: number | null) => boolean;
declare const evenSquares: number[];
declare const evenSquaresNonNull: number[];
declare function isNonNull(x: number | null): x is number;
declare function isNonNullVar(x: number | null): x is number;
declare function isNonNullGeneric<T>(x: T): x is T & ({} | undefined);
declare const myGuard: (o: string | undefined) => o is string;
declare const mySecondGuard: (o: string | undefined) => o is string;
type MyObj = {
data?: string;
};
type MyArray = {
list?: MyObj[];
}[];
declare const myArray: MyArray;
declare const result: any[][];
declare const result2: any[][];
type Foo = {
foo: string;
};
type Bar = Foo & {
bar: string;
};
declare const list: (Foo | Bar)[];
declare const resultBars: Bar[];
declare function isBarNonNull(x: Foo | Bar | null): x is Bar;
declare const fooOrBar: Foo | Bar;
declare const a: string[];
declare function backwardsGuard(x: number | string): x is number;
declare function isString(x: string | number): x is string;
declare let strOrNum: string | number;
declare function flakyIsString(x: string | number): boolean;
declare function isDate(x: object): x is Date;
declare function flakyIsDate(x: object): boolean;
declare let maybeDate: object;
declare function irrelevantIsNumber(x: string | number): boolean;
declare function irrelevantIsNumberDestructuring(x: string | number): boolean;
declare function areBothNums(x: string | number, y: string | number): boolean;
declare function doubleReturn(x: string | number): boolean;
declare function guardsOneButNotOthers(a: string | number, b: string | number, c: string | number): b is string;
declare function dunderguard(__x: number | string): __x is string;
declare const booleanIdentity: (x: boolean) => boolean;
declare const numOrBoolean: (x: number | boolean) => x is number | true;
interface NumberInferrer {
isNumber(x: number | string): x is number;
}
declare class Inferrer implements NumberInferrer {
isNumber(x: number | string): x is number;
}
declare let numOrStr: number | string;
declare const inf: Inferrer;
declare class C1 {
isC2(): boolean;
}
declare class C2 extends C1 {
z: number;
}
declare let c: C1;
declare function doNotRefineDestructuredParam({ x, y }: {
x: number | null;
y: number;
}): boolean;
declare function isShortString(x: unknown): boolean;
declare let str: string;
declare function isStringFromUnknown(x: unknown): x is string;
declare function isNumOrStr(x: unknown): x is string | number;
declare let unk: unknown;
declare function assertAndPredicate(x: string | number | Date): x is string;
declare let snd: string | number | Date;
declare function isNumberWithThis(this: Date, x: number | string): x is number;
declare function narrowFromAny(x: any): x is number;
declare const noInferenceFromRest: (f_0: "a" | "b") => boolean;
declare const noInferenceFromImpossibleRest: () => boolean;
declare function inferWithRest(x: string | null, ...f: ["a", "b"]): x is string;

View File

@ -0,0 +1,749 @@
//// [tests/cases/compiler/inferTypePredicates.ts] ////
=== inferTypePredicates.ts ===
// https://github.com/microsoft/TypeScript/issues/16069
const numsOrNull = [1, 2, 3, 4, null];
>numsOrNull : Symbol(numsOrNull, Decl(inferTypePredicates.ts, 2, 5))
const filteredNumsTruthy: number[] = numsOrNull.filter(x => !!x); // should error
>filteredNumsTruthy : Symbol(filteredNumsTruthy, Decl(inferTypePredicates.ts, 3, 5))
>numsOrNull.filter : Symbol(Array.filter, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>numsOrNull : Symbol(numsOrNull, Decl(inferTypePredicates.ts, 2, 5))
>filter : Symbol(Array.filter, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>x : Symbol(x, Decl(inferTypePredicates.ts, 3, 55))
>x : Symbol(x, Decl(inferTypePredicates.ts, 3, 55))
const filteredNumsNonNullish: number[] = numsOrNull.filter(x => x !== null); // should ok
>filteredNumsNonNullish : Symbol(filteredNumsNonNullish, Decl(inferTypePredicates.ts, 4, 5))
>numsOrNull.filter : Symbol(Array.filter, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>numsOrNull : Symbol(numsOrNull, Decl(inferTypePredicates.ts, 2, 5))
>filter : Symbol(Array.filter, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>x : Symbol(x, Decl(inferTypePredicates.ts, 4, 59))
>x : Symbol(x, Decl(inferTypePredicates.ts, 4, 59))
const evenSquaresInline: number[] = // should error
>evenSquaresInline : Symbol(evenSquaresInline, Decl(inferTypePredicates.ts, 6, 5))
[1, 2, 3, 4]
>[1, 2, 3, 4] .map(x => x % 2 === 0 ? x * x : null) .filter : Symbol(Array.filter, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>[1, 2, 3, 4] .map : Symbol(Array.map, Decl(lib.es5.d.ts, --, --))
.map(x => x % 2 === 0 ? x * x : null)
>map : Symbol(Array.map, Decl(lib.es5.d.ts, --, --))
>x : Symbol(x, Decl(inferTypePredicates.ts, 8, 13))
>x : Symbol(x, Decl(inferTypePredicates.ts, 8, 13))
>x : Symbol(x, Decl(inferTypePredicates.ts, 8, 13))
>x : Symbol(x, Decl(inferTypePredicates.ts, 8, 13))
.filter(x => !!x); // tests truthiness, not non-nullishness
>filter : Symbol(Array.filter, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>x : Symbol(x, Decl(inferTypePredicates.ts, 9, 16))
>x : Symbol(x, Decl(inferTypePredicates.ts, 9, 16))
const isTruthy = (x: number | null) => !!x;
>isTruthy : Symbol(isTruthy, Decl(inferTypePredicates.ts, 11, 5))
>x : Symbol(x, Decl(inferTypePredicates.ts, 11, 18))
>x : Symbol(x, Decl(inferTypePredicates.ts, 11, 18))
const evenSquares: number[] = // should error
>evenSquares : Symbol(evenSquares, Decl(inferTypePredicates.ts, 13, 5))
[1, 2, 3, 4]
>[1, 2, 3, 4] .map(x => x % 2 === 0 ? x * x : null) .filter : Symbol(Array.filter, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>[1, 2, 3, 4] .map : Symbol(Array.map, Decl(lib.es5.d.ts, --, --))
.map(x => x % 2 === 0 ? x * x : null)
>map : Symbol(Array.map, Decl(lib.es5.d.ts, --, --))
>x : Symbol(x, Decl(inferTypePredicates.ts, 15, 9))
>x : Symbol(x, Decl(inferTypePredicates.ts, 15, 9))
>x : Symbol(x, Decl(inferTypePredicates.ts, 15, 9))
>x : Symbol(x, Decl(inferTypePredicates.ts, 15, 9))
.filter(isTruthy);
>filter : Symbol(Array.filter, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>isTruthy : Symbol(isTruthy, Decl(inferTypePredicates.ts, 11, 5))
const evenSquaresNonNull: number[] = // should ok
>evenSquaresNonNull : Symbol(evenSquaresNonNull, Decl(inferTypePredicates.ts, 18, 5))
[1, 2, 3, 4]
>[1, 2, 3, 4] .map(x => x % 2 === 0 ? x * x : null) .filter : Symbol(Array.filter, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>[1, 2, 3, 4] .map : Symbol(Array.map, Decl(lib.es5.d.ts, --, --))
.map(x => x % 2 === 0 ? x * x : null)
>map : Symbol(Array.map, Decl(lib.es5.d.ts, --, --))
>x : Symbol(x, Decl(inferTypePredicates.ts, 20, 9))
>x : Symbol(x, Decl(inferTypePredicates.ts, 20, 9))
>x : Symbol(x, Decl(inferTypePredicates.ts, 20, 9))
>x : Symbol(x, Decl(inferTypePredicates.ts, 20, 9))
.filter(x => x !== null);
>filter : Symbol(Array.filter, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>x : Symbol(x, Decl(inferTypePredicates.ts, 21, 12))
>x : Symbol(x, Decl(inferTypePredicates.ts, 21, 12))
function isNonNull(x: number | null) {
>isNonNull : Symbol(isNonNull, Decl(inferTypePredicates.ts, 21, 29))
>x : Symbol(x, Decl(inferTypePredicates.ts, 23, 19))
return x !== null;
>x : Symbol(x, Decl(inferTypePredicates.ts, 23, 19))
}
// factoring out a boolean works thanks to aliased discriminants
function isNonNullVar(x: number | null) {
>isNonNullVar : Symbol(isNonNullVar, Decl(inferTypePredicates.ts, 25, 1))
>x : Symbol(x, Decl(inferTypePredicates.ts, 28, 22))
const ok = x !== null;
>ok : Symbol(ok, Decl(inferTypePredicates.ts, 29, 7))
>x : Symbol(x, Decl(inferTypePredicates.ts, 28, 22))
return ok;
>ok : Symbol(ok, Decl(inferTypePredicates.ts, 29, 7))
}
function isNonNullGeneric<T>(x: T) {
>isNonNullGeneric : Symbol(isNonNullGeneric, Decl(inferTypePredicates.ts, 31, 1))
>T : Symbol(T, Decl(inferTypePredicates.ts, 33, 26))
>x : Symbol(x, Decl(inferTypePredicates.ts, 33, 29))
>T : Symbol(T, Decl(inferTypePredicates.ts, 33, 26))
return x !== null;
>x : Symbol(x, Decl(inferTypePredicates.ts, 33, 29))
}
// Type guards can flow between functions
const myGuard = (o: string | undefined): o is string => !!o;
>myGuard : Symbol(myGuard, Decl(inferTypePredicates.ts, 38, 5))
>o : Symbol(o, Decl(inferTypePredicates.ts, 38, 17))
>o : Symbol(o, Decl(inferTypePredicates.ts, 38, 17))
>o : Symbol(o, Decl(inferTypePredicates.ts, 38, 17))
const mySecondGuard = (o: string | undefined) => myGuard(o);
>mySecondGuard : Symbol(mySecondGuard, Decl(inferTypePredicates.ts, 39, 5))
>o : Symbol(o, Decl(inferTypePredicates.ts, 39, 23))
>myGuard : Symbol(myGuard, Decl(inferTypePredicates.ts, 38, 5))
>o : Symbol(o, Decl(inferTypePredicates.ts, 39, 23))
// https://github.com/microsoft/TypeScript/issues/16069#issuecomment-1327449914
// This doesn't work because the false condition prevents type guard inference.
// Breaking up the filters does work.
type MyObj = { data?: string };
>MyObj : Symbol(MyObj, Decl(inferTypePredicates.ts, 39, 60))
>data : Symbol(data, Decl(inferTypePredicates.ts, 44, 14))
type MyArray = { list?: MyObj[] }[];
>MyArray : Symbol(MyArray, Decl(inferTypePredicates.ts, 44, 31))
>list : Symbol(list, Decl(inferTypePredicates.ts, 45, 16))
>MyObj : Symbol(MyObj, Decl(inferTypePredicates.ts, 39, 60))
const myArray: MyArray = [];
>myArray : Symbol(myArray, Decl(inferTypePredicates.ts, 46, 5))
>MyArray : Symbol(MyArray, Decl(inferTypePredicates.ts, 44, 31))
const result = myArray
>result : Symbol(result, Decl(inferTypePredicates.ts, 48, 5))
>myArray .map((arr) => arr.list) .filter((arr) => arr && arr.length) .map : Symbol(Array.map, Decl(lib.es5.d.ts, --, --))
>myArray .map((arr) => arr.list) .filter : Symbol(Array.filter, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>myArray .map : Symbol(Array.map, Decl(lib.es5.d.ts, --, --))
>myArray : Symbol(myArray, Decl(inferTypePredicates.ts, 46, 5))
.map((arr) => arr.list)
>map : Symbol(Array.map, Decl(lib.es5.d.ts, --, --))
>arr : Symbol(arr, Decl(inferTypePredicates.ts, 49, 8))
>arr.list : Symbol(list, Decl(inferTypePredicates.ts, 45, 16))
>arr : Symbol(arr, Decl(inferTypePredicates.ts, 49, 8))
>list : Symbol(list, Decl(inferTypePredicates.ts, 45, 16))
.filter((arr) => arr && arr.length)
>filter : Symbol(Array.filter, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>arr : Symbol(arr, Decl(inferTypePredicates.ts, 50, 11))
>arr : Symbol(arr, Decl(inferTypePredicates.ts, 50, 11))
>arr.length : Symbol(Array.length, Decl(lib.es5.d.ts, --, --))
>arr : Symbol(arr, Decl(inferTypePredicates.ts, 50, 11))
>length : Symbol(Array.length, Decl(lib.es5.d.ts, --, --))
.map((arr) => arr // should error
>map : Symbol(Array.map, Decl(lib.es5.d.ts, --, --))
>arr : Symbol(arr, Decl(inferTypePredicates.ts, 51, 8))
>arr // should error .filter((obj) => obj && obj.data) .map : Symbol(Array.map, Decl(lib.es5.d.ts, --, --))
>arr // should error .filter : Symbol(Array.filter, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>arr : Symbol(arr, Decl(inferTypePredicates.ts, 51, 8))
.filter((obj) => obj && obj.data)
>filter : Symbol(Array.filter, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>obj : Symbol(obj, Decl(inferTypePredicates.ts, 52, 13))
>obj : Symbol(obj, Decl(inferTypePredicates.ts, 52, 13))
>obj.data : Symbol(data, Decl(inferTypePredicates.ts, 44, 14))
>obj : Symbol(obj, Decl(inferTypePredicates.ts, 52, 13))
>data : Symbol(data, Decl(inferTypePredicates.ts, 44, 14))
.map(obj => JSON.parse(obj.data)) // should error
>map : Symbol(Array.map, Decl(lib.es5.d.ts, --, --))
>obj : Symbol(obj, Decl(inferTypePredicates.ts, 53, 9))
>JSON.parse : Symbol(JSON.parse, Decl(lib.es5.d.ts, --, --))
>JSON : Symbol(JSON, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>parse : Symbol(JSON.parse, Decl(lib.es5.d.ts, --, --))
>obj.data : Symbol(data, Decl(inferTypePredicates.ts, 44, 14))
>obj : Symbol(obj, Decl(inferTypePredicates.ts, 53, 9))
>data : Symbol(data, Decl(inferTypePredicates.ts, 44, 14))
);
const result2 = myArray
>result2 : Symbol(result2, Decl(inferTypePredicates.ts, 56, 5))
>myArray .map((arr) => arr.list) .filter((arr) => !!arr) .filter(arr => arr.length) .map : Symbol(Array.map, Decl(lib.es5.d.ts, --, --))
>myArray .map((arr) => arr.list) .filter((arr) => !!arr) .filter : Symbol(Array.filter, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>myArray .map((arr) => arr.list) .filter : Symbol(Array.filter, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>myArray .map : Symbol(Array.map, Decl(lib.es5.d.ts, --, --))
>myArray : Symbol(myArray, Decl(inferTypePredicates.ts, 46, 5))
.map((arr) => arr.list)
>map : Symbol(Array.map, Decl(lib.es5.d.ts, --, --))
>arr : Symbol(arr, Decl(inferTypePredicates.ts, 57, 8))
>arr.list : Symbol(list, Decl(inferTypePredicates.ts, 45, 16))
>arr : Symbol(arr, Decl(inferTypePredicates.ts, 57, 8))
>list : Symbol(list, Decl(inferTypePredicates.ts, 45, 16))
.filter((arr) => !!arr)
>filter : Symbol(Array.filter, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>arr : Symbol(arr, Decl(inferTypePredicates.ts, 58, 11))
>arr : Symbol(arr, Decl(inferTypePredicates.ts, 58, 11))
.filter(arr => arr.length)
>filter : Symbol(Array.filter, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>arr : Symbol(arr, Decl(inferTypePredicates.ts, 59, 10))
>arr.length : Symbol(Array.length, Decl(lib.es5.d.ts, --, --))
>arr : Symbol(arr, Decl(inferTypePredicates.ts, 59, 10))
>length : Symbol(Array.length, Decl(lib.es5.d.ts, --, --))
.map((arr) => arr // should ok
>map : Symbol(Array.map, Decl(lib.es5.d.ts, --, --))
>arr : Symbol(arr, Decl(inferTypePredicates.ts, 60, 8))
>arr // should ok .filter((obj) => obj) // inferring a guard here would require https://github.com/microsoft/TypeScript/issues/42384 .filter(obj => !!obj.data) .map : Symbol(Array.map, Decl(lib.es5.d.ts, --, --))
>arr // should ok .filter((obj) => obj) // inferring a guard here would require https://github.com/microsoft/TypeScript/issues/42384 .filter : Symbol(Array.filter, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>arr // should ok .filter : Symbol(Array.filter, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>arr : Symbol(arr, Decl(inferTypePredicates.ts, 60, 8))
.filter((obj) => obj)
>filter : Symbol(Array.filter, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>obj : Symbol(obj, Decl(inferTypePredicates.ts, 61, 13))
>obj : Symbol(obj, Decl(inferTypePredicates.ts, 61, 13))
// inferring a guard here would require https://github.com/microsoft/TypeScript/issues/42384
.filter(obj => !!obj.data)
>filter : Symbol(Array.filter, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>obj : Symbol(obj, Decl(inferTypePredicates.ts, 63, 12))
>obj.data : Symbol(data, Decl(inferTypePredicates.ts, 44, 14))
>obj : Symbol(obj, Decl(inferTypePredicates.ts, 63, 12))
>data : Symbol(data, Decl(inferTypePredicates.ts, 44, 14))
.map(obj => JSON.parse(obj.data))
>map : Symbol(Array.map, Decl(lib.es5.d.ts, --, --))
>obj : Symbol(obj, Decl(inferTypePredicates.ts, 64, 9))
>JSON.parse : Symbol(JSON.parse, Decl(lib.es5.d.ts, --, --))
>JSON : Symbol(JSON, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>parse : Symbol(JSON.parse, Decl(lib.es5.d.ts, --, --))
>obj.data : Symbol(data, Decl(inferTypePredicates.ts, 44, 14))
>obj : Symbol(obj, Decl(inferTypePredicates.ts, 64, 9))
>data : Symbol(data, Decl(inferTypePredicates.ts, 44, 14))
);
// https://github.com/microsoft/TypeScript/issues/16069#issuecomment-1183547889
type Foo = {
>Foo : Symbol(Foo, Decl(inferTypePredicates.ts, 65, 4))
foo: string;
>foo : Symbol(foo, Decl(inferTypePredicates.ts, 68, 12))
}
type Bar = Foo & {
>Bar : Symbol(Bar, Decl(inferTypePredicates.ts, 70, 1))
>Foo : Symbol(Foo, Decl(inferTypePredicates.ts, 65, 4))
bar: string;
>bar : Symbol(bar, Decl(inferTypePredicates.ts, 71, 18))
}
const list: (Foo | Bar)[] = [];
>list : Symbol(list, Decl(inferTypePredicates.ts, 75, 5))
>Foo : Symbol(Foo, Decl(inferTypePredicates.ts, 65, 4))
>Bar : Symbol(Bar, Decl(inferTypePredicates.ts, 70, 1))
const resultBars: Bar[] = list.filter((value) => 'bar' in value); // should ok
>resultBars : Symbol(resultBars, Decl(inferTypePredicates.ts, 76, 5))
>Bar : Symbol(Bar, Decl(inferTypePredicates.ts, 70, 1))
>list.filter : Symbol(Array.filter, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>list : Symbol(list, Decl(inferTypePredicates.ts, 75, 5))
>filter : Symbol(Array.filter, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>value : Symbol(value, Decl(inferTypePredicates.ts, 76, 39))
>value : Symbol(value, Decl(inferTypePredicates.ts, 76, 39))
function isBarNonNull(x: Foo | Bar | null) {
>isBarNonNull : Symbol(isBarNonNull, Decl(inferTypePredicates.ts, 76, 65))
>x : Symbol(x, Decl(inferTypePredicates.ts, 78, 22))
>Foo : Symbol(Foo, Decl(inferTypePredicates.ts, 65, 4))
>Bar : Symbol(Bar, Decl(inferTypePredicates.ts, 70, 1))
return ('bar' in x!);
>x : Symbol(x, Decl(inferTypePredicates.ts, 78, 22))
}
const fooOrBar = list[0];
>fooOrBar : Symbol(fooOrBar, Decl(inferTypePredicates.ts, 81, 5))
>list : Symbol(list, Decl(inferTypePredicates.ts, 75, 5))
if (isBarNonNull(fooOrBar)) {
>isBarNonNull : Symbol(isBarNonNull, Decl(inferTypePredicates.ts, 76, 65))
>fooOrBar : Symbol(fooOrBar, Decl(inferTypePredicates.ts, 81, 5))
const t: Bar = fooOrBar; // should ok
>t : Symbol(t, Decl(inferTypePredicates.ts, 83, 7))
>Bar : Symbol(Bar, Decl(inferTypePredicates.ts, 70, 1))
>fooOrBar : Symbol(fooOrBar, Decl(inferTypePredicates.ts, 81, 5))
}
// https://github.com/microsoft/TypeScript/issues/38390#issuecomment-626019466
// Ryan's example (currently legal):
const a = [1, "foo", 2, "bar"].filter(x => typeof x === "string");
>a : Symbol(a, Decl(inferTypePredicates.ts, 88, 5))
>[1, "foo", 2, "bar"].filter : Symbol(Array.filter, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>filter : Symbol(Array.filter, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>x : Symbol(x, Decl(inferTypePredicates.ts, 88, 38))
>x : Symbol(x, Decl(inferTypePredicates.ts, 88, 38))
a.push(10);
>a.push : Symbol(Array.push, Decl(lib.es5.d.ts, --, --))
>a : Symbol(a, Decl(inferTypePredicates.ts, 88, 5))
>push : Symbol(Array.push, Decl(lib.es5.d.ts, --, --))
// Defer to explicit type guards, even when they're incorrect.
function backwardsGuard(x: number|string): x is number {
>backwardsGuard : Symbol(backwardsGuard, Decl(inferTypePredicates.ts, 89, 11))
>x : Symbol(x, Decl(inferTypePredicates.ts, 92, 24))
>x : Symbol(x, Decl(inferTypePredicates.ts, 92, 24))
return typeof x === 'string';
>x : Symbol(x, Decl(inferTypePredicates.ts, 92, 24))
}
// Partition tests. The "false" case matters.
function isString(x: string | number) {
>isString : Symbol(isString, Decl(inferTypePredicates.ts, 94, 1))
>x : Symbol(x, Decl(inferTypePredicates.ts, 97, 18))
return typeof x === 'string';
>x : Symbol(x, Decl(inferTypePredicates.ts, 97, 18))
}
declare let strOrNum: string | number;
>strOrNum : Symbol(strOrNum, Decl(inferTypePredicates.ts, 101, 11))
if (isString(strOrNum)) {
>isString : Symbol(isString, Decl(inferTypePredicates.ts, 94, 1))
>strOrNum : Symbol(strOrNum, Decl(inferTypePredicates.ts, 101, 11))
let t: string = strOrNum; // should ok
>t : Symbol(t, Decl(inferTypePredicates.ts, 103, 5))
>strOrNum : Symbol(strOrNum, Decl(inferTypePredicates.ts, 101, 11))
} else {
let t: number = strOrNum; // should ok
>t : Symbol(t, Decl(inferTypePredicates.ts, 105, 5))
>strOrNum : Symbol(strOrNum, Decl(inferTypePredicates.ts, 101, 11))
}
function flakyIsString(x: string | number) {
>flakyIsString : Symbol(flakyIsString, Decl(inferTypePredicates.ts, 106, 1))
>x : Symbol(x, Decl(inferTypePredicates.ts, 108, 23))
return typeof x === 'string' && Math.random() > 0.5;
>x : Symbol(x, Decl(inferTypePredicates.ts, 108, 23))
>Math.random : Symbol(Math.random, Decl(lib.es5.d.ts, --, --))
>Math : Symbol(Math, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>random : Symbol(Math.random, Decl(lib.es5.d.ts, --, --))
}
if (flakyIsString(strOrNum)) {
>flakyIsString : Symbol(flakyIsString, Decl(inferTypePredicates.ts, 106, 1))
>strOrNum : Symbol(strOrNum, Decl(inferTypePredicates.ts, 101, 11))
let t: string = strOrNum; // should error
>t : Symbol(t, Decl(inferTypePredicates.ts, 112, 5))
>strOrNum : Symbol(strOrNum, Decl(inferTypePredicates.ts, 101, 11))
} else {
let t: number = strOrNum; // should error
>t : Symbol(t, Decl(inferTypePredicates.ts, 114, 5))
>strOrNum : Symbol(strOrNum, Decl(inferTypePredicates.ts, 101, 11))
}
function isDate(x: object) {
>isDate : Symbol(isDate, Decl(inferTypePredicates.ts, 115, 1))
>x : Symbol(x, Decl(inferTypePredicates.ts, 117, 16))
return x instanceof Date;
>x : Symbol(x, Decl(inferTypePredicates.ts, 117, 16))
>Date : Symbol(Date, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.scripthost.d.ts, --, --))
}
function flakyIsDate(x: object) {
>flakyIsDate : Symbol(flakyIsDate, Decl(inferTypePredicates.ts, 119, 1))
>x : Symbol(x, Decl(inferTypePredicates.ts, 120, 21))
return x instanceof Date && Math.random() > 0.5;
>x : Symbol(x, Decl(inferTypePredicates.ts, 120, 21))
>Date : Symbol(Date, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.scripthost.d.ts, --, --))
>Math.random : Symbol(Math.random, Decl(lib.es5.d.ts, --, --))
>Math : Symbol(Math, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>random : Symbol(Math.random, Decl(lib.es5.d.ts, --, --))
}
declare let maybeDate: object;
>maybeDate : Symbol(maybeDate, Decl(inferTypePredicates.ts, 124, 11))
if (isDate(maybeDate)) {
>isDate : Symbol(isDate, Decl(inferTypePredicates.ts, 115, 1))
>maybeDate : Symbol(maybeDate, Decl(inferTypePredicates.ts, 124, 11))
let t: Date = maybeDate; // should ok
>t : Symbol(t, Decl(inferTypePredicates.ts, 126, 5))
>Date : Symbol(Date, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.scripthost.d.ts, --, --))
>maybeDate : Symbol(maybeDate, Decl(inferTypePredicates.ts, 124, 11))
} else {
let t: object = maybeDate; // should ok
>t : Symbol(t, Decl(inferTypePredicates.ts, 128, 5))
>maybeDate : Symbol(maybeDate, Decl(inferTypePredicates.ts, 124, 11))
}
if (flakyIsDate(maybeDate)) {
>flakyIsDate : Symbol(flakyIsDate, Decl(inferTypePredicates.ts, 119, 1))
>maybeDate : Symbol(maybeDate, Decl(inferTypePredicates.ts, 124, 11))
let t: Date = maybeDate; // should error
>t : Symbol(t, Decl(inferTypePredicates.ts, 132, 5))
>Date : Symbol(Date, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.scripthost.d.ts, --, --))
>maybeDate : Symbol(maybeDate, Decl(inferTypePredicates.ts, 124, 11))
} else {
let t: object = maybeDate; // should ok
>t : Symbol(t, Decl(inferTypePredicates.ts, 134, 5))
>maybeDate : Symbol(maybeDate, Decl(inferTypePredicates.ts, 124, 11))
}
// This should not infer a type guard since the value on which we do the refinement
// is not related to the original parameter.
function irrelevantIsNumber(x: string | number) {
>irrelevantIsNumber : Symbol(irrelevantIsNumber, Decl(inferTypePredicates.ts, 135, 1))
>x : Symbol(x, Decl(inferTypePredicates.ts, 139, 28))
x = Math.random() < 0.5 ? "string" : 123;
>x : Symbol(x, Decl(inferTypePredicates.ts, 139, 28))
>Math.random : Symbol(Math.random, Decl(lib.es5.d.ts, --, --))
>Math : Symbol(Math, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>random : Symbol(Math.random, Decl(lib.es5.d.ts, --, --))
return typeof x === 'string';
>x : Symbol(x, Decl(inferTypePredicates.ts, 139, 28))
}
function irrelevantIsNumberDestructuring(x: string | number) {
>irrelevantIsNumberDestructuring : Symbol(irrelevantIsNumberDestructuring, Decl(inferTypePredicates.ts, 142, 1))
>x : Symbol(x, Decl(inferTypePredicates.ts, 143, 41))
[x] = [Math.random() < 0.5 ? "string" : 123];
>x : Symbol(x, Decl(inferTypePredicates.ts, 143, 41))
>Math.random : Symbol(Math.random, Decl(lib.es5.d.ts, --, --))
>Math : Symbol(Math, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>random : Symbol(Math.random, Decl(lib.es5.d.ts, --, --))
return typeof x === 'string';
>x : Symbol(x, Decl(inferTypePredicates.ts, 143, 41))
}
// Cannot infer a type guard for either param because of the false case.
function areBothNums(x: string|number, y: string|number) {
>areBothNums : Symbol(areBothNums, Decl(inferTypePredicates.ts, 146, 1))
>x : Symbol(x, Decl(inferTypePredicates.ts, 149, 21))
>y : Symbol(y, Decl(inferTypePredicates.ts, 149, 38))
return typeof x === 'number' && typeof y === 'number';
>x : Symbol(x, Decl(inferTypePredicates.ts, 149, 21))
>y : Symbol(y, Decl(inferTypePredicates.ts, 149, 38))
}
// Could potentially infer a type guard here but it would require more bookkeeping.
function doubleReturn(x: string|number) {
>doubleReturn : Symbol(doubleReturn, Decl(inferTypePredicates.ts, 151, 1))
>x : Symbol(x, Decl(inferTypePredicates.ts, 154, 22))
if (typeof x === 'string') {
>x : Symbol(x, Decl(inferTypePredicates.ts, 154, 22))
return true;
}
return false;
}
function guardsOneButNotOthers(a: string|number, b: string|number, c: string|number) {
>guardsOneButNotOthers : Symbol(guardsOneButNotOthers, Decl(inferTypePredicates.ts, 159, 1))
>a : Symbol(a, Decl(inferTypePredicates.ts, 161, 31))
>b : Symbol(b, Decl(inferTypePredicates.ts, 161, 48))
>c : Symbol(c, Decl(inferTypePredicates.ts, 161, 66))
return typeof b === 'string';
>b : Symbol(b, Decl(inferTypePredicates.ts, 161, 48))
}
// Checks that there are no string escaping issues
function dunderguard(__x: number | string) {
>dunderguard : Symbol(dunderguard, Decl(inferTypePredicates.ts, 163, 1))
>__x : Symbol(__x, Decl(inferTypePredicates.ts, 166, 21))
return typeof __x === 'string';
>__x : Symbol(__x, Decl(inferTypePredicates.ts, 166, 21))
}
// could infer a type guard here but it doesn't seem that helpful.
const booleanIdentity = (x: boolean) => x;
>booleanIdentity : Symbol(booleanIdentity, Decl(inferTypePredicates.ts, 171, 5))
>x : Symbol(x, Decl(inferTypePredicates.ts, 171, 25))
>x : Symbol(x, Decl(inferTypePredicates.ts, 171, 25))
// we infer "x is number | true" which is accurate but of debatable utility.
const numOrBoolean = (x: number | boolean) => typeof x === 'number' || x;
>numOrBoolean : Symbol(numOrBoolean, Decl(inferTypePredicates.ts, 174, 5))
>x : Symbol(x, Decl(inferTypePredicates.ts, 174, 22))
>x : Symbol(x, Decl(inferTypePredicates.ts, 174, 22))
>x : Symbol(x, Decl(inferTypePredicates.ts, 174, 22))
// inferred guards in methods
interface NumberInferrer {
>NumberInferrer : Symbol(NumberInferrer, Decl(inferTypePredicates.ts, 174, 73))
isNumber(x: number | string): x is number;
>isNumber : Symbol(NumberInferrer.isNumber, Decl(inferTypePredicates.ts, 177, 26))
>x : Symbol(x, Decl(inferTypePredicates.ts, 178, 11))
>x : Symbol(x, Decl(inferTypePredicates.ts, 178, 11))
}
class Inferrer implements NumberInferrer {
>Inferrer : Symbol(Inferrer, Decl(inferTypePredicates.ts, 179, 1))
>NumberInferrer : Symbol(NumberInferrer, Decl(inferTypePredicates.ts, 174, 73))
isNumber(x: number | string) { // should ok
>isNumber : Symbol(Inferrer.isNumber, Decl(inferTypePredicates.ts, 180, 42))
>x : Symbol(x, Decl(inferTypePredicates.ts, 181, 11))
return typeof x === 'number';
>x : Symbol(x, Decl(inferTypePredicates.ts, 181, 11))
}
}
declare let numOrStr: number | string;
>numOrStr : Symbol(numOrStr, Decl(inferTypePredicates.ts, 185, 11))
const inf = new Inferrer();
>inf : Symbol(inf, Decl(inferTypePredicates.ts, 186, 5))
>Inferrer : Symbol(Inferrer, Decl(inferTypePredicates.ts, 179, 1))
if (inf.isNumber(numOrStr)) {
>inf.isNumber : Symbol(Inferrer.isNumber, Decl(inferTypePredicates.ts, 180, 42))
>inf : Symbol(inf, Decl(inferTypePredicates.ts, 186, 5))
>isNumber : Symbol(Inferrer.isNumber, Decl(inferTypePredicates.ts, 180, 42))
>numOrStr : Symbol(numOrStr, Decl(inferTypePredicates.ts, 185, 11))
let t: number = numOrStr; // should ok
>t : Symbol(t, Decl(inferTypePredicates.ts, 188, 5))
>numOrStr : Symbol(numOrStr, Decl(inferTypePredicates.ts, 185, 11))
} else {
let t: string = numOrStr; // should ok
>t : Symbol(t, Decl(inferTypePredicates.ts, 190, 5))
>numOrStr : Symbol(numOrStr, Decl(inferTypePredicates.ts, 185, 11))
}
// Type predicates are not inferred on "this"
class C1 {
>C1 : Symbol(C1, Decl(inferTypePredicates.ts, 191, 1))
isC2() {
>isC2 : Symbol(C1.isC2, Decl(inferTypePredicates.ts, 194, 10))
return this instanceof C2;
>this : Symbol(C1, Decl(inferTypePredicates.ts, 191, 1))
>C2 : Symbol(C2, Decl(inferTypePredicates.ts, 198, 1))
}
}
class C2 extends C1 {
>C2 : Symbol(C2, Decl(inferTypePredicates.ts, 198, 1))
>C1 : Symbol(C1, Decl(inferTypePredicates.ts, 191, 1))
z = 0;
>z : Symbol(C2.z, Decl(inferTypePredicates.ts, 199, 21))
}
declare let c: C1;
>c : Symbol(c, Decl(inferTypePredicates.ts, 202, 11))
>C1 : Symbol(C1, Decl(inferTypePredicates.ts, 191, 1))
if (c.isC2()) {
>c.isC2 : Symbol(C1.isC2, Decl(inferTypePredicates.ts, 194, 10))
>c : Symbol(c, Decl(inferTypePredicates.ts, 202, 11))
>isC2 : Symbol(C1.isC2, Decl(inferTypePredicates.ts, 194, 10))
let c2: C2 = c; // should error
>c2 : Symbol(c2, Decl(inferTypePredicates.ts, 204, 5))
>C2 : Symbol(C2, Decl(inferTypePredicates.ts, 198, 1))
>c : Symbol(c, Decl(inferTypePredicates.ts, 202, 11))
}
function doNotRefineDestructuredParam({x, y}: {x: number | null, y: number}) {
>doNotRefineDestructuredParam : Symbol(doNotRefineDestructuredParam, Decl(inferTypePredicates.ts, 205, 1))
>x : Symbol(x, Decl(inferTypePredicates.ts, 207, 39))
>y : Symbol(y, Decl(inferTypePredicates.ts, 207, 41))
>x : Symbol(x, Decl(inferTypePredicates.ts, 207, 47))
>y : Symbol(y, Decl(inferTypePredicates.ts, 207, 64))
return typeof x === 'number';
>x : Symbol(x, Decl(inferTypePredicates.ts, 207, 39))
}
// The type predicate must remain valid when the function is called with subtypes.
function isShortString(x: unknown) {
>isShortString : Symbol(isShortString, Decl(inferTypePredicates.ts, 209, 1))
>x : Symbol(x, Decl(inferTypePredicates.ts, 212, 23))
return typeof x === "string" && x.length < 10;
>x : Symbol(x, Decl(inferTypePredicates.ts, 212, 23))
>x.length : Symbol(String.length, Decl(lib.es5.d.ts, --, --))
>x : Symbol(x, Decl(inferTypePredicates.ts, 212, 23))
>length : Symbol(String.length, Decl(lib.es5.d.ts, --, --))
}
declare let str: string;
>str : Symbol(str, Decl(inferTypePredicates.ts, 216, 11))
if (isShortString(str)) {
>isShortString : Symbol(isShortString, Decl(inferTypePredicates.ts, 209, 1))
>str : Symbol(str, Decl(inferTypePredicates.ts, 216, 11))
str.charAt(0); // should ok
>str.charAt : Symbol(String.charAt, Decl(lib.es5.d.ts, --, --))
>str : Symbol(str, Decl(inferTypePredicates.ts, 216, 11))
>charAt : Symbol(String.charAt, Decl(lib.es5.d.ts, --, --))
} else {
str.charAt(0); // should ok
>str.charAt : Symbol(String.charAt, Decl(lib.es5.d.ts, --, --))
>str : Symbol(str, Decl(inferTypePredicates.ts, 216, 11))
>charAt : Symbol(String.charAt, Decl(lib.es5.d.ts, --, --))
}
function isStringFromUnknown(x: unknown) {
>isStringFromUnknown : Symbol(isStringFromUnknown, Decl(inferTypePredicates.ts, 221, 1))
>x : Symbol(x, Decl(inferTypePredicates.ts, 223, 29))
return typeof x === "string";
>x : Symbol(x, Decl(inferTypePredicates.ts, 223, 29))
}
if (isStringFromUnknown(str)) {
>isStringFromUnknown : Symbol(isStringFromUnknown, Decl(inferTypePredicates.ts, 221, 1))
>str : Symbol(str, Decl(inferTypePredicates.ts, 216, 11))
str.charAt(0); // should OK
>str.charAt : Symbol(String.charAt, Decl(lib.es5.d.ts, --, --))
>str : Symbol(str, Decl(inferTypePredicates.ts, 216, 11))
>charAt : Symbol(String.charAt, Decl(lib.es5.d.ts, --, --))
} else {
let t: never = str; // should OK
>t : Symbol(t, Decl(inferTypePredicates.ts, 229, 5))
>str : Symbol(str, Decl(inferTypePredicates.ts, 216, 11))
}
// infer a union type
function isNumOrStr(x: unknown) {
>isNumOrStr : Symbol(isNumOrStr, Decl(inferTypePredicates.ts, 230, 1))
>x : Symbol(x, Decl(inferTypePredicates.ts, 233, 20))
return (typeof x === "number" || typeof x === "string");
>x : Symbol(x, Decl(inferTypePredicates.ts, 233, 20))
>x : Symbol(x, Decl(inferTypePredicates.ts, 233, 20))
}
declare let unk: unknown;
>unk : Symbol(unk, Decl(inferTypePredicates.ts, 236, 11))
if (isNumOrStr(unk)) {
>isNumOrStr : Symbol(isNumOrStr, Decl(inferTypePredicates.ts, 230, 1))
>unk : Symbol(unk, Decl(inferTypePredicates.ts, 236, 11))
let t: number | string = unk; // should ok
>t : Symbol(t, Decl(inferTypePredicates.ts, 238, 5))
>unk : Symbol(unk, Decl(inferTypePredicates.ts, 236, 11))
}
// A function can be a type predicate even if it throws.
function assertAndPredicate(x: string | number | Date) {
>assertAndPredicate : Symbol(assertAndPredicate, Decl(inferTypePredicates.ts, 239, 1))
>x : Symbol(x, Decl(inferTypePredicates.ts, 242, 28))
>Date : Symbol(Date, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.scripthost.d.ts, --, --))
if (x instanceof Date) {
>x : Symbol(x, Decl(inferTypePredicates.ts, 242, 28))
>Date : Symbol(Date, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.scripthost.d.ts, --, --))
throw new Error();
>Error : Symbol(Error, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
}
return typeof x === 'string';
>x : Symbol(x, Decl(inferTypePredicates.ts, 242, 28))
}
declare let snd: string | number | Date;
>snd : Symbol(snd, Decl(inferTypePredicates.ts, 249, 11))
>Date : Symbol(Date, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.scripthost.d.ts, --, --))
if (assertAndPredicate(snd)) {
>assertAndPredicate : Symbol(assertAndPredicate, Decl(inferTypePredicates.ts, 239, 1))
>snd : Symbol(snd, Decl(inferTypePredicates.ts, 249, 11))
let t: string = snd; // should error
>t : Symbol(t, Decl(inferTypePredicates.ts, 251, 5))
>snd : Symbol(snd, Decl(inferTypePredicates.ts, 249, 11))
}
function isNumberWithThis(this: Date, x: number | string) {
>isNumberWithThis : Symbol(isNumberWithThis, Decl(inferTypePredicates.ts, 252, 1))
>this : Symbol(this, Decl(inferTypePredicates.ts, 254, 26))
>Date : Symbol(Date, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.scripthost.d.ts, --, --))
>x : Symbol(x, Decl(inferTypePredicates.ts, 254, 37))
return typeof x === 'number';
>x : Symbol(x, Decl(inferTypePredicates.ts, 254, 37))
}
function narrowFromAny(x: any) {
>narrowFromAny : Symbol(narrowFromAny, Decl(inferTypePredicates.ts, 256, 1))
>x : Symbol(x, Decl(inferTypePredicates.ts, 258, 23))
return typeof x === 'number';
>x : Symbol(x, Decl(inferTypePredicates.ts, 258, 23))
}
const noInferenceFromRest = (...f: ["a" | "b"]) => f[0] === "a";
>noInferenceFromRest : Symbol(noInferenceFromRest, Decl(inferTypePredicates.ts, 262, 5))
>f : Symbol(f, Decl(inferTypePredicates.ts, 262, 29))
>f : Symbol(f, Decl(inferTypePredicates.ts, 262, 29))
>0 : Symbol(0)
const noInferenceFromImpossibleRest = (...f: []) => typeof f === "undefined";
>noInferenceFromImpossibleRest : Symbol(noInferenceFromImpossibleRest, Decl(inferTypePredicates.ts, 263, 5))
>f : Symbol(f, Decl(inferTypePredicates.ts, 263, 39))
>f : Symbol(f, Decl(inferTypePredicates.ts, 263, 39))
function inferWithRest(x: string | null, ...f: ["a", "b"]) {
>inferWithRest : Symbol(inferWithRest, Decl(inferTypePredicates.ts, 263, 77))
>x : Symbol(x, Decl(inferTypePredicates.ts, 265, 23))
>f : Symbol(f, Decl(inferTypePredicates.ts, 265, 40))
return typeof x === 'string';
>x : Symbol(x, Decl(inferTypePredicates.ts, 265, 23))
}

View File

@ -0,0 +1,981 @@
//// [tests/cases/compiler/inferTypePredicates.ts] ////
=== inferTypePredicates.ts ===
// https://github.com/microsoft/TypeScript/issues/16069
const numsOrNull = [1, 2, 3, 4, null];
>numsOrNull : (number | null)[]
>[1, 2, 3, 4, null] : (number | null)[]
>1 : 1
>2 : 2
>3 : 3
>4 : 4
const filteredNumsTruthy: number[] = numsOrNull.filter(x => !!x); // should error
>filteredNumsTruthy : number[]
>numsOrNull.filter(x => !!x) : (number | null)[]
>numsOrNull.filter : { <S extends number | null>(predicate: (value: number | null, index: number, array: (number | null)[]) => value is S, thisArg?: any): S[]; (predicate: (value: number | null, index: number, array: (number | null)[]) => unknown, thisArg?: any): (number | null)[]; }
>numsOrNull : (number | null)[]
>filter : { <S extends number | null>(predicate: (value: number | null, index: number, array: (number | null)[]) => value is S, thisArg?: any): S[]; (predicate: (value: number | null, index: number, array: (number | null)[]) => unknown, thisArg?: any): (number | null)[]; }
>x => !!x : (x: number | null) => boolean
>x : number | null
>!!x : boolean
>!x : boolean
>x : number | null
const filteredNumsNonNullish: number[] = numsOrNull.filter(x => x !== null); // should ok
>filteredNumsNonNullish : number[]
>numsOrNull.filter(x => x !== null) : number[]
>numsOrNull.filter : { <S extends number | null>(predicate: (value: number | null, index: number, array: (number | null)[]) => value is S, thisArg?: any): S[]; (predicate: (value: number | null, index: number, array: (number | null)[]) => unknown, thisArg?: any): (number | null)[]; }
>numsOrNull : (number | null)[]
>filter : { <S extends number | null>(predicate: (value: number | null, index: number, array: (number | null)[]) => value is S, thisArg?: any): S[]; (predicate: (value: number | null, index: number, array: (number | null)[]) => unknown, thisArg?: any): (number | null)[]; }
>x => x !== null : (x: number | null) => x is number
>x : number | null
>x !== null : boolean
>x : number | null
const evenSquaresInline: number[] = // should error
>evenSquaresInline : number[]
[1, 2, 3, 4]
>[1, 2, 3, 4] .map(x => x % 2 === 0 ? x * x : null) .filter(x => !!x) : (number | null)[]
>[1, 2, 3, 4] .map(x => x % 2 === 0 ? x * x : null) .filter : { <S extends number | null>(predicate: (value: number | null, index: number, array: (number | null)[]) => value is S, thisArg?: any): S[]; (predicate: (value: number | null, index: number, array: (number | null)[]) => unknown, thisArg?: any): (number | null)[]; }
>[1, 2, 3, 4] .map(x => x % 2 === 0 ? x * x : null) : (number | null)[]
>[1, 2, 3, 4] .map : <U>(callbackfn: (value: number, index: number, array: number[]) => U, thisArg?: any) => U[]
>[1, 2, 3, 4] : number[]
>1 : 1
>2 : 2
>3 : 3
>4 : 4
.map(x => x % 2 === 0 ? x * x : null)
>map : <U>(callbackfn: (value: number, index: number, array: number[]) => U, thisArg?: any) => U[]
>x => x % 2 === 0 ? x * x : null : (x: number) => number | null
>x : number
>x % 2 === 0 ? x * x : null : number | null
>x % 2 === 0 : boolean
>x % 2 : number
>x : number
>2 : 2
>0 : 0
>x * x : number
>x : number
>x : number
.filter(x => !!x); // tests truthiness, not non-nullishness
>filter : { <S extends number | null>(predicate: (value: number | null, index: number, array: (number | null)[]) => value is S, thisArg?: any): S[]; (predicate: (value: number | null, index: number, array: (number | null)[]) => unknown, thisArg?: any): (number | null)[]; }
>x => !!x : (x: number | null) => boolean
>x : number | null
>!!x : boolean
>!x : boolean
>x : number | null
const isTruthy = (x: number | null) => !!x;
>isTruthy : (x: number | null) => boolean
>(x: number | null) => !!x : (x: number | null) => boolean
>x : number | null
>!!x : boolean
>!x : boolean
>x : number | null
const evenSquares: number[] = // should error
>evenSquares : number[]
[1, 2, 3, 4]
>[1, 2, 3, 4] .map(x => x % 2 === 0 ? x * x : null) .filter(isTruthy) : (number | null)[]
>[1, 2, 3, 4] .map(x => x % 2 === 0 ? x * x : null) .filter : { <S extends number | null>(predicate: (value: number | null, index: number, array: (number | null)[]) => value is S, thisArg?: any): S[]; (predicate: (value: number | null, index: number, array: (number | null)[]) => unknown, thisArg?: any): (number | null)[]; }
>[1, 2, 3, 4] .map(x => x % 2 === 0 ? x * x : null) : (number | null)[]
>[1, 2, 3, 4] .map : <U>(callbackfn: (value: number, index: number, array: number[]) => U, thisArg?: any) => U[]
>[1, 2, 3, 4] : number[]
>1 : 1
>2 : 2
>3 : 3
>4 : 4
.map(x => x % 2 === 0 ? x * x : null)
>map : <U>(callbackfn: (value: number, index: number, array: number[]) => U, thisArg?: any) => U[]
>x => x % 2 === 0 ? x * x : null : (x: number) => number | null
>x : number
>x % 2 === 0 ? x * x : null : number | null
>x % 2 === 0 : boolean
>x % 2 : number
>x : number
>2 : 2
>0 : 0
>x * x : number
>x : number
>x : number
.filter(isTruthy);
>filter : { <S extends number | null>(predicate: (value: number | null, index: number, array: (number | null)[]) => value is S, thisArg?: any): S[]; (predicate: (value: number | null, index: number, array: (number | null)[]) => unknown, thisArg?: any): (number | null)[]; }
>isTruthy : (x: number | null) => boolean
const evenSquaresNonNull: number[] = // should ok
>evenSquaresNonNull : number[]
[1, 2, 3, 4]
>[1, 2, 3, 4] .map(x => x % 2 === 0 ? x * x : null) .filter(x => x !== null) : number[]
>[1, 2, 3, 4] .map(x => x % 2 === 0 ? x * x : null) .filter : { <S extends number | null>(predicate: (value: number | null, index: number, array: (number | null)[]) => value is S, thisArg?: any): S[]; (predicate: (value: number | null, index: number, array: (number | null)[]) => unknown, thisArg?: any): (number | null)[]; }
>[1, 2, 3, 4] .map(x => x % 2 === 0 ? x * x : null) : (number | null)[]
>[1, 2, 3, 4] .map : <U>(callbackfn: (value: number, index: number, array: number[]) => U, thisArg?: any) => U[]
>[1, 2, 3, 4] : number[]
>1 : 1
>2 : 2
>3 : 3
>4 : 4
.map(x => x % 2 === 0 ? x * x : null)
>map : <U>(callbackfn: (value: number, index: number, array: number[]) => U, thisArg?: any) => U[]
>x => x % 2 === 0 ? x * x : null : (x: number) => number | null
>x : number
>x % 2 === 0 ? x * x : null : number | null
>x % 2 === 0 : boolean
>x % 2 : number
>x : number
>2 : 2
>0 : 0
>x * x : number
>x : number
>x : number
.filter(x => x !== null);
>filter : { <S extends number | null>(predicate: (value: number | null, index: number, array: (number | null)[]) => value is S, thisArg?: any): S[]; (predicate: (value: number | null, index: number, array: (number | null)[]) => unknown, thisArg?: any): (number | null)[]; }
>x => x !== null : (x: number | null) => x is number
>x : number | null
>x !== null : boolean
>x : number | null
function isNonNull(x: number | null) {
>isNonNull : (x: number | null) => x is number
>x : number | null
return x !== null;
>x !== null : boolean
>x : number | null
}
// factoring out a boolean works thanks to aliased discriminants
function isNonNullVar(x: number | null) {
>isNonNullVar : (x: number | null) => x is number
>x : number | null
const ok = x !== null;
>ok : boolean
>x !== null : boolean
>x : number | null
return ok;
>ok : boolean
}
function isNonNullGeneric<T>(x: T) {
>isNonNullGeneric : <T>(x: T) => x is T & ({} | undefined)
>x : T
return x !== null;
>x !== null : boolean
>x : T
}
// Type guards can flow between functions
const myGuard = (o: string | undefined): o is string => !!o;
>myGuard : (o: string | undefined) => o is string
>(o: string | undefined): o is string => !!o : (o: string | undefined) => o is string
>o : string | undefined
>!!o : boolean
>!o : boolean
>o : string | undefined
const mySecondGuard = (o: string | undefined) => myGuard(o);
>mySecondGuard : (o: string | undefined) => o is string
>(o: string | undefined) => myGuard(o) : (o: string | undefined) => o is string
>o : string | undefined
>myGuard(o) : boolean
>myGuard : (o: string | undefined) => o is string
>o : string | undefined
// https://github.com/microsoft/TypeScript/issues/16069#issuecomment-1327449914
// This doesn't work because the false condition prevents type guard inference.
// Breaking up the filters does work.
type MyObj = { data?: string };
>MyObj : { data?: string | undefined; }
>data : string | undefined
type MyArray = { list?: MyObj[] }[];
>MyArray : { list?: MyObj[] | undefined; }[]
>list : MyObj[] | undefined
const myArray: MyArray = [];
>myArray : MyArray
>[] : never[]
const result = myArray
>result : any[][]
>myArray .map((arr) => arr.list) .filter((arr) => arr && arr.length) .map((arr) => arr // should error .filter((obj) => obj && obj.data) .map(obj => JSON.parse(obj.data)) // should error ) : any[][]
>myArray .map((arr) => arr.list) .filter((arr) => arr && arr.length) .map : <U>(callbackfn: (value: MyObj[] | undefined, index: number, array: (MyObj[] | undefined)[]) => U, thisArg?: any) => U[]
>myArray .map((arr) => arr.list) .filter((arr) => arr && arr.length) : (MyObj[] | undefined)[]
>myArray .map((arr) => arr.list) .filter : { <S extends MyObj[] | undefined>(predicate: (value: MyObj[] | undefined, index: number, array: (MyObj[] | undefined)[]) => value is S, thisArg?: any): S[]; (predicate: (value: MyObj[] | undefined, index: number, array: (MyObj[] | undefined)[]) => unknown, thisArg?: any): (MyObj[] | undefined)[]; }
>myArray .map((arr) => arr.list) : (MyObj[] | undefined)[]
>myArray .map : <U>(callbackfn: (value: { list?: MyObj[] | undefined; }, index: number, array: { list?: MyObj[] | undefined; }[]) => U, thisArg?: any) => U[]
>myArray : MyArray
.map((arr) => arr.list)
>map : <U>(callbackfn: (value: { list?: MyObj[] | undefined; }, index: number, array: { list?: MyObj[] | undefined; }[]) => U, thisArg?: any) => U[]
>(arr) => arr.list : (arr: { list?: MyObj[] | undefined; }) => MyObj[] | undefined
>arr : { list?: MyObj[] | undefined; }
>arr.list : MyObj[] | undefined
>arr : { list?: MyObj[] | undefined; }
>list : MyObj[] | undefined
.filter((arr) => arr && arr.length)
>filter : { <S extends MyObj[] | undefined>(predicate: (value: MyObj[] | undefined, index: number, array: (MyObj[] | undefined)[]) => value is S, thisArg?: any): S[]; (predicate: (value: MyObj[] | undefined, index: number, array: (MyObj[] | undefined)[]) => unknown, thisArg?: any): (MyObj[] | undefined)[]; }
>(arr) => arr && arr.length : (arr: MyObj[] | undefined) => number | undefined
>arr : MyObj[] | undefined
>arr && arr.length : number | undefined
>arr : MyObj[] | undefined
>arr.length : number
>arr : MyObj[]
>length : number
.map((arr) => arr // should error
>map : <U>(callbackfn: (value: MyObj[] | undefined, index: number, array: (MyObj[] | undefined)[]) => U, thisArg?: any) => U[]
>(arr) => arr // should error .filter((obj) => obj && obj.data) .map(obj => JSON.parse(obj.data)) : (arr: MyObj[] | undefined) => any[]
>arr : MyObj[] | undefined
>arr // should error .filter((obj) => obj && obj.data) .map(obj => JSON.parse(obj.data)) : any[]
>arr // should error .filter((obj) => obj && obj.data) .map : <U>(callbackfn: (value: MyObj, index: number, array: MyObj[]) => U, thisArg?: any) => U[]
>arr // should error .filter((obj) => obj && obj.data) : MyObj[]
>arr // should error .filter : { <S extends MyObj>(predicate: (value: MyObj, index: number, array: MyObj[]) => value is S, thisArg?: any): S[]; (predicate: (value: MyObj, index: number, array: MyObj[]) => unknown, thisArg?: any): MyObj[]; }
>arr : MyObj[] | undefined
.filter((obj) => obj && obj.data)
>filter : { <S extends MyObj>(predicate: (value: MyObj, index: number, array: MyObj[]) => value is S, thisArg?: any): S[]; (predicate: (value: MyObj, index: number, array: MyObj[]) => unknown, thisArg?: any): MyObj[]; }
>(obj) => obj && obj.data : (obj: MyObj) => string | undefined
>obj : MyObj
>obj && obj.data : string | undefined
>obj : MyObj
>obj.data : string | undefined
>obj : MyObj
>data : string | undefined
.map(obj => JSON.parse(obj.data)) // should error
>map : <U>(callbackfn: (value: MyObj, index: number, array: MyObj[]) => U, thisArg?: any) => U[]
>obj => JSON.parse(obj.data) : (obj: MyObj) => any
>obj : MyObj
>JSON.parse(obj.data) : any
>JSON.parse : (text: string, reviver?: ((this: any, key: string, value: any) => any) | undefined) => any
>JSON : JSON
>parse : (text: string, reviver?: ((this: any, key: string, value: any) => any) | undefined) => any
>obj.data : string | undefined
>obj : MyObj
>data : string | undefined
);
const result2 = myArray
>result2 : any[][]
>myArray .map((arr) => arr.list) .filter((arr) => !!arr) .filter(arr => arr.length) .map((arr) => arr // should ok .filter((obj) => obj) // inferring a guard here would require https://github.com/microsoft/TypeScript/issues/42384 .filter(obj => !!obj.data) .map(obj => JSON.parse(obj.data)) ) : any[][]
>myArray .map((arr) => arr.list) .filter((arr) => !!arr) .filter(arr => arr.length) .map : <U>(callbackfn: (value: MyObj[], index: number, array: MyObj[][]) => U, thisArg?: any) => U[]
>myArray .map((arr) => arr.list) .filter((arr) => !!arr) .filter(arr => arr.length) : MyObj[][]
>myArray .map((arr) => arr.list) .filter((arr) => !!arr) .filter : { <S extends MyObj[]>(predicate: (value: MyObj[], index: number, array: MyObj[][]) => value is S, thisArg?: any): S[]; (predicate: (value: MyObj[], index: number, array: MyObj[][]) => unknown, thisArg?: any): MyObj[][]; }
>myArray .map((arr) => arr.list) .filter((arr) => !!arr) : MyObj[][]
>myArray .map((arr) => arr.list) .filter : { <S extends MyObj[] | undefined>(predicate: (value: MyObj[] | undefined, index: number, array: (MyObj[] | undefined)[]) => value is S, thisArg?: any): S[]; (predicate: (value: MyObj[] | undefined, index: number, array: (MyObj[] | undefined)[]) => unknown, thisArg?: any): (MyObj[] | undefined)[]; }
>myArray .map((arr) => arr.list) : (MyObj[] | undefined)[]
>myArray .map : <U>(callbackfn: (value: { list?: MyObj[] | undefined; }, index: number, array: { list?: MyObj[] | undefined; }[]) => U, thisArg?: any) => U[]
>myArray : MyArray
.map((arr) => arr.list)
>map : <U>(callbackfn: (value: { list?: MyObj[] | undefined; }, index: number, array: { list?: MyObj[] | undefined; }[]) => U, thisArg?: any) => U[]
>(arr) => arr.list : (arr: { list?: MyObj[] | undefined; }) => MyObj[] | undefined
>arr : { list?: MyObj[] | undefined; }
>arr.list : MyObj[] | undefined
>arr : { list?: MyObj[] | undefined; }
>list : MyObj[] | undefined
.filter((arr) => !!arr)
>filter : { <S extends MyObj[] | undefined>(predicate: (value: MyObj[] | undefined, index: number, array: (MyObj[] | undefined)[]) => value is S, thisArg?: any): S[]; (predicate: (value: MyObj[] | undefined, index: number, array: (MyObj[] | undefined)[]) => unknown, thisArg?: any): (MyObj[] | undefined)[]; }
>(arr) => !!arr : (arr: MyObj[] | undefined) => arr is MyObj[]
>arr : MyObj[] | undefined
>!!arr : boolean
>!arr : boolean
>arr : MyObj[] | undefined
.filter(arr => arr.length)
>filter : { <S extends MyObj[]>(predicate: (value: MyObj[], index: number, array: MyObj[][]) => value is S, thisArg?: any): S[]; (predicate: (value: MyObj[], index: number, array: MyObj[][]) => unknown, thisArg?: any): MyObj[][]; }
>arr => arr.length : (arr: MyObj[]) => number
>arr : MyObj[]
>arr.length : number
>arr : MyObj[]
>length : number
.map((arr) => arr // should ok
>map : <U>(callbackfn: (value: MyObj[], index: number, array: MyObj[][]) => U, thisArg?: any) => U[]
>(arr) => arr // should ok .filter((obj) => obj) // inferring a guard here would require https://github.com/microsoft/TypeScript/issues/42384 .filter(obj => !!obj.data) .map(obj => JSON.parse(obj.data)) : (arr: MyObj[]) => any[]
>arr : MyObj[]
>arr // should ok .filter((obj) => obj) // inferring a guard here would require https://github.com/microsoft/TypeScript/issues/42384 .filter(obj => !!obj.data) .map(obj => JSON.parse(obj.data)) : any[]
>arr // should ok .filter((obj) => obj) // inferring a guard here would require https://github.com/microsoft/TypeScript/issues/42384 .filter(obj => !!obj.data) .map : <U>(callbackfn: (value: MyObj, index: number, array: MyObj[]) => U, thisArg?: any) => U[]
>arr // should ok .filter((obj) => obj) // inferring a guard here would require https://github.com/microsoft/TypeScript/issues/42384 .filter(obj => !!obj.data) : MyObj[]
>arr // should ok .filter((obj) => obj) // inferring a guard here would require https://github.com/microsoft/TypeScript/issues/42384 .filter : { <S extends MyObj>(predicate: (value: MyObj, index: number, array: MyObj[]) => value is S, thisArg?: any): S[]; (predicate: (value: MyObj, index: number, array: MyObj[]) => unknown, thisArg?: any): MyObj[]; }
>arr // should ok .filter((obj) => obj) : MyObj[]
>arr // should ok .filter : { <S extends MyObj>(predicate: (value: MyObj, index: number, array: MyObj[]) => value is S, thisArg?: any): S[]; (predicate: (value: MyObj, index: number, array: MyObj[]) => unknown, thisArg?: any): MyObj[]; }
>arr : MyObj[]
.filter((obj) => obj)
>filter : { <S extends MyObj>(predicate: (value: MyObj, index: number, array: MyObj[]) => value is S, thisArg?: any): S[]; (predicate: (value: MyObj, index: number, array: MyObj[]) => unknown, thisArg?: any): MyObj[]; }
>(obj) => obj : (obj: MyObj) => MyObj
>obj : MyObj
>obj : MyObj
// inferring a guard here would require https://github.com/microsoft/TypeScript/issues/42384
.filter(obj => !!obj.data)
>filter : { <S extends MyObj>(predicate: (value: MyObj, index: number, array: MyObj[]) => value is S, thisArg?: any): S[]; (predicate: (value: MyObj, index: number, array: MyObj[]) => unknown, thisArg?: any): MyObj[]; }
>obj => !!obj.data : (obj: MyObj) => boolean
>obj : MyObj
>!!obj.data : boolean
>!obj.data : boolean
>obj.data : string | undefined
>obj : MyObj
>data : string | undefined
.map(obj => JSON.parse(obj.data))
>map : <U>(callbackfn: (value: MyObj, index: number, array: MyObj[]) => U, thisArg?: any) => U[]
>obj => JSON.parse(obj.data) : (obj: MyObj) => any
>obj : MyObj
>JSON.parse(obj.data) : any
>JSON.parse : (text: string, reviver?: ((this: any, key: string, value: any) => any) | undefined) => any
>JSON : JSON
>parse : (text: string, reviver?: ((this: any, key: string, value: any) => any) | undefined) => any
>obj.data : string | undefined
>obj : MyObj
>data : string | undefined
);
// https://github.com/microsoft/TypeScript/issues/16069#issuecomment-1183547889
type Foo = {
>Foo : { foo: string; }
foo: string;
>foo : string
}
type Bar = Foo & {
>Bar : Foo & { bar: string; }
bar: string;
>bar : string
}
const list: (Foo | Bar)[] = [];
>list : (Foo | Bar)[]
>[] : never[]
const resultBars: Bar[] = list.filter((value) => 'bar' in value); // should ok
>resultBars : Bar[]
>list.filter((value) => 'bar' in value) : Bar[]
>list.filter : { <S extends Foo | Bar>(predicate: (value: Foo | Bar, index: number, array: (Foo | Bar)[]) => value is S, thisArg?: any): S[]; (predicate: (value: Foo | Bar, index: number, array: (Foo | Bar)[]) => unknown, thisArg?: any): (Foo | Bar)[]; }
>list : (Foo | Bar)[]
>filter : { <S extends Foo | Bar>(predicate: (value: Foo | Bar, index: number, array: (Foo | Bar)[]) => value is S, thisArg?: any): S[]; (predicate: (value: Foo | Bar, index: number, array: (Foo | Bar)[]) => unknown, thisArg?: any): (Foo | Bar)[]; }
>(value) => 'bar' in value : (value: Foo | Bar) => value is Bar
>value : Foo | Bar
>'bar' in value : boolean
>'bar' : "bar"
>value : Foo | Bar
function isBarNonNull(x: Foo | Bar | null) {
>isBarNonNull : (x: Foo | Bar | null) => x is Bar
>x : Foo | Bar | null
return ('bar' in x!);
>('bar' in x!) : boolean
>'bar' in x! : boolean
>'bar' : "bar"
>x! : Foo | Bar
>x : Foo | Bar | null
}
const fooOrBar = list[0];
>fooOrBar : Foo | Bar
>list[0] : Foo | Bar
>list : (Foo | Bar)[]
>0 : 0
if (isBarNonNull(fooOrBar)) {
>isBarNonNull(fooOrBar) : boolean
>isBarNonNull : (x: Foo | Bar | null) => x is Bar
>fooOrBar : Foo | Bar
const t: Bar = fooOrBar; // should ok
>t : Bar
>fooOrBar : Bar
}
// https://github.com/microsoft/TypeScript/issues/38390#issuecomment-626019466
// Ryan's example (currently legal):
const a = [1, "foo", 2, "bar"].filter(x => typeof x === "string");
>a : string[]
>[1, "foo", 2, "bar"].filter(x => typeof x === "string") : string[]
>[1, "foo", 2, "bar"].filter : { <S extends string | number>(predicate: (value: string | number, index: number, array: (string | number)[]) => value is S, thisArg?: any): S[]; (predicate: (value: string | number, index: number, array: (string | number)[]) => unknown, thisArg?: any): (string | number)[]; }
>[1, "foo", 2, "bar"] : (string | number)[]
>1 : 1
>"foo" : "foo"
>2 : 2
>"bar" : "bar"
>filter : { <S extends string | number>(predicate: (value: string | number, index: number, array: (string | number)[]) => value is S, thisArg?: any): S[]; (predicate: (value: string | number, index: number, array: (string | number)[]) => unknown, thisArg?: any): (string | number)[]; }
>x => typeof x === "string" : (x: string | number) => x is string
>x : string | number
>typeof x === "string" : boolean
>typeof x : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function"
>x : string | number
>"string" : "string"
a.push(10);
>a.push(10) : number
>a.push : (...items: string[]) => number
>a : string[]
>push : (...items: string[]) => number
>10 : 10
// Defer to explicit type guards, even when they're incorrect.
function backwardsGuard(x: number|string): x is number {
>backwardsGuard : (x: number | string) => x is number
>x : string | number
return typeof x === 'string';
>typeof x === 'string' : boolean
>typeof x : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function"
>x : string | number
>'string' : "string"
}
// Partition tests. The "false" case matters.
function isString(x: string | number) {
>isString : (x: string | number) => x is string
>x : string | number
return typeof x === 'string';
>typeof x === 'string' : boolean
>typeof x : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function"
>x : string | number
>'string' : "string"
}
declare let strOrNum: string | number;
>strOrNum : string | number
if (isString(strOrNum)) {
>isString(strOrNum) : boolean
>isString : (x: string | number) => x is string
>strOrNum : string | number
let t: string = strOrNum; // should ok
>t : string
>strOrNum : string
} else {
let t: number = strOrNum; // should ok
>t : number
>strOrNum : number
}
function flakyIsString(x: string | number) {
>flakyIsString : (x: string | number) => boolean
>x : string | number
return typeof x === 'string' && Math.random() > 0.5;
>typeof x === 'string' && Math.random() > 0.5 : boolean
>typeof x === 'string' : boolean
>typeof x : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function"
>x : string | number
>'string' : "string"
>Math.random() > 0.5 : boolean
>Math.random() : number
>Math.random : () => number
>Math : Math
>random : () => number
>0.5 : 0.5
}
if (flakyIsString(strOrNum)) {
>flakyIsString(strOrNum) : boolean
>flakyIsString : (x: string | number) => boolean
>strOrNum : string | number
let t: string = strOrNum; // should error
>t : string
>strOrNum : string | number
} else {
let t: number = strOrNum; // should error
>t : number
>strOrNum : string | number
}
function isDate(x: object) {
>isDate : (x: object) => x is Date
>x : object
return x instanceof Date;
>x instanceof Date : boolean
>x : object
>Date : DateConstructor
}
function flakyIsDate(x: object) {
>flakyIsDate : (x: object) => boolean
>x : object
return x instanceof Date && Math.random() > 0.5;
>x instanceof Date && Math.random() > 0.5 : boolean
>x instanceof Date : boolean
>x : object
>Date : DateConstructor
>Math.random() > 0.5 : boolean
>Math.random() : number
>Math.random : () => number
>Math : Math
>random : () => number
>0.5 : 0.5
}
declare let maybeDate: object;
>maybeDate : object
if (isDate(maybeDate)) {
>isDate(maybeDate) : boolean
>isDate : (x: object) => x is Date
>maybeDate : object
let t: Date = maybeDate; // should ok
>t : Date
>maybeDate : Date
} else {
let t: object = maybeDate; // should ok
>t : object
>maybeDate : object
}
if (flakyIsDate(maybeDate)) {
>flakyIsDate(maybeDate) : boolean
>flakyIsDate : (x: object) => boolean
>maybeDate : object
let t: Date = maybeDate; // should error
>t : Date
>maybeDate : object
} else {
let t: object = maybeDate; // should ok
>t : object
>maybeDate : object
}
// This should not infer a type guard since the value on which we do the refinement
// is not related to the original parameter.
function irrelevantIsNumber(x: string | number) {
>irrelevantIsNumber : (x: string | number) => boolean
>x : string | number
x = Math.random() < 0.5 ? "string" : 123;
>x = Math.random() < 0.5 ? "string" : 123 : "string" | 123
>x : string | number
>Math.random() < 0.5 ? "string" : 123 : "string" | 123
>Math.random() < 0.5 : boolean
>Math.random() : number
>Math.random : () => number
>Math : Math
>random : () => number
>0.5 : 0.5
>"string" : "string"
>123 : 123
return typeof x === 'string';
>typeof x === 'string' : boolean
>typeof x : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function"
>x : string | number
>'string' : "string"
}
function irrelevantIsNumberDestructuring(x: string | number) {
>irrelevantIsNumberDestructuring : (x: string | number) => boolean
>x : string | number
[x] = [Math.random() < 0.5 ? "string" : 123];
>[x] = [Math.random() < 0.5 ? "string" : 123] : [string | number]
>[x] : [string | number]
>x : string | number
>[Math.random() < 0.5 ? "string" : 123] : [string | number]
>Math.random() < 0.5 ? "string" : 123 : "string" | 123
>Math.random() < 0.5 : boolean
>Math.random() : number
>Math.random : () => number
>Math : Math
>random : () => number
>0.5 : 0.5
>"string" : "string"
>123 : 123
return typeof x === 'string';
>typeof x === 'string' : boolean
>typeof x : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function"
>x : string | number
>'string' : "string"
}
// Cannot infer a type guard for either param because of the false case.
function areBothNums(x: string|number, y: string|number) {
>areBothNums : (x: string | number, y: string | number) => boolean
>x : string | number
>y : string | number
return typeof x === 'number' && typeof y === 'number';
>typeof x === 'number' && typeof y === 'number' : boolean
>typeof x === 'number' : boolean
>typeof x : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function"
>x : string | number
>'number' : "number"
>typeof y === 'number' : boolean
>typeof y : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function"
>y : string | number
>'number' : "number"
}
// Could potentially infer a type guard here but it would require more bookkeeping.
function doubleReturn(x: string|number) {
>doubleReturn : (x: string | number) => boolean
>x : string | number
if (typeof x === 'string') {
>typeof x === 'string' : boolean
>typeof x : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function"
>x : string | number
>'string' : "string"
return true;
>true : true
}
return false;
>false : false
}
function guardsOneButNotOthers(a: string|number, b: string|number, c: string|number) {
>guardsOneButNotOthers : (a: string | number, b: string | number, c: string | number) => b is string
>a : string | number
>b : string | number
>c : string | number
return typeof b === 'string';
>typeof b === 'string' : boolean
>typeof b : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function"
>b : string | number
>'string' : "string"
}
// Checks that there are no string escaping issues
function dunderguard(__x: number | string) {
>dunderguard : (__x: number | string) => __x is string
>__x : string | number
return typeof __x === 'string';
>typeof __x === 'string' : boolean
>typeof __x : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function"
>__x : string | number
>'string' : "string"
}
// could infer a type guard here but it doesn't seem that helpful.
const booleanIdentity = (x: boolean) => x;
>booleanIdentity : (x: boolean) => boolean
>(x: boolean) => x : (x: boolean) => boolean
>x : boolean
>x : boolean
// we infer "x is number | true" which is accurate but of debatable utility.
const numOrBoolean = (x: number | boolean) => typeof x === 'number' || x;
>numOrBoolean : (x: number | boolean) => x is number | true
>(x: number | boolean) => typeof x === 'number' || x : (x: number | boolean) => x is number | true
>x : number | boolean
>typeof x === 'number' || x : boolean
>typeof x === 'number' : boolean
>typeof x : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function"
>x : number | boolean
>'number' : "number"
>x : boolean
// inferred guards in methods
interface NumberInferrer {
isNumber(x: number | string): x is number;
>isNumber : (x: number | string) => x is number
>x : string | number
}
class Inferrer implements NumberInferrer {
>Inferrer : Inferrer
isNumber(x: number | string) { // should ok
>isNumber : (x: number | string) => x is number
>x : string | number
return typeof x === 'number';
>typeof x === 'number' : boolean
>typeof x : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function"
>x : string | number
>'number' : "number"
}
}
declare let numOrStr: number | string;
>numOrStr : string | number
const inf = new Inferrer();
>inf : Inferrer
>new Inferrer() : Inferrer
>Inferrer : typeof Inferrer
if (inf.isNumber(numOrStr)) {
>inf.isNumber(numOrStr) : boolean
>inf.isNumber : (x: string | number) => x is number
>inf : Inferrer
>isNumber : (x: string | number) => x is number
>numOrStr : string | number
let t: number = numOrStr; // should ok
>t : number
>numOrStr : number
} else {
let t: string = numOrStr; // should ok
>t : string
>numOrStr : string
}
// Type predicates are not inferred on "this"
class C1 {
>C1 : C1
isC2() {
>isC2 : () => boolean
return this instanceof C2;
>this instanceof C2 : boolean
>this : this
>C2 : typeof C2
}
}
class C2 extends C1 {
>C2 : C2
>C1 : C1
z = 0;
>z : number
>0 : 0
}
declare let c: C1;
>c : C1
if (c.isC2()) {
>c.isC2() : boolean
>c.isC2 : () => boolean
>c : C1
>isC2 : () => boolean
let c2: C2 = c; // should error
>c2 : C2
>c : C1
}
function doNotRefineDestructuredParam({x, y}: {x: number | null, y: number}) {
>doNotRefineDestructuredParam : ({ x, y }: { x: number | null; y: number;}) => boolean
>x : number | null
>y : number
>x : number | null
>y : number
return typeof x === 'number';
>typeof x === 'number' : boolean
>typeof x : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function"
>x : number | null
>'number' : "number"
}
// The type predicate must remain valid when the function is called with subtypes.
function isShortString(x: unknown) {
>isShortString : (x: unknown) => boolean
>x : unknown
return typeof x === "string" && x.length < 10;
>typeof x === "string" && x.length < 10 : boolean
>typeof x === "string" : boolean
>typeof x : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function"
>x : unknown
>"string" : "string"
>x.length < 10 : boolean
>x.length : number
>x : string
>length : number
>10 : 10
}
declare let str: string;
>str : string
if (isShortString(str)) {
>isShortString(str) : boolean
>isShortString : (x: unknown) => boolean
>str : string
str.charAt(0); // should ok
>str.charAt(0) : string
>str.charAt : (pos: number) => string
>str : string
>charAt : (pos: number) => string
>0 : 0
} else {
str.charAt(0); // should ok
>str.charAt(0) : string
>str.charAt : (pos: number) => string
>str : string
>charAt : (pos: number) => string
>0 : 0
}
function isStringFromUnknown(x: unknown) {
>isStringFromUnknown : (x: unknown) => x is string
>x : unknown
return typeof x === "string";
>typeof x === "string" : boolean
>typeof x : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function"
>x : unknown
>"string" : "string"
}
if (isStringFromUnknown(str)) {
>isStringFromUnknown(str) : boolean
>isStringFromUnknown : (x: unknown) => x is string
>str : string
str.charAt(0); // should OK
>str.charAt(0) : string
>str.charAt : (pos: number) => string
>str : string
>charAt : (pos: number) => string
>0 : 0
} else {
let t: never = str; // should OK
>t : never
>str : never
}
// infer a union type
function isNumOrStr(x: unknown) {
>isNumOrStr : (x: unknown) => x is string | number
>x : unknown
return (typeof x === "number" || typeof x === "string");
>(typeof x === "number" || typeof x === "string") : boolean
>typeof x === "number" || typeof x === "string" : boolean
>typeof x === "number" : boolean
>typeof x : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function"
>x : unknown
>"number" : "number"
>typeof x === "string" : boolean
>typeof x : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function"
>x : unknown
>"string" : "string"
}
declare let unk: unknown;
>unk : unknown
if (isNumOrStr(unk)) {
>isNumOrStr(unk) : boolean
>isNumOrStr : (x: unknown) => x is string | number
>unk : unknown
let t: number | string = unk; // should ok
>t : string | number
>unk : string | number
}
// A function can be a type predicate even if it throws.
function assertAndPredicate(x: string | number | Date) {
>assertAndPredicate : (x: string | number | Date) => x is string
>x : string | number | Date
if (x instanceof Date) {
>x instanceof Date : boolean
>x : string | number | Date
>Date : DateConstructor
throw new Error();
>new Error() : Error
>Error : ErrorConstructor
}
return typeof x === 'string';
>typeof x === 'string' : boolean
>typeof x : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function"
>x : string | number
>'string' : "string"
}
declare let snd: string | number | Date;
>snd : string | number | Date
if (assertAndPredicate(snd)) {
>assertAndPredicate(snd) : boolean
>assertAndPredicate : (x: string | number | Date) => x is string
>snd : string | number | Date
let t: string = snd; // should error
>t : string
>snd : string
}
function isNumberWithThis(this: Date, x: number | string) {
>isNumberWithThis : (this: Date, x: number | string) => x is number
>this : Date
>x : string | number
return typeof x === 'number';
>typeof x === 'number' : boolean
>typeof x : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function"
>x : string | number
>'number' : "number"
}
function narrowFromAny(x: any) {
>narrowFromAny : (x: any) => x is number
>x : any
return typeof x === 'number';
>typeof x === 'number' : boolean
>typeof x : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function"
>x : any
>'number' : "number"
}
const noInferenceFromRest = (...f: ["a" | "b"]) => f[0] === "a";
>noInferenceFromRest : (f_0: "a" | "b") => boolean
>(...f: ["a" | "b"]) => f[0] === "a" : (f_0: "a" | "b") => boolean
>f : ["a" | "b"]
>f[0] === "a" : boolean
>f[0] : "a" | "b"
>f : ["a" | "b"]
>0 : 0
>"a" : "a"
const noInferenceFromImpossibleRest = (...f: []) => typeof f === "undefined";
>noInferenceFromImpossibleRest : () => boolean
>(...f: []) => typeof f === "undefined" : () => boolean
>f : []
>typeof f === "undefined" : boolean
>typeof f : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function"
>f : []
>"undefined" : "undefined"
function inferWithRest(x: string | null, ...f: ["a", "b"]) {
>inferWithRest : (x: string | null, f_0: "a", f_1: "b") => x is string
>x : string | null
>f : ["a", "b"]
return typeof x === 'string';
>typeof x === 'string' : boolean
>typeof x : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function"
>x : string | null
>'string' : "string"
}

View File

@ -1,30 +0,0 @@
/src/a.js(10,7): error TS2417: Class static side 'typeof ElementsArray' incorrectly extends base class static side '{ isArray(arg: any): arg is any[]; readonly prototype: any[]; }'.
Types of property 'isArray' are incompatible.
Type '(arg: any) => boolean' is not assignable to type '(arg: any) => arg is any[]'.
Signature '(arg: any): boolean' must be a type predicate.
==== /src/a.js (1 errors) ====
class Thing {
static {
this.doSomething = () => {};
}
}
Thing.doSomething();
// GH#46468
class ElementsArray extends Array {
~~~~~~~~~~~~~
!!! error TS2417: Class static side 'typeof ElementsArray' incorrectly extends base class static side '{ isArray(arg: any): arg is any[]; readonly prototype: any[]; }'.
!!! error TS2417: Types of property 'isArray' are incompatible.
!!! error TS2417: Type '(arg: any) => boolean' is not assignable to type '(arg: any) => arg is any[]'.
!!! error TS2417: Signature '(arg: any): boolean' must be a type predicate.
static {
const superisArray = super.isArray;
const customIsArray = (arg)=> superisArray(arg);
this.isArray = customIsArray;
}
}
ElementsArray.isArray(new ElementsArray());

View File

@ -33,27 +33,27 @@ class ElementsArray extends Array {
>isArray : (arg: any) => arg is any[]
const customIsArray = (arg)=> superisArray(arg);
>customIsArray : (arg: any) => boolean
>(arg)=> superisArray(arg) : (arg: any) => boolean
>customIsArray : (arg: any) => arg is any[]
>(arg)=> superisArray(arg) : (arg: any) => arg is any[]
>arg : any
>superisArray(arg) : boolean
>superisArray : (arg: any) => arg is any[]
>arg : any
this.isArray = customIsArray;
>this.isArray = customIsArray : (arg: any) => boolean
>this.isArray : (arg: any) => boolean
>this.isArray = customIsArray : (arg: any) => arg is any[]
>this.isArray : (arg: any) => arg is any[]
>this : typeof ElementsArray
>isArray : (arg: any) => boolean
>customIsArray : (arg: any) => boolean
>isArray : (arg: any) => arg is any[]
>customIsArray : (arg: any) => arg is any[]
}
}
ElementsArray.isArray(new ElementsArray());
>ElementsArray.isArray(new ElementsArray()) : boolean
>ElementsArray.isArray : (arg: any) => boolean
>ElementsArray.isArray : (arg: any) => arg is any[]
>ElementsArray : typeof ElementsArray
>isArray : (arg: any) => boolean
>isArray : (arg: any) => arg is any[]
>new ElementsArray() : ElementsArray
>ElementsArray : typeof ElementsArray

View File

@ -94,7 +94,7 @@ class PersonMixin extends Function {
>Function : Function
public check(o: any) {
>check : (o: any) => boolean
>check : (o: any) => o is Person
>o : any
return typeof o === "object" && o !== null && o instanceof Person;

View File

@ -0,0 +1,19 @@
// @strictNullChecks: true
// This should not be a circularity error. See
// https://github.com/microsoft/TypeScript/pull/57465#issuecomment-1960271216
export type Client = ReturnType<typeof getPrismaClient> extends new () => infer T ? T : never
export function getPrismaClient(options?: any) {
class PrismaClient {
self: Client;
constructor(options?: any) {
return (this.self = applyModelsAndClientExtensions(this));
}
}
return PrismaClient
}
export function applyModelsAndClientExtensions(client: Client) {
return client;
}

View File

@ -0,0 +1,271 @@
// @strictNullChecks: true
// @declaration: true
// https://github.com/microsoft/TypeScript/issues/16069
const numsOrNull = [1, 2, 3, 4, null];
const filteredNumsTruthy: number[] = numsOrNull.filter(x => !!x); // should error
const filteredNumsNonNullish: number[] = numsOrNull.filter(x => x !== null); // should ok
const evenSquaresInline: number[] = // should error
[1, 2, 3, 4]
.map(x => x % 2 === 0 ? x * x : null)
.filter(x => !!x); // tests truthiness, not non-nullishness
const isTruthy = (x: number | null) => !!x;
const evenSquares: number[] = // should error
[1, 2, 3, 4]
.map(x => x % 2 === 0 ? x * x : null)
.filter(isTruthy);
const evenSquaresNonNull: number[] = // should ok
[1, 2, 3, 4]
.map(x => x % 2 === 0 ? x * x : null)
.filter(x => x !== null);
function isNonNull(x: number | null) {
return x !== null;
}
// factoring out a boolean works thanks to aliased discriminants
function isNonNullVar(x: number | null) {
const ok = x !== null;
return ok;
}
function isNonNullGeneric<T>(x: T) {
return x !== null;
}
// Type guards can flow between functions
const myGuard = (o: string | undefined): o is string => !!o;
const mySecondGuard = (o: string | undefined) => myGuard(o);
// https://github.com/microsoft/TypeScript/issues/16069#issuecomment-1327449914
// This doesn't work because the false condition prevents type guard inference.
// Breaking up the filters does work.
type MyObj = { data?: string };
type MyArray = { list?: MyObj[] }[];
const myArray: MyArray = [];
const result = myArray
.map((arr) => arr.list)
.filter((arr) => arr && arr.length)
.map((arr) => arr // should error
.filter((obj) => obj && obj.data)
.map(obj => JSON.parse(obj.data)) // should error
);
const result2 = myArray
.map((arr) => arr.list)
.filter((arr) => !!arr)
.filter(arr => arr.length)
.map((arr) => arr // should ok
.filter((obj) => obj)
// inferring a guard here would require https://github.com/microsoft/TypeScript/issues/42384
.filter(obj => !!obj.data)
.map(obj => JSON.parse(obj.data))
);
// https://github.com/microsoft/TypeScript/issues/16069#issuecomment-1183547889
type Foo = {
foo: string;
}
type Bar = Foo & {
bar: string;
}
const list: (Foo | Bar)[] = [];
const resultBars: Bar[] = list.filter((value) => 'bar' in value); // should ok
function isBarNonNull(x: Foo | Bar | null) {
return ('bar' in x!);
}
const fooOrBar = list[0];
if (isBarNonNull(fooOrBar)) {
const t: Bar = fooOrBar; // should ok
}
// https://github.com/microsoft/TypeScript/issues/38390#issuecomment-626019466
// Ryan's example (currently legal):
const a = [1, "foo", 2, "bar"].filter(x => typeof x === "string");
a.push(10);
// Defer to explicit type guards, even when they're incorrect.
function backwardsGuard(x: number|string): x is number {
return typeof x === 'string';
}
// Partition tests. The "false" case matters.
function isString(x: string | number) {
return typeof x === 'string';
}
declare let strOrNum: string | number;
if (isString(strOrNum)) {
let t: string = strOrNum; // should ok
} else {
let t: number = strOrNum; // should ok
}
function flakyIsString(x: string | number) {
return typeof x === 'string' && Math.random() > 0.5;
}
if (flakyIsString(strOrNum)) {
let t: string = strOrNum; // should error
} else {
let t: number = strOrNum; // should error
}
function isDate(x: object) {
return x instanceof Date;
}
function flakyIsDate(x: object) {
return x instanceof Date && Math.random() > 0.5;
}
declare let maybeDate: object;
if (isDate(maybeDate)) {
let t: Date = maybeDate; // should ok
} else {
let t: object = maybeDate; // should ok
}
if (flakyIsDate(maybeDate)) {
let t: Date = maybeDate; // should error
} else {
let t: object = maybeDate; // should ok
}
// This should not infer a type guard since the value on which we do the refinement
// is not related to the original parameter.
function irrelevantIsNumber(x: string | number) {
x = Math.random() < 0.5 ? "string" : 123;
return typeof x === 'string';
}
function irrelevantIsNumberDestructuring(x: string | number) {
[x] = [Math.random() < 0.5 ? "string" : 123];
return typeof x === 'string';
}
// Cannot infer a type guard for either param because of the false case.
function areBothNums(x: string|number, y: string|number) {
return typeof x === 'number' && typeof y === 'number';
}
// Could potentially infer a type guard here but it would require more bookkeeping.
function doubleReturn(x: string|number) {
if (typeof x === 'string') {
return true;
}
return false;
}
function guardsOneButNotOthers(a: string|number, b: string|number, c: string|number) {
return typeof b === 'string';
}
// Checks that there are no string escaping issues
function dunderguard(__x: number | string) {
return typeof __x === 'string';
}
// could infer a type guard here but it doesn't seem that helpful.
const booleanIdentity = (x: boolean) => x;
// we infer "x is number | true" which is accurate but of debatable utility.
const numOrBoolean = (x: number | boolean) => typeof x === 'number' || x;
// inferred guards in methods
interface NumberInferrer {
isNumber(x: number | string): x is number;
}
class Inferrer implements NumberInferrer {
isNumber(x: number | string) { // should ok
return typeof x === 'number';
}
}
declare let numOrStr: number | string;
const inf = new Inferrer();
if (inf.isNumber(numOrStr)) {
let t: number = numOrStr; // should ok
} else {
let t: string = numOrStr; // should ok
}
// Type predicates are not inferred on "this"
class C1 {
isC2() {
return this instanceof C2;
}
}
class C2 extends C1 {
z = 0;
}
declare let c: C1;
if (c.isC2()) {
let c2: C2 = c; // should error
}
function doNotRefineDestructuredParam({x, y}: {x: number | null, y: number}) {
return typeof x === 'number';
}
// The type predicate must remain valid when the function is called with subtypes.
function isShortString(x: unknown) {
return typeof x === "string" && x.length < 10;
}
declare let str: string;
if (isShortString(str)) {
str.charAt(0); // should ok
} else {
str.charAt(0); // should ok
}
function isStringFromUnknown(x: unknown) {
return typeof x === "string";
}
if (isStringFromUnknown(str)) {
str.charAt(0); // should OK
} else {
let t: never = str; // should OK
}
// infer a union type
function isNumOrStr(x: unknown) {
return (typeof x === "number" || typeof x === "string");
}
declare let unk: unknown;
if (isNumOrStr(unk)) {
let t: number | string = unk; // should ok
}
// A function can be a type predicate even if it throws.
function assertAndPredicate(x: string | number | Date) {
if (x instanceof Date) {
throw new Error();
}
return typeof x === 'string';
}
declare let snd: string | number | Date;
if (assertAndPredicate(snd)) {
let t: string = snd; // should error
}
function isNumberWithThis(this: Date, x: number | string) {
return typeof x === 'number';
}
function narrowFromAny(x: any) {
return typeof x === 'number';
}
const noInferenceFromRest = (...f: ["a" | "b"]) => f[0] === "a";
const noInferenceFromImpossibleRest = (...f: []) => typeof f === "undefined";
function inferWithRest(x: string | null, ...f: ["a", "b"]) {
return typeof x === 'string';
}

View File

@ -65,5 +65,5 @@ verify.quickInfos({
7: "(method) GuardInterface.isFollower(): this is FollowerGuard",
13: "let leaderStatus: boolean",
14: "let checkedLeaderStatus: boolean",
15: "function isLeaderGuard(g: RoyalGuard): boolean"
15: "function isLeaderGuard(g: RoyalGuard): g is LeadGuard"
});