diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 7e9f3ced07fec..081bb1fb5e9ba 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -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) { diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index f9ab74b053256..a3a8a5f8ee7bd 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -15458,9 +15458,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)) { + const { declaration } = signature; + signature.resolvedTypePredicate = noTypePredicate; // avoid infinite loop + signature.resolvedTypePredicate = getTypePredicateFromBody(declaration) || noTypePredicate; + } + else { + signature.resolvedTypePredicate = noTypePredicate; + } } Debug.assert(!!signature.resolvedTypePredicate); } @@ -37389,6 +37399,119 @@ 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 || func.parameters.length === 0) 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)) { + // 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, + }; + + if (isBinaryExpression(expr)) { + // fast path for x => x === y and other similar patterns + // This can only be a type predicate if y is a unit type (e.g. null) but it can be quite expensive to determine that via the usual code path. + const { operatorToken } = expr; + const operator = operatorToken.kind; + let target; + switch (operator) { + case SyntaxKind.EqualsEqualsToken: + case SyntaxKind.ExclamationEqualsToken: + case SyntaxKind.EqualsEqualsEqualsToken: + case SyntaxKind.ExclamationEqualsEqualsToken: + const { left, right } = expr; + if (isMatchingReference(param.name, left)) { + target = right; + } + else if (isMatchingReference(param.name, right)) { + target = left; + } + } + if (target) { + const targetType = checkExpressionCached(target); + if (!isUnitType(targetType)) { + return undefined; + } + } + } + + 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, + }; + + if ((trueType.flags & TypeFlags.Union) && (trueType as UnionType).types.length >= 20) { + // For large unions, try the false check on just the first ten. + // If this is non-never then we can skip checking the remaining constituents. + const unionType = trueType as UnionType; + const head: UnionType = { ...unionType, types: unionType.types.slice(0, 10) }; + const falseSubTypeHead = getFlowTypeOfReference(param.name, head, head, func, falseCondition); + if (!(falseSubTypeHead.flags & TypeFlags.Never)) { + return undefined; + } + const rest: UnionType = { ...unionType, types: unionType.types.slice(10) }; + const falseSubTypeRest = getFlowTypeOfReference(param.name, rest, rest, func, falseCondition); + if (!(falseSubTypeRest.flags & TypeFlags.Never)) { + return undefined; + } + return trueType; + } + else { + const falseSubtype = getFlowTypeOfReference(param.name, trueType, trueType, func, falseCondition); + const isNever = !!(falseSubtype.flags & TypeFlags.Never); + return isNever ? trueType : undefined; + } + } + /** * TypeScript Specification 1.0 (6.3) - July 2014 * An explicitly typed function whose return type isn't the Void type, diff --git a/tests/baselines/reference/circularConstructorWithReturn.js b/tests/baselines/reference/circularConstructorWithReturn.js new file mode 100644 index 0000000000000..b045d23c7e36f --- /dev/null +++ b/tests/baselines/reference/circularConstructorWithReturn.js @@ -0,0 +1,41 @@ +//// [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 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.applyModelsAndClientExtensions = exports.getPrismaClient = void 0; +function getPrismaClient(options) { + var PrismaClient = /** @class */ (function () { + function PrismaClient(options) { + return (this.self = applyModelsAndClientExtensions(this)); + } + return PrismaClient; + }()); + return PrismaClient; +} +exports.getPrismaClient = getPrismaClient; +function applyModelsAndClientExtensions(client) { + return client; +} +exports.applyModelsAndClientExtensions = applyModelsAndClientExtensions; diff --git a/tests/baselines/reference/circularConstructorWithReturn.symbols b/tests/baselines/reference/circularConstructorWithReturn.symbols new file mode 100644 index 0000000000000..52cb36b6457a8 --- /dev/null +++ b/tests/baselines/reference/circularConstructorWithReturn.symbols @@ -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 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)) +} + diff --git a/tests/baselines/reference/circularConstructorWithReturn.types b/tests/baselines/reference/circularConstructorWithReturn.types new file mode 100644 index 0000000000000..53e59755f3942 --- /dev/null +++ b/tests/baselines/reference/circularConstructorWithReturn.types @@ -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 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 +} + diff --git a/tests/baselines/reference/findLast(target=esnext).types b/tests/baselines/reference/findLast(target=esnext).types index cd390c3a57f3b..d010b65027bab 100644 --- a/tests/baselines/reference/findLast(target=esnext).types +++ b/tests/baselines/reference/findLast(target=esnext).types @@ -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 : { (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 : { (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 : { (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 : { (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 : { (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 : { (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 : { (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 : { (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 : { (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 : { (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 : { (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 : { (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 : { (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 : { (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 : { (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 : { (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 : { (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 : { (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 : { (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 : { (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 : { (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 : { (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 diff --git a/tests/baselines/reference/importHelpersES6.types b/tests/baselines/reference/importHelpersES6.types index 9ed54f457c130..8ae8c0cc90de0 100644 --- a/tests/baselines/reference/importHelpersES6.types +++ b/tests/baselines/reference/importHelpersES6.types @@ -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 diff --git a/tests/baselines/reference/inKeywordAndUnknown.types b/tests/baselines/reference/inKeywordAndUnknown.types index 81a24b272970f..577e19857d586 100644 --- a/tests/baselines/reference/inKeywordAndUnknown.types +++ b/tests/baselines/reference/inKeywordAndUnknown.types @@ -79,7 +79,7 @@ function f1(x: unknown) { } function f2(x: T) { ->f2 : (x: T) => boolean +>f2 : (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(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(x: T) { ->f4 : (x: T) => boolean +>f4 : (x: T) => x is T & Object & Record<"a", unknown> >x : T return x instanceof Object && 'a' in x; @@ -123,7 +123,7 @@ function f4(x: T) { } function f5(x: T & {}) { ->f5 : (x: T & {}) => boolean +>f5 : (x: T & {}) => x is T & Object & Record<"a", unknown> >x : T & {} return x instanceof Object && 'a' in x; @@ -137,7 +137,7 @@ function f5(x: T & {}) { } function f6(x: T & {}) { ->f6 : (x: T & {}) => boolean +>f6 : (x: T & {}) => x is T & Object & Record<"a", unknown> >x : T return x instanceof Object && 'a' in x; @@ -151,7 +151,7 @@ function f6(x: T & {}) { } function f7(x: T & {}) { ->f7 : (x: T & {}) => boolean +>f7 : (x: T & {}) => x is T & Record<"a", unknown> >x : T return x instanceof Object && 'a' in x; diff --git a/tests/baselines/reference/inferTypePredicates.errors.txt b/tests/baselines/reference/inferTypePredicates.errors.txt new file mode 100644 index 0000000000000..0598c0a8440bd --- /dev/null +++ b/tests/baselines/reference/inferTypePredicates.errors.txt @@ -0,0 +1,313 @@ +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(205,7): error TS2741: Property 'z' is missing in type 'C1' but required in type 'C2'. + + +==== inferTypePredicates.ts (10 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(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): x is Date { + return x instanceof Date; + } + function flakyIsDate(x: object): x is Date { + return x instanceof Date; + } + + 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 ok + } 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 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'; + } + + // test fast path for '===' (which is usually not a type predicate) + declare let needle: string | number | boolean; + declare let haystack: (string | number | Date | RegExp)[]; + const finds = haystack.filter(n => n === needle); + \ No newline at end of file diff --git a/tests/baselines/reference/inferTypePredicates.js b/tests/baselines/reference/inferTypePredicates.js new file mode 100644 index 0000000000000..d2f21cfde401b --- /dev/null +++ b/tests/baselines/reference/inferTypePredicates.js @@ -0,0 +1,504 @@ +//// [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(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): x is Date { + return x instanceof Date; +} +function flakyIsDate(x: object): x is Date { + return x instanceof Date; +} + +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 ok +} 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 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'; +} + +// test fast path for '===' (which is usually not a type predicate) +declare let needle: string | number | boolean; +declare let haystack: (string | number | Date | RegExp)[]; +const finds = haystack.filter(n => n === needle); + + +//// [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; +} +if (isDate(maybeDate)) { + var t = maybeDate; // should ok +} +else { + var t = maybeDate; // should ok +} +if (flakyIsDate(maybeDate)) { + var t = maybeDate; // should ok +} +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 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 finds = haystack.filter(function (n) { return n === needle; }); diff --git a/tests/baselines/reference/inferTypePredicates.symbols b/tests/baselines/reference/inferTypePredicates.symbols new file mode 100644 index 0000000000000..3d55c1910f01a --- /dev/null +++ b/tests/baselines/reference/inferTypePredicates.symbols @@ -0,0 +1,748 @@ +//// [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(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): x is Date { +>isDate : Symbol(isDate, Decl(inferTypePredicates.ts, 115, 1)) +>x : Symbol(x, Decl(inferTypePredicates.ts, 117, 16)) +>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, --, --)) + + 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): x is Date { +>flakyIsDate : Symbol(flakyIsDate, Decl(inferTypePredicates.ts, 119, 1)) +>x : Symbol(x, Decl(inferTypePredicates.ts, 120, 21)) +>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, --, --)) + + return x instanceof Date; +>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, --, --)) +} + +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 ok +>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 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)) +} + +// test fast path for '===' (which is usually not a type predicate) +declare let needle: string | number | boolean; +>needle : Symbol(needle, Decl(inferTypePredicates.ts, 263, 11)) + +declare let haystack: (string | number | Date | RegExp)[]; +>haystack : Symbol(haystack, Decl(inferTypePredicates.ts, 264, 11)) +>Date : Symbol(Date, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.scripthost.d.ts, --, --)) +>RegExp : Symbol(RegExp, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) + +const finds = haystack.filter(n => n === needle); +>finds : Symbol(finds, Decl(inferTypePredicates.ts, 265, 5)) +>haystack.filter : Symbol(Array.filter, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>haystack : Symbol(haystack, Decl(inferTypePredicates.ts, 264, 11)) +>filter : Symbol(Array.filter, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>n : Symbol(n, Decl(inferTypePredicates.ts, 265, 30)) +>n : Symbol(n, Decl(inferTypePredicates.ts, 265, 30)) +>needle : Symbol(needle, Decl(inferTypePredicates.ts, 263, 11)) + diff --git a/tests/baselines/reference/inferTypePredicates.types b/tests/baselines/reference/inferTypePredicates.types new file mode 100644 index 0000000000000..39d0a6937cfe1 --- /dev/null +++ b/tests/baselines/reference/inferTypePredicates.types @@ -0,0 +1,962 @@ +//// [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 : { (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 : { (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 : { (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 : { (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 : { (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 : (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 : (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 : { (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 : { (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 : (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 : (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 : { (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 : { (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 : (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 : (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 : { (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(x: T) { +>isNonNullGeneric : (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 : (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 : { (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 : (callbackfn: (value: { list?: MyObj[] | undefined; }, index: number, array: { list?: MyObj[] | undefined; }[]) => U, thisArg?: any) => U[] +>myArray : MyArray + + .map((arr) => arr.list) +>map : (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 : { (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 : (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 : (callbackfn: (value: MyObj, index: number, array: MyObj[]) => U, thisArg?: any) => U[] +>arr // should error .filter((obj) => obj && obj.data) : MyObj[] +>arr // should error .filter : { (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 : { (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 : (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 : (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 : { (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 : { (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 : (callbackfn: (value: { list?: MyObj[] | undefined; }, index: number, array: { list?: MyObj[] | undefined; }[]) => U, thisArg?: any) => U[] +>myArray : MyArray + + .map((arr) => arr.list) +>map : (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 : { (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 : { (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 : (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 : (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 : { (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 : { (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 : { (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 : { (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 : (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 : { (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 : { (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 : { (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 : { (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): x is Date { +>isDate : (x: object) => x is Date +>x : object + + return x instanceof Date; +>x instanceof Date : boolean +>x : object +>Date : DateConstructor +} +function flakyIsDate(x: object): x is Date { +>flakyIsDate : (x: object) => x is Date +>x : object + + return x instanceof Date; +>x instanceof Date : boolean +>x : object +>Date : DateConstructor +} + +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) => 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 +} + +// 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 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" +} + +// test fast path for '===' (which is usually not a type predicate) +declare let needle: string | number | boolean; +>needle : string | number | boolean + +declare let haystack: (string | number | Date | RegExp)[]; +>haystack : (string | number | RegExp | Date)[] + +const finds = haystack.filter(n => n === needle); +>finds : (string | number | RegExp | Date)[] +>haystack.filter(n => n === needle) : (string | number | RegExp | Date)[] +>haystack.filter : { (predicate: (value: string | number | RegExp | Date, index: number, array: (string | number | RegExp | Date)[]) => value is S, thisArg?: any): S[]; (predicate: (value: string | number | RegExp | Date, index: number, array: (string | number | RegExp | Date)[]) => unknown, thisArg?: any): (string | number | RegExp | Date)[]; } +>haystack : (string | number | RegExp | Date)[] +>filter : { (predicate: (value: string | number | RegExp | Date, index: number, array: (string | number | RegExp | Date)[]) => value is S, thisArg?: any): S[]; (predicate: (value: string | number | RegExp | Date, index: number, array: (string | number | RegExp | Date)[]) => unknown, thisArg?: any): (string | number | RegExp | Date)[]; } +>n => n === needle : (n: string | number | RegExp | Date) => boolean +>n : string | number | RegExp | Date +>n === needle : boolean +>n : string | number | RegExp | Date +>needle : string | number | boolean + diff --git a/tests/baselines/reference/javascriptThisAssignmentInStaticBlock.errors.txt b/tests/baselines/reference/javascriptThisAssignmentInStaticBlock.errors.txt deleted file mode 100644 index a4729d56ef55b..0000000000000 --- a/tests/baselines/reference/javascriptThisAssignmentInStaticBlock.errors.txt +++ /dev/null @@ -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()); \ No newline at end of file diff --git a/tests/baselines/reference/javascriptThisAssignmentInStaticBlock.types b/tests/baselines/reference/javascriptThisAssignmentInStaticBlock.types index d3e93bca00dde..878d7ab799230 100644 --- a/tests/baselines/reference/javascriptThisAssignmentInStaticBlock.types +++ b/tests/baselines/reference/javascriptThisAssignmentInStaticBlock.types @@ -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 diff --git a/tests/baselines/reference/narrowByInstanceof.types b/tests/baselines/reference/narrowByInstanceof.types index 27900529f1fcc..a5d9169f25a23 100644 --- a/tests/baselines/reference/narrowByInstanceof.types +++ b/tests/baselines/reference/narrowByInstanceof.types @@ -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; diff --git a/tests/cases/compiler/circularConstructorWithReturn.ts b/tests/cases/compiler/circularConstructorWithReturn.ts new file mode 100644 index 0000000000000..ca65908103816 --- /dev/null +++ b/tests/cases/compiler/circularConstructorWithReturn.ts @@ -0,0 +1,18 @@ +// This should not be a circularity error. See +// https://github.com/microsoft/TypeScript/pull/57465#issuecomment-1960271216 +export type Client = ReturnType 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; +} diff --git a/tests/cases/compiler/inferTypePredicates.ts b/tests/cases/compiler/inferTypePredicates.ts new file mode 100644 index 0000000000000..3ed30fdb496a7 --- /dev/null +++ b/tests/cases/compiler/inferTypePredicates.ts @@ -0,0 +1,267 @@ +// @strictNullChecks: 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(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): x is Date { + return x instanceof Date; +} +function flakyIsDate(x: object): x is Date { + return x instanceof Date; +} + +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 ok +} 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 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'; +} + +// test fast path for '===' (which is usually not a type predicate) +declare let needle: string | number | boolean; +declare let haystack: (string | number | Date | RegExp)[]; +const finds = haystack.filter(n => n === needle); diff --git a/tests/cases/fourslash/thisPredicateFunctionQuickInfo.ts b/tests/cases/fourslash/thisPredicateFunctionQuickInfo.ts index 120a562607af8..cca30c4e8c8fb 100644 --- a/tests/cases/fourslash/thisPredicateFunctionQuickInfo.ts +++ b/tests/cases/fourslash/thisPredicateFunctionQuickInfo.ts @@ -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" });