Infer type predicates from function bodies

This commit is contained in:
Dan Vanderkam
2024-02-20 17:32:46 -05:00
parent 60f93aa83a
commit 0c24ccc049
13 changed files with 2402 additions and 83 deletions
+85 -3
View File
@@ -15458,9 +15458,17 @@ 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 === booleanType)) {
const {declaration} = signature;
signature.resolvedTypePredicate = noTypePredicate; // avoid infinite loop
signature.resolvedTypePredicate = getTypePredicateFromBody(declaration, signature) || noTypePredicate;
} else {
signature.resolvedTypePredicate = noTypePredicate;
}
}
Debug.assert(!!signature.resolvedTypePredicate);
}
@@ -37389,6 +37397,80 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}
}
function getTypePredicateFromBody(func: FunctionLikeDeclaration, _sig: Signature): TypePredicate | undefined {
const functionFlags = getFunctionFlags(func);
if (functionFlags !== FunctionFlags.Normal) return undefined;
// Only attempt to infer a type predicate if there's exactly one return.
let singleReturn: Expression | undefined;
if (func.body && func.body.kind !== SyntaxKind.Block) {
singleReturn = func.body; // arrow function
} else {
if (functionHasImplicitReturn(func)) return undefined;
const bailedEarly = forEachReturnStatement(func.body as Block, returnStatement => {
if (singleReturn || !returnStatement.expression) return true;
singleReturn = returnStatement.expression;
});
if (bailedEarly || !singleReturn) return undefined;
}
const predicate = checkIfExpressionRefinesAnyParameter(singleReturn);
if (predicate) {
const [i, type] = predicate;
const param = func.parameters[i];
if (isIdentifier(param.name)) {
// TODO: is there an alternative to the "as string" here? (It's __String)
return createTypePredicate(TypePredicateKind.Identifier, param.name.escapedText as string, i, type);
}
}
return undefined;
function checkIfExpressionRefinesAnyParameter(expr: Expression): [number, Type] | undefined {
expr = skipParentheses(expr, /*excludeJSDocTypeAssertions*/ true);
const type = checkExpressionCached(expr, CheckMode.TypeOnly);
if (type !== booleanType || !func.body) return undefined;
return forEach(func.parameters, (param, i) => {
const initType = getSymbolLinks(param.symbol).type;
if (!initType || initType === booleanType || isSymbolAssigned(param.symbol)) {
// Refining "x: boolean" to "x is true" or "x is false" isn't useful.
return;
}
const trueType = checkIfExpressionRefinesParameter(expr, param, initType);
if (trueType) {
return [i, trueType];
}
});
}
function checkIfExpressionRefinesParameter(expr: Expression, param: ParameterDeclaration, initType: Type): Type | undefined {
const antecedent = (expr as Expression & {flowNode?: FlowNode}).flowNode ?? { flags: FlowFlags.Start };
const trueCondition: FlowCondition = {
flags: FlowFlags.TrueCondition,
node: expr,
antecedent,
};
const trueType = getFlowTypeOfReference(param.name, initType, initType, func, trueCondition);
if (trueType === initType) return undefined;
// "x is T" means that x is T if and only if it returns true. If it returns false then x is not T.
// However, TS may not be able to represent "not T", in which case we can be more lax.
// It's safe to infer a type guard if falseType = Exclude<initType, trueType>
// This matches what you'd get if you called the type guard in an if/else statement.
const falseCondition: FlowCondition = {
...trueCondition,
flags: FlowFlags.FalseCondition,
}
const falseType = getFlowTypeOfReference(param.name, initType, initType, func, falseCondition);
const candidateFalse = filterType(initType, t => !isTypeSubtypeOf(t, trueType));
if (isTypeIdenticalTo(candidateFalse, falseType)) {
return trueType;
}
}
}
/**
* TypeScript Specification 1.0 (6.3) - July 2014
* An explicitly typed function whose return type isn't the Void type,
@@ -3,12 +3,12 @@
=== findLast.ts ===
const itemNumber: number | undefined = [0].findLast((item) => item === 0);
>itemNumber : number
>[0].findLast((item) => item === 0) : number
>[0].findLast((item) => item === 0) : 0
>[0].findLast : { <S extends number>(predicate: (value: number, index: number, array: number[]) => value is S, thisArg?: any): S; (predicate: (value: number, index: number, array: number[]) => unknown, thisArg?: any): number; }
>[0] : number[]
>0 : 0
>findLast : { <S extends number>(predicate: (value: number, index: number, array: number[]) => value is S, thisArg?: any): S; (predicate: (value: number, index: number, array: number[]) => unknown, thisArg?: any): number; }
>(item) => item === 0 : (item: number) => boolean
>(item) => item === 0 : (item: number) => item is 0
>item : number
>item === 0 : boolean
>item : number
@@ -16,120 +16,120 @@ const itemNumber: number | undefined = [0].findLast((item) => item === 0);
const itemString: string | undefined = ["string"].findLast((item) => item === "string");
>itemString : string
>["string"].findLast((item) => item === "string") : string
>["string"].findLast((item) => item === "string") : "string"
>["string"].findLast : { <S extends string>(predicate: (value: string, index: number, array: string[]) => value is S, thisArg?: any): S; (predicate: (value: string, index: number, array: string[]) => unknown, thisArg?: any): string; }
>["string"] : string[]
>"string" : "string"
>findLast : { <S extends string>(predicate: (value: string, index: number, array: string[]) => value is S, thisArg?: any): S; (predicate: (value: string, index: number, array: string[]) => unknown, thisArg?: any): string; }
>(item) => item === "string" : (item: string) => boolean
>(item) => item === "string" : (item: string) => item is "string"
>item : string
>item === "string" : boolean
>item : string
>"string" : "string"
new Int8Array().findLast((item) => item === 0);
>new Int8Array().findLast((item) => item === 0) : number
>new Int8Array().findLast((item) => item === 0) : 0
>new Int8Array().findLast : { <S extends number>(predicate: (value: number, index: number, array: Int8Array) => value is S, thisArg?: any): S; (predicate: (value: number, index: number, array: Int8Array) => unknown, thisArg?: any): number; }
>new Int8Array() : Int8Array
>Int8Array : Int8ArrayConstructor
>findLast : { <S extends number>(predicate: (value: number, index: number, array: Int8Array) => value is S, thisArg?: any): S; (predicate: (value: number, index: number, array: Int8Array) => unknown, thisArg?: any): number; }
>(item) => item === 0 : (item: number) => boolean
>(item) => item === 0 : (item: number) => item is 0
>item : number
>item === 0 : boolean
>item : number
>0 : 0
new Uint8Array().findLast((item) => item === 0);
>new Uint8Array().findLast((item) => item === 0) : number
>new Uint8Array().findLast((item) => item === 0) : 0
>new Uint8Array().findLast : { <S extends number>(predicate: (value: number, index: number, array: Uint8Array) => value is S, thisArg?: any): S; (predicate: (value: number, index: number, array: Uint8Array) => unknown, thisArg?: any): number; }
>new Uint8Array() : Uint8Array
>Uint8Array : Uint8ArrayConstructor
>findLast : { <S extends number>(predicate: (value: number, index: number, array: Uint8Array) => value is S, thisArg?: any): S; (predicate: (value: number, index: number, array: Uint8Array) => unknown, thisArg?: any): number; }
>(item) => item === 0 : (item: number) => boolean
>(item) => item === 0 : (item: number) => item is 0
>item : number
>item === 0 : boolean
>item : number
>0 : 0
new Uint8ClampedArray().findLast((item) => item === 0);
>new Uint8ClampedArray().findLast((item) => item === 0) : number
>new Uint8ClampedArray().findLast((item) => item === 0) : 0
>new Uint8ClampedArray().findLast : { <S extends number>(predicate: (value: number, index: number, array: Uint8ClampedArray) => value is S, thisArg?: any): S; (predicate: (value: number, index: number, array: Uint8ClampedArray) => unknown, thisArg?: any): number; }
>new Uint8ClampedArray() : Uint8ClampedArray
>Uint8ClampedArray : Uint8ClampedArrayConstructor
>findLast : { <S extends number>(predicate: (value: number, index: number, array: Uint8ClampedArray) => value is S, thisArg?: any): S; (predicate: (value: number, index: number, array: Uint8ClampedArray) => unknown, thisArg?: any): number; }
>(item) => item === 0 : (item: number) => boolean
>(item) => item === 0 : (item: number) => item is 0
>item : number
>item === 0 : boolean
>item : number
>0 : 0
new Int16Array().findLast((item) => item === 0);
>new Int16Array().findLast((item) => item === 0) : number
>new Int16Array().findLast((item) => item === 0) : 0
>new Int16Array().findLast : { <S extends number>(predicate: (value: number, index: number, array: Int16Array) => value is S, thisArg?: any): S; (predicate: (value: number, index: number, array: Int16Array) => unknown, thisArg?: any): number; }
>new Int16Array() : Int16Array
>Int16Array : Int16ArrayConstructor
>findLast : { <S extends number>(predicate: (value: number, index: number, array: Int16Array) => value is S, thisArg?: any): S; (predicate: (value: number, index: number, array: Int16Array) => unknown, thisArg?: any): number; }
>(item) => item === 0 : (item: number) => boolean
>(item) => item === 0 : (item: number) => item is 0
>item : number
>item === 0 : boolean
>item : number
>0 : 0
new Uint16Array().findLast((item) => item === 0);
>new Uint16Array().findLast((item) => item === 0) : number
>new Uint16Array().findLast((item) => item === 0) : 0
>new Uint16Array().findLast : { <S extends number>(predicate: (value: number, index: number, array: Uint16Array) => value is S, thisArg?: any): S; (predicate: (value: number, index: number, array: Uint16Array) => unknown, thisArg?: any): number; }
>new Uint16Array() : Uint16Array
>Uint16Array : Uint16ArrayConstructor
>findLast : { <S extends number>(predicate: (value: number, index: number, array: Uint16Array) => value is S, thisArg?: any): S; (predicate: (value: number, index: number, array: Uint16Array) => unknown, thisArg?: any): number; }
>(item) => item === 0 : (item: number) => boolean
>(item) => item === 0 : (item: number) => item is 0
>item : number
>item === 0 : boolean
>item : number
>0 : 0
new Int32Array().findLast((item) => item === 0);
>new Int32Array().findLast((item) => item === 0) : number
>new Int32Array().findLast((item) => item === 0) : 0
>new Int32Array().findLast : { <S extends number>(predicate: (value: number, index: number, array: Int32Array) => value is S, thisArg?: any): S; (predicate: (value: number, index: number, array: Int32Array) => unknown, thisArg?: any): number; }
>new Int32Array() : Int32Array
>Int32Array : Int32ArrayConstructor
>findLast : { <S extends number>(predicate: (value: number, index: number, array: Int32Array) => value is S, thisArg?: any): S; (predicate: (value: number, index: number, array: Int32Array) => unknown, thisArg?: any): number; }
>(item) => item === 0 : (item: number) => boolean
>(item) => item === 0 : (item: number) => item is 0
>item : number
>item === 0 : boolean
>item : number
>0 : 0
new Uint32Array().findLast((item) => item === 0);
>new Uint32Array().findLast((item) => item === 0) : number
>new Uint32Array().findLast((item) => item === 0) : 0
>new Uint32Array().findLast : { <S extends number>(predicate: (value: number, index: number, array: Uint32Array) => value is S, thisArg?: any): S; (predicate: (value: number, index: number, array: Uint32Array) => unknown, thisArg?: any): number; }
>new Uint32Array() : Uint32Array
>Uint32Array : Uint32ArrayConstructor
>findLast : { <S extends number>(predicate: (value: number, index: number, array: Uint32Array) => value is S, thisArg?: any): S; (predicate: (value: number, index: number, array: Uint32Array) => unknown, thisArg?: any): number; }
>(item) => item === 0 : (item: number) => boolean
>(item) => item === 0 : (item: number) => item is 0
>item : number
>item === 0 : boolean
>item : number
>0 : 0
new Float32Array().findLast((item) => item === 0);
>new Float32Array().findLast((item) => item === 0) : number
>new Float32Array().findLast((item) => item === 0) : 0
>new Float32Array().findLast : { <S extends number>(predicate: (value: number, index: number, array: Float32Array) => value is S, thisArg?: any): S; (predicate: (value: number, index: number, array: Float32Array) => unknown, thisArg?: any): number; }
>new Float32Array() : Float32Array
>Float32Array : Float32ArrayConstructor
>findLast : { <S extends number>(predicate: (value: number, index: number, array: Float32Array) => value is S, thisArg?: any): S; (predicate: (value: number, index: number, array: Float32Array) => unknown, thisArg?: any): number; }
>(item) => item === 0 : (item: number) => boolean
>(item) => item === 0 : (item: number) => item is 0
>item : number
>item === 0 : boolean
>item : number
>0 : 0
new Float64Array().findLast((item) => item === 0);
>new Float64Array().findLast((item) => item === 0) : number
>new Float64Array().findLast((item) => item === 0) : 0
>new Float64Array().findLast : { <S extends number>(predicate: (value: number, index: number, array: Float64Array) => value is S, thisArg?: any): S; (predicate: (value: number, index: number, array: Float64Array) => unknown, thisArg?: any): number; }
>new Float64Array() : Float64Array
>Float64Array : Float64ArrayConstructor
>findLast : { <S extends number>(predicate: (value: number, index: number, array: Float64Array) => value is S, thisArg?: any): S; (predicate: (value: number, index: number, array: Float64Array) => unknown, thisArg?: any): number; }
>(item) => item === 0 : (item: number) => boolean
>(item) => item === 0 : (item: number) => item is 0
>item : number
>item === 0 : boolean
>item : number
@@ -170,7 +170,7 @@ const indexNumber: number = [0].findLastIndex((item) => item === 0);
>[0] : number[]
>0 : 0
>findLastIndex : (predicate: (value: number, index: number, array: number[]) => unknown, thisArg?: any) => number
>(item) => item === 0 : (item: number) => boolean
>(item) => item === 0 : (item: number) => item is 0
>item : number
>item === 0 : boolean
>item : number
@@ -183,7 +183,7 @@ const indexString: number = ["string"].findLastIndex((item) => item === "string"
>["string"] : string[]
>"string" : "string"
>findLastIndex : (predicate: (value: string, index: number, array: string[]) => unknown, thisArg?: any) => number
>(item) => item === "string" : (item: string) => boolean
>(item) => item === "string" : (item: string) => item is "string"
>item : string
>item === "string" : boolean
>item : string
@@ -195,7 +195,7 @@ new Int8Array().findLastIndex((item) => item === 0);
>new Int8Array() : Int8Array
>Int8Array : Int8ArrayConstructor
>findLastIndex : (predicate: (value: number, index: number, array: Int8Array) => unknown, thisArg?: any) => number
>(item) => item === 0 : (item: number) => boolean
>(item) => item === 0 : (item: number) => item is 0
>item : number
>item === 0 : boolean
>item : number
@@ -207,7 +207,7 @@ new Uint8Array().findLastIndex((item) => item === 0);
>new Uint8Array() : Uint8Array
>Uint8Array : Uint8ArrayConstructor
>findLastIndex : (predicate: (value: number, index: number, array: Uint8Array) => unknown, thisArg?: any) => number
>(item) => item === 0 : (item: number) => boolean
>(item) => item === 0 : (item: number) => item is 0
>item : number
>item === 0 : boolean
>item : number
@@ -219,7 +219,7 @@ new Uint8ClampedArray().findLastIndex((item) => item === 0);
>new Uint8ClampedArray() : Uint8ClampedArray
>Uint8ClampedArray : Uint8ClampedArrayConstructor
>findLastIndex : (predicate: (value: number, index: number, array: Uint8ClampedArray) => unknown, thisArg?: any) => number
>(item) => item === 0 : (item: number) => boolean
>(item) => item === 0 : (item: number) => item is 0
>item : number
>item === 0 : boolean
>item : number
@@ -231,7 +231,7 @@ new Int16Array().findLastIndex((item) => item === 0);
>new Int16Array() : Int16Array
>Int16Array : Int16ArrayConstructor
>findLastIndex : (predicate: (value: number, index: number, array: Int16Array) => unknown, thisArg?: any) => number
>(item) => item === 0 : (item: number) => boolean
>(item) => item === 0 : (item: number) => item is 0
>item : number
>item === 0 : boolean
>item : number
@@ -243,7 +243,7 @@ new Uint16Array().findLastIndex((item) => item === 0);
>new Uint16Array() : Uint16Array
>Uint16Array : Uint16ArrayConstructor
>findLastIndex : (predicate: (value: number, index: number, array: Uint16Array) => unknown, thisArg?: any) => number
>(item) => item === 0 : (item: number) => boolean
>(item) => item === 0 : (item: number) => item is 0
>item : number
>item === 0 : boolean
>item : number
@@ -255,7 +255,7 @@ new Int32Array().findLastIndex((item) => item === 0);
>new Int32Array() : Int32Array
>Int32Array : Int32ArrayConstructor
>findLastIndex : (predicate: (value: number, index: number, array: Int32Array) => unknown, thisArg?: any) => number
>(item) => item === 0 : (item: number) => boolean
>(item) => item === 0 : (item: number) => item is 0
>item : number
>item === 0 : boolean
>item : number
@@ -267,7 +267,7 @@ new Uint32Array().findLastIndex((item) => item === 0);
>new Uint32Array() : Uint32Array
>Uint32Array : Uint32ArrayConstructor
>findLastIndex : (predicate: (value: number, index: number, array: Uint32Array) => unknown, thisArg?: any) => number
>(item) => item === 0 : (item: number) => boolean
>(item) => item === 0 : (item: number) => item is 0
>item : number
>item === 0 : boolean
>item : number
@@ -279,7 +279,7 @@ new Float32Array().findLastIndex((item) => item === 0);
>new Float32Array() : Float32Array
>Float32Array : Float32ArrayConstructor
>findLastIndex : (predicate: (value: number, index: number, array: Float32Array) => unknown, thisArg?: any) => number
>(item) => item === 0 : (item: number) => boolean
>(item) => item === 0 : (item: number) => item is 0
>item : number
>item === 0 : boolean
>item : number
@@ -291,7 +291,7 @@ new Float64Array().findLastIndex((item) => item === 0);
>new Float64Array() : Float64Array
>Float64Array : Float64ArrayConstructor
>findLastIndex : (predicate: (value: number, index: number, array: Float64Array) => unknown, thisArg?: any) => number
>(item) => item === 0 : (item: number) => boolean
>(item) => item === 0 : (item: number) => item is 0
>item : number
>item === 0 : boolean
>item : number
@@ -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
@@ -79,7 +79,7 @@ function f1(x: unknown) {
}
function f2<T>(x: T) {
>f2 : <T>(x: T) => boolean
>f2 : <T>(x: T) => x is T & Object & Record<"a", unknown>
>x : T
return x && x instanceof Object && 'a' in x;
@@ -95,7 +95,7 @@ function f2<T>(x: T) {
}
function f3(x: {}) {
>f3 : (x: {}) => boolean
>f3 : (x: {}) => x is Object & Record<"a", unknown>
>x : {}
return x instanceof Object && 'a' in x;
@@ -109,7 +109,7 @@ function f3(x: {}) {
}
function f4<T extends {}>(x: T) {
>f4 : <T extends {}>(x: T) => boolean
>f4 : <T extends {}>(x: T) => x is T & Object & Record<"a", unknown>
>x : T
return x instanceof Object && 'a' in x;
@@ -123,7 +123,7 @@ function f4<T extends {}>(x: T) {
}
function f5<T>(x: T & {}) {
>f5 : <T>(x: T & {}) => boolean
>f5 : <T>(x: T & {}) => x is T & Object & Record<"a", unknown>
>x : T & {}
return x instanceof Object && 'a' in x;
@@ -137,7 +137,7 @@ function f5<T>(x: T & {}) {
}
function f6<T extends {}>(x: T & {}) {
>f6 : <T extends {}>(x: T & {}) => boolean
>f6 : <T extends {}>(x: T & {}) => x is T & Object & Record<"a", unknown>
>x : T
return x instanceof Object && 'a' in x;
@@ -151,7 +151,7 @@ function f6<T extends {}>(x: T & {}) {
}
function f7<T extends object>(x: T & {}) {
>f7 : <T extends object>(x: T & {}) => boolean
>f7 : <T extends object>(x: T & {}) => x is T & Record<"a", unknown>
>x : T
return x instanceof Object && 'a' in x;
@@ -0,0 +1,257 @@
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<T>(x: T) {
return x !== null;
}
// Type guards can flow between functions
const myGuard = (o: string | undefined): o is string => !!o;
const mySecondGuard = (o: string | undefined) => myGuard(o);
// https://github.com/microsoft/TypeScript/issues/16069#issuecomment-1327449914
// This doesn't work because the false condition prevents type guard inference.
// Breaking up the filters does work.
type MyObj = { data?: string };
type MyArray = { list?: MyObj[] }[];
const myArray: MyArray = [];
const result = myArray
.map((arr) => arr.list)
.filter((arr) => arr && arr.length)
.map((arr) => arr // should error
~~~
!!! error TS18048: 'arr' is possibly 'undefined'.
.filter((obj) => obj && obj.data)
.map(obj => JSON.parse(obj.data)) // should error
~~~~~~~~
!!! error TS2345: Argument of type 'string | undefined' is not assignable to parameter of type 'string'.
!!! error TS2345: Type 'undefined' is not assignable to type 'string'.
);
const result2 = myArray
.map((arr) => arr.list)
.filter((arr) => !!arr)
.filter(arr => arr.length)
.map((arr) => arr // should ok
.filter((obj) => obj)
// inferring a guard here would require https://github.com/microsoft/TypeScript/issues/42384
.filter(obj => !!obj.data)
.map(obj => JSON.parse(obj.data))
~~~~~~~~
!!! error TS2345: Argument of type 'string | undefined' is not assignable to parameter of type 'string'.
!!! error TS2345: Type 'undefined' is not assignable to type 'string'.
);
// https://github.com/microsoft/TypeScript/issues/16069#issuecomment-1183547889
type Foo = {
foo: string;
}
type Bar = Foo & {
bar: string;
}
const list: (Foo | Bar)[] = [];
const resultBars: Bar[] = list.filter((value) => 'bar' in value); // should ok
function isBarNonNull(x: Foo | Bar | null) {
return ('bar' in x!);
}
const fooOrBar = list[0];
if (isBarNonNull(fooOrBar)) {
const t: Bar = fooOrBar; // should ok
}
// https://github.com/microsoft/TypeScript/issues/38390#issuecomment-626019466
// Ryan's example (currently legal):
const a = [1, "foo", 2, "bar"].filter(x => typeof x === "string");
a.push(10);
~~
!!! error TS2345: Argument of type 'number' is not assignable to parameter of type 'string'.
// Defer to explicit type guards, even when they're incorrect.
function backwardsGuard(x: number|string): x is number {
return typeof x === 'string';
}
// Partition tests. The "false" case matters.
function isString(x: string | number) {
return typeof x === 'string';
}
declare let strOrNum: string | number;
if (isString(strOrNum)) {
let t: string = strOrNum; // should ok
} else {
let t: number = strOrNum; // should ok
}
function flakyIsString(x: string | number) {
return typeof x === 'string' && Math.random() > 0.5;
}
if (flakyIsString(strOrNum)) {
let t: string = strOrNum; // should error
~
!!! error TS2322: Type 'string | number' is not assignable to type 'string'.
!!! error TS2322: Type 'number' is not assignable to type 'string'.
} else {
let t: number = strOrNum; // should error
~
!!! error TS2322: Type 'string | number' is not assignable to type 'number'.
!!! error TS2322: Type 'string' is not assignable to type 'number'.
}
function isDate(x: object): 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';
}
// String escaping issue (please help!)
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;
// could infer "x is number | true" but don't; debateable whether that's helpful.
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';
}
@@ -0,0 +1,405 @@
//// [tests/cases/compiler/inferTypePredicates.ts] ////
//// [inferTypePredicates.ts]
// https://github.com/microsoft/TypeScript/issues/16069
const numsOrNull = [1, 2, 3, 4, null];
const filteredNumsTruthy: number[] = numsOrNull.filter(x => !!x); // should error
const filteredNumsNonNullish: number[] = numsOrNull.filter(x => x !== null); // should ok
const evenSquaresInline: number[] = // should error
[1, 2, 3, 4]
.map(x => x % 2 === 0 ? x * x : null)
.filter(x => !!x); // tests truthiness, not non-nullishness
const isTruthy = (x: number | null) => !!x;
const evenSquares: number[] = // should error
[1, 2, 3, 4]
.map(x => x % 2 === 0 ? x * x : null)
.filter(isTruthy);
const evenSquaresNonNull: number[] = // should ok
[1, 2, 3, 4]
.map(x => x % 2 === 0 ? x * x : null)
.filter(x => x !== null);
function isNonNull(x: number | null) {
return x !== null;
}
// factoring out a boolean works thanks to aliased discriminants
function isNonNullVar(x: number | null) {
const ok = x !== null;
return ok;
}
function isNonNullGeneric<T>(x: T) {
return x !== null;
}
// Type guards can flow between functions
const myGuard = (o: string | undefined): o is string => !!o;
const mySecondGuard = (o: string | undefined) => myGuard(o);
// https://github.com/microsoft/TypeScript/issues/16069#issuecomment-1327449914
// This doesn't work because the false condition prevents type guard inference.
// Breaking up the filters does work.
type MyObj = { data?: string };
type MyArray = { list?: MyObj[] }[];
const myArray: MyArray = [];
const result = myArray
.map((arr) => arr.list)
.filter((arr) => arr && arr.length)
.map((arr) => arr // should error
.filter((obj) => obj && obj.data)
.map(obj => JSON.parse(obj.data)) // should error
);
const result2 = myArray
.map((arr) => arr.list)
.filter((arr) => !!arr)
.filter(arr => arr.length)
.map((arr) => arr // should ok
.filter((obj) => obj)
// inferring a guard here would require https://github.com/microsoft/TypeScript/issues/42384
.filter(obj => !!obj.data)
.map(obj => JSON.parse(obj.data))
);
// https://github.com/microsoft/TypeScript/issues/16069#issuecomment-1183547889
type Foo = {
foo: string;
}
type Bar = Foo & {
bar: string;
}
const list: (Foo | Bar)[] = [];
const resultBars: Bar[] = list.filter((value) => 'bar' in value); // should ok
function isBarNonNull(x: Foo | Bar | null) {
return ('bar' in x!);
}
const fooOrBar = list[0];
if (isBarNonNull(fooOrBar)) {
const t: Bar = fooOrBar; // should ok
}
// https://github.com/microsoft/TypeScript/issues/38390#issuecomment-626019466
// Ryan's example (currently legal):
const a = [1, "foo", 2, "bar"].filter(x => typeof x === "string");
a.push(10);
// Defer to explicit type guards, even when they're incorrect.
function backwardsGuard(x: number|string): x is number {
return typeof x === 'string';
}
// Partition tests. The "false" case matters.
function isString(x: string | number) {
return typeof x === 'string';
}
declare let strOrNum: string | number;
if (isString(strOrNum)) {
let t: string = strOrNum; // should ok
} else {
let t: number = strOrNum; // should ok
}
function flakyIsString(x: string | number) {
return typeof x === 'string' && Math.random() > 0.5;
}
if (flakyIsString(strOrNum)) {
let t: string = strOrNum; // should error
} else {
let t: number = strOrNum; // should error
}
function isDate(x: object): 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';
}
// String escaping issue (please help!)
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;
// could infer "x is number | true" but don't; debateable whether that's helpful.
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';
}
//// [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';
}
// String escaping issue (please help!)
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; };
// could infer "x is number | true" but don't; debateable whether that's helpful.
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';
}
@@ -0,0 +1,608 @@
//// [tests/cases/compiler/inferTypePredicates.ts] ////
=== inferTypePredicates.ts ===
// https://github.com/microsoft/TypeScript/issues/16069
const numsOrNull = [1, 2, 3, 4, null];
>numsOrNull : Symbol(numsOrNull, Decl(inferTypePredicates.ts, 2, 5))
const filteredNumsTruthy: number[] = numsOrNull.filter(x => !!x); // should error
>filteredNumsTruthy : Symbol(filteredNumsTruthy, Decl(inferTypePredicates.ts, 3, 5))
>numsOrNull.filter : Symbol(Array.filter, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>numsOrNull : Symbol(numsOrNull, Decl(inferTypePredicates.ts, 2, 5))
>filter : Symbol(Array.filter, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>x : Symbol(x, Decl(inferTypePredicates.ts, 3, 55))
>x : Symbol(x, Decl(inferTypePredicates.ts, 3, 55))
const filteredNumsNonNullish: number[] = numsOrNull.filter(x => x !== null); // should ok
>filteredNumsNonNullish : Symbol(filteredNumsNonNullish, Decl(inferTypePredicates.ts, 4, 5))
>numsOrNull.filter : Symbol(Array.filter, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>numsOrNull : Symbol(numsOrNull, Decl(inferTypePredicates.ts, 2, 5))
>filter : Symbol(Array.filter, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>x : Symbol(x, Decl(inferTypePredicates.ts, 4, 59))
>x : Symbol(x, Decl(inferTypePredicates.ts, 4, 59))
const evenSquaresInline: number[] = // should error
>evenSquaresInline : Symbol(evenSquaresInline, Decl(inferTypePredicates.ts, 6, 5))
[1, 2, 3, 4]
>[1, 2, 3, 4] .map(x => x % 2 === 0 ? x * x : null) .filter : Symbol(Array.filter, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>[1, 2, 3, 4] .map : Symbol(Array.map, Decl(lib.es5.d.ts, --, --))
.map(x => x % 2 === 0 ? x * x : null)
>map : Symbol(Array.map, Decl(lib.es5.d.ts, --, --))
>x : Symbol(x, Decl(inferTypePredicates.ts, 8, 13))
>x : Symbol(x, Decl(inferTypePredicates.ts, 8, 13))
>x : Symbol(x, Decl(inferTypePredicates.ts, 8, 13))
>x : Symbol(x, Decl(inferTypePredicates.ts, 8, 13))
.filter(x => !!x); // tests truthiness, not non-nullishness
>filter : Symbol(Array.filter, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>x : Symbol(x, Decl(inferTypePredicates.ts, 9, 16))
>x : Symbol(x, Decl(inferTypePredicates.ts, 9, 16))
const isTruthy = (x: number | null) => !!x;
>isTruthy : Symbol(isTruthy, Decl(inferTypePredicates.ts, 11, 5))
>x : Symbol(x, Decl(inferTypePredicates.ts, 11, 18))
>x : Symbol(x, Decl(inferTypePredicates.ts, 11, 18))
const evenSquares: number[] = // should error
>evenSquares : Symbol(evenSquares, Decl(inferTypePredicates.ts, 13, 5))
[1, 2, 3, 4]
>[1, 2, 3, 4] .map(x => x % 2 === 0 ? x * x : null) .filter : Symbol(Array.filter, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>[1, 2, 3, 4] .map : Symbol(Array.map, Decl(lib.es5.d.ts, --, --))
.map(x => x % 2 === 0 ? x * x : null)
>map : Symbol(Array.map, Decl(lib.es5.d.ts, --, --))
>x : Symbol(x, Decl(inferTypePredicates.ts, 15, 9))
>x : Symbol(x, Decl(inferTypePredicates.ts, 15, 9))
>x : Symbol(x, Decl(inferTypePredicates.ts, 15, 9))
>x : Symbol(x, Decl(inferTypePredicates.ts, 15, 9))
.filter(isTruthy);
>filter : Symbol(Array.filter, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>isTruthy : Symbol(isTruthy, Decl(inferTypePredicates.ts, 11, 5))
const evenSquaresNonNull: number[] = // should ok
>evenSquaresNonNull : Symbol(evenSquaresNonNull, Decl(inferTypePredicates.ts, 18, 5))
[1, 2, 3, 4]
>[1, 2, 3, 4] .map(x => x % 2 === 0 ? x * x : null) .filter : Symbol(Array.filter, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>[1, 2, 3, 4] .map : Symbol(Array.map, Decl(lib.es5.d.ts, --, --))
.map(x => x % 2 === 0 ? x * x : null)
>map : Symbol(Array.map, Decl(lib.es5.d.ts, --, --))
>x : Symbol(x, Decl(inferTypePredicates.ts, 20, 9))
>x : Symbol(x, Decl(inferTypePredicates.ts, 20, 9))
>x : Symbol(x, Decl(inferTypePredicates.ts, 20, 9))
>x : Symbol(x, Decl(inferTypePredicates.ts, 20, 9))
.filter(x => x !== null);
>filter : Symbol(Array.filter, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>x : Symbol(x, Decl(inferTypePredicates.ts, 21, 12))
>x : Symbol(x, Decl(inferTypePredicates.ts, 21, 12))
function isNonNull(x: number | null) {
>isNonNull : Symbol(isNonNull, Decl(inferTypePredicates.ts, 21, 29))
>x : Symbol(x, Decl(inferTypePredicates.ts, 23, 19))
return x !== null;
>x : Symbol(x, Decl(inferTypePredicates.ts, 23, 19))
}
// factoring out a boolean works thanks to aliased discriminants
function isNonNullVar(x: number | null) {
>isNonNullVar : Symbol(isNonNullVar, Decl(inferTypePredicates.ts, 25, 1))
>x : Symbol(x, Decl(inferTypePredicates.ts, 28, 22))
const ok = x !== null;
>ok : Symbol(ok, Decl(inferTypePredicates.ts, 29, 7))
>x : Symbol(x, Decl(inferTypePredicates.ts, 28, 22))
return ok;
>ok : Symbol(ok, Decl(inferTypePredicates.ts, 29, 7))
}
function isNonNullGeneric<T>(x: T) {
>isNonNullGeneric : Symbol(isNonNullGeneric, Decl(inferTypePredicates.ts, 31, 1))
>T : Symbol(T, Decl(inferTypePredicates.ts, 33, 26))
>x : Symbol(x, Decl(inferTypePredicates.ts, 33, 29))
>T : Symbol(T, Decl(inferTypePredicates.ts, 33, 26))
return x !== null;
>x : Symbol(x, Decl(inferTypePredicates.ts, 33, 29))
}
// Type guards can flow between functions
const myGuard = (o: string | undefined): o is string => !!o;
>myGuard : Symbol(myGuard, Decl(inferTypePredicates.ts, 38, 5))
>o : Symbol(o, Decl(inferTypePredicates.ts, 38, 17))
>o : Symbol(o, Decl(inferTypePredicates.ts, 38, 17))
>o : Symbol(o, Decl(inferTypePredicates.ts, 38, 17))
const mySecondGuard = (o: string | undefined) => myGuard(o);
>mySecondGuard : Symbol(mySecondGuard, Decl(inferTypePredicates.ts, 39, 5))
>o : Symbol(o, Decl(inferTypePredicates.ts, 39, 23))
>myGuard : Symbol(myGuard, Decl(inferTypePredicates.ts, 38, 5))
>o : Symbol(o, Decl(inferTypePredicates.ts, 39, 23))
// https://github.com/microsoft/TypeScript/issues/16069#issuecomment-1327449914
// This doesn't work because the false condition prevents type guard inference.
// Breaking up the filters does work.
type MyObj = { data?: string };
>MyObj : Symbol(MyObj, Decl(inferTypePredicates.ts, 39, 60))
>data : Symbol(data, Decl(inferTypePredicates.ts, 44, 14))
type MyArray = { list?: MyObj[] }[];
>MyArray : Symbol(MyArray, Decl(inferTypePredicates.ts, 44, 31))
>list : Symbol(list, Decl(inferTypePredicates.ts, 45, 16))
>MyObj : Symbol(MyObj, Decl(inferTypePredicates.ts, 39, 60))
const myArray: MyArray = [];
>myArray : Symbol(myArray, Decl(inferTypePredicates.ts, 46, 5))
>MyArray : Symbol(MyArray, Decl(inferTypePredicates.ts, 44, 31))
const result = myArray
>result : Symbol(result, Decl(inferTypePredicates.ts, 48, 5))
>myArray .map((arr) => arr.list) .filter((arr) => arr && arr.length) .map : Symbol(Array.map, Decl(lib.es5.d.ts, --, --))
>myArray .map((arr) => arr.list) .filter : Symbol(Array.filter, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>myArray .map : Symbol(Array.map, Decl(lib.es5.d.ts, --, --))
>myArray : Symbol(myArray, Decl(inferTypePredicates.ts, 46, 5))
.map((arr) => arr.list)
>map : Symbol(Array.map, Decl(lib.es5.d.ts, --, --))
>arr : Symbol(arr, Decl(inferTypePredicates.ts, 49, 8))
>arr.list : Symbol(list, Decl(inferTypePredicates.ts, 45, 16))
>arr : Symbol(arr, Decl(inferTypePredicates.ts, 49, 8))
>list : Symbol(list, Decl(inferTypePredicates.ts, 45, 16))
.filter((arr) => arr && arr.length)
>filter : Symbol(Array.filter, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>arr : Symbol(arr, Decl(inferTypePredicates.ts, 50, 11))
>arr : Symbol(arr, Decl(inferTypePredicates.ts, 50, 11))
>arr.length : Symbol(Array.length, Decl(lib.es5.d.ts, --, --))
>arr : Symbol(arr, Decl(inferTypePredicates.ts, 50, 11))
>length : Symbol(Array.length, Decl(lib.es5.d.ts, --, --))
.map((arr) => arr // should error
>map : Symbol(Array.map, Decl(lib.es5.d.ts, --, --))
>arr : Symbol(arr, Decl(inferTypePredicates.ts, 51, 8))
>arr // should error .filter((obj) => obj && obj.data) .map : Symbol(Array.map, Decl(lib.es5.d.ts, --, --))
>arr // should error .filter : Symbol(Array.filter, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>arr : Symbol(arr, Decl(inferTypePredicates.ts, 51, 8))
.filter((obj) => obj && obj.data)
>filter : Symbol(Array.filter, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>obj : Symbol(obj, Decl(inferTypePredicates.ts, 52, 13))
>obj : Symbol(obj, Decl(inferTypePredicates.ts, 52, 13))
>obj.data : Symbol(data, Decl(inferTypePredicates.ts, 44, 14))
>obj : Symbol(obj, Decl(inferTypePredicates.ts, 52, 13))
>data : Symbol(data, Decl(inferTypePredicates.ts, 44, 14))
.map(obj => JSON.parse(obj.data)) // should error
>map : Symbol(Array.map, Decl(lib.es5.d.ts, --, --))
>obj : Symbol(obj, Decl(inferTypePredicates.ts, 53, 9))
>JSON.parse : Symbol(JSON.parse, Decl(lib.es5.d.ts, --, --))
>JSON : Symbol(JSON, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>parse : Symbol(JSON.parse, Decl(lib.es5.d.ts, --, --))
>obj.data : Symbol(data, Decl(inferTypePredicates.ts, 44, 14))
>obj : Symbol(obj, Decl(inferTypePredicates.ts, 53, 9))
>data : Symbol(data, Decl(inferTypePredicates.ts, 44, 14))
);
const result2 = myArray
>result2 : Symbol(result2, Decl(inferTypePredicates.ts, 56, 5))
>myArray .map((arr) => arr.list) .filter((arr) => !!arr) .filter(arr => arr.length) .map : Symbol(Array.map, Decl(lib.es5.d.ts, --, --))
>myArray .map((arr) => arr.list) .filter((arr) => !!arr) .filter : Symbol(Array.filter, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>myArray .map((arr) => arr.list) .filter : Symbol(Array.filter, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>myArray .map : Symbol(Array.map, Decl(lib.es5.d.ts, --, --))
>myArray : Symbol(myArray, Decl(inferTypePredicates.ts, 46, 5))
.map((arr) => arr.list)
>map : Symbol(Array.map, Decl(lib.es5.d.ts, --, --))
>arr : Symbol(arr, Decl(inferTypePredicates.ts, 57, 8))
>arr.list : Symbol(list, Decl(inferTypePredicates.ts, 45, 16))
>arr : Symbol(arr, Decl(inferTypePredicates.ts, 57, 8))
>list : Symbol(list, Decl(inferTypePredicates.ts, 45, 16))
.filter((arr) => !!arr)
>filter : Symbol(Array.filter, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>arr : Symbol(arr, Decl(inferTypePredicates.ts, 58, 11))
>arr : Symbol(arr, Decl(inferTypePredicates.ts, 58, 11))
.filter(arr => arr.length)
>filter : Symbol(Array.filter, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>arr : Symbol(arr, Decl(inferTypePredicates.ts, 59, 10))
>arr.length : Symbol(Array.length, Decl(lib.es5.d.ts, --, --))
>arr : Symbol(arr, Decl(inferTypePredicates.ts, 59, 10))
>length : Symbol(Array.length, Decl(lib.es5.d.ts, --, --))
.map((arr) => arr // should ok
>map : Symbol(Array.map, Decl(lib.es5.d.ts, --, --))
>arr : Symbol(arr, Decl(inferTypePredicates.ts, 60, 8))
>arr // should ok .filter((obj) => obj) // inferring a guard here would require https://github.com/microsoft/TypeScript/issues/42384 .filter(obj => !!obj.data) .map : Symbol(Array.map, Decl(lib.es5.d.ts, --, --))
>arr // should ok .filter((obj) => obj) // inferring a guard here would require https://github.com/microsoft/TypeScript/issues/42384 .filter : Symbol(Array.filter, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>arr // should ok .filter : Symbol(Array.filter, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>arr : Symbol(arr, Decl(inferTypePredicates.ts, 60, 8))
.filter((obj) => obj)
>filter : Symbol(Array.filter, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>obj : Symbol(obj, Decl(inferTypePredicates.ts, 61, 13))
>obj : Symbol(obj, Decl(inferTypePredicates.ts, 61, 13))
// inferring a guard here would require https://github.com/microsoft/TypeScript/issues/42384
.filter(obj => !!obj.data)
>filter : Symbol(Array.filter, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>obj : Symbol(obj, Decl(inferTypePredicates.ts, 63, 12))
>obj.data : Symbol(data, Decl(inferTypePredicates.ts, 44, 14))
>obj : Symbol(obj, Decl(inferTypePredicates.ts, 63, 12))
>data : Symbol(data, Decl(inferTypePredicates.ts, 44, 14))
.map(obj => JSON.parse(obj.data))
>map : Symbol(Array.map, Decl(lib.es5.d.ts, --, --))
>obj : Symbol(obj, Decl(inferTypePredicates.ts, 64, 9))
>JSON.parse : Symbol(JSON.parse, Decl(lib.es5.d.ts, --, --))
>JSON : Symbol(JSON, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>parse : Symbol(JSON.parse, Decl(lib.es5.d.ts, --, --))
>obj.data : Symbol(data, Decl(inferTypePredicates.ts, 44, 14))
>obj : Symbol(obj, Decl(inferTypePredicates.ts, 64, 9))
>data : Symbol(data, Decl(inferTypePredicates.ts, 44, 14))
);
// https://github.com/microsoft/TypeScript/issues/16069#issuecomment-1183547889
type Foo = {
>Foo : Symbol(Foo, Decl(inferTypePredicates.ts, 65, 4))
foo: string;
>foo : Symbol(foo, Decl(inferTypePredicates.ts, 68, 12))
}
type Bar = Foo & {
>Bar : Symbol(Bar, Decl(inferTypePredicates.ts, 70, 1))
>Foo : Symbol(Foo, Decl(inferTypePredicates.ts, 65, 4))
bar: string;
>bar : Symbol(bar, Decl(inferTypePredicates.ts, 71, 18))
}
const list: (Foo | Bar)[] = [];
>list : Symbol(list, Decl(inferTypePredicates.ts, 75, 5))
>Foo : Symbol(Foo, Decl(inferTypePredicates.ts, 65, 4))
>Bar : Symbol(Bar, Decl(inferTypePredicates.ts, 70, 1))
const resultBars: Bar[] = list.filter((value) => 'bar' in value); // should ok
>resultBars : Symbol(resultBars, Decl(inferTypePredicates.ts, 76, 5))
>Bar : Symbol(Bar, Decl(inferTypePredicates.ts, 70, 1))
>list.filter : Symbol(Array.filter, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>list : Symbol(list, Decl(inferTypePredicates.ts, 75, 5))
>filter : Symbol(Array.filter, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>value : Symbol(value, Decl(inferTypePredicates.ts, 76, 39))
>value : Symbol(value, Decl(inferTypePredicates.ts, 76, 39))
function isBarNonNull(x: Foo | Bar | null) {
>isBarNonNull : Symbol(isBarNonNull, Decl(inferTypePredicates.ts, 76, 65))
>x : Symbol(x, Decl(inferTypePredicates.ts, 78, 22))
>Foo : Symbol(Foo, Decl(inferTypePredicates.ts, 65, 4))
>Bar : Symbol(Bar, Decl(inferTypePredicates.ts, 70, 1))
return ('bar' in x!);
>x : Symbol(x, Decl(inferTypePredicates.ts, 78, 22))
}
const fooOrBar = list[0];
>fooOrBar : Symbol(fooOrBar, Decl(inferTypePredicates.ts, 81, 5))
>list : Symbol(list, Decl(inferTypePredicates.ts, 75, 5))
if (isBarNonNull(fooOrBar)) {
>isBarNonNull : Symbol(isBarNonNull, Decl(inferTypePredicates.ts, 76, 65))
>fooOrBar : Symbol(fooOrBar, Decl(inferTypePredicates.ts, 81, 5))
const t: Bar = fooOrBar; // should ok
>t : Symbol(t, Decl(inferTypePredicates.ts, 83, 7))
>Bar : Symbol(Bar, Decl(inferTypePredicates.ts, 70, 1))
>fooOrBar : Symbol(fooOrBar, Decl(inferTypePredicates.ts, 81, 5))
}
// https://github.com/microsoft/TypeScript/issues/38390#issuecomment-626019466
// Ryan's example (currently legal):
const a = [1, "foo", 2, "bar"].filter(x => typeof x === "string");
>a : Symbol(a, Decl(inferTypePredicates.ts, 88, 5))
>[1, "foo", 2, "bar"].filter : Symbol(Array.filter, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>filter : Symbol(Array.filter, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>x : Symbol(x, Decl(inferTypePredicates.ts, 88, 38))
>x : Symbol(x, Decl(inferTypePredicates.ts, 88, 38))
a.push(10);
>a.push : Symbol(Array.push, Decl(lib.es5.d.ts, --, --))
>a : Symbol(a, Decl(inferTypePredicates.ts, 88, 5))
>push : Symbol(Array.push, Decl(lib.es5.d.ts, --, --))
// Defer to explicit type guards, even when they're incorrect.
function backwardsGuard(x: number|string): x is number {
>backwardsGuard : Symbol(backwardsGuard, Decl(inferTypePredicates.ts, 89, 11))
>x : Symbol(x, Decl(inferTypePredicates.ts, 92, 24))
>x : Symbol(x, Decl(inferTypePredicates.ts, 92, 24))
return typeof x === 'string';
>x : Symbol(x, Decl(inferTypePredicates.ts, 92, 24))
}
// Partition tests. The "false" case matters.
function isString(x: string | number) {
>isString : Symbol(isString, Decl(inferTypePredicates.ts, 94, 1))
>x : Symbol(x, Decl(inferTypePredicates.ts, 97, 18))
return typeof x === 'string';
>x : Symbol(x, Decl(inferTypePredicates.ts, 97, 18))
}
declare let strOrNum: string | number;
>strOrNum : Symbol(strOrNum, Decl(inferTypePredicates.ts, 101, 11))
if (isString(strOrNum)) {
>isString : Symbol(isString, Decl(inferTypePredicates.ts, 94, 1))
>strOrNum : Symbol(strOrNum, Decl(inferTypePredicates.ts, 101, 11))
let t: string = strOrNum; // should ok
>t : Symbol(t, Decl(inferTypePredicates.ts, 103, 5))
>strOrNum : Symbol(strOrNum, Decl(inferTypePredicates.ts, 101, 11))
} else {
let t: number = strOrNum; // should ok
>t : Symbol(t, Decl(inferTypePredicates.ts, 105, 5))
>strOrNum : Symbol(strOrNum, Decl(inferTypePredicates.ts, 101, 11))
}
function flakyIsString(x: string | number) {
>flakyIsString : Symbol(flakyIsString, Decl(inferTypePredicates.ts, 106, 1))
>x : Symbol(x, Decl(inferTypePredicates.ts, 108, 23))
return typeof x === 'string' && Math.random() > 0.5;
>x : Symbol(x, Decl(inferTypePredicates.ts, 108, 23))
>Math.random : Symbol(Math.random, Decl(lib.es5.d.ts, --, --))
>Math : Symbol(Math, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>random : Symbol(Math.random, Decl(lib.es5.d.ts, --, --))
}
if (flakyIsString(strOrNum)) {
>flakyIsString : Symbol(flakyIsString, Decl(inferTypePredicates.ts, 106, 1))
>strOrNum : Symbol(strOrNum, Decl(inferTypePredicates.ts, 101, 11))
let t: string = strOrNum; // should error
>t : Symbol(t, Decl(inferTypePredicates.ts, 112, 5))
>strOrNum : Symbol(strOrNum, Decl(inferTypePredicates.ts, 101, 11))
} else {
let t: number = strOrNum; // should error
>t : Symbol(t, Decl(inferTypePredicates.ts, 114, 5))
>strOrNum : Symbol(strOrNum, Decl(inferTypePredicates.ts, 101, 11))
}
function isDate(x: object): 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))
}
// String escaping issue (please help!)
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))
// could infer "x is number | true" but don't; debateable whether that's helpful.
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))
}
@@ -0,0 +1,786 @@
//// [tests/cases/compiler/inferTypePredicates.ts] ////
=== inferTypePredicates.ts ===
// https://github.com/microsoft/TypeScript/issues/16069
const numsOrNull = [1, 2, 3, 4, null];
>numsOrNull : (number | null)[]
>[1, 2, 3, 4, null] : (number | null)[]
>1 : 1
>2 : 2
>3 : 3
>4 : 4
const filteredNumsTruthy: number[] = numsOrNull.filter(x => !!x); // should error
>filteredNumsTruthy : number[]
>numsOrNull.filter(x => !!x) : (number | null)[]
>numsOrNull.filter : { <S extends number | null>(predicate: (value: number | null, index: number, array: (number | null)[]) => value is S, thisArg?: any): S[]; (predicate: (value: number | null, index: number, array: (number | null)[]) => unknown, thisArg?: any): (number | null)[]; }
>numsOrNull : (number | null)[]
>filter : { <S extends number | null>(predicate: (value: number | null, index: number, array: (number | null)[]) => value is S, thisArg?: any): S[]; (predicate: (value: number | null, index: number, array: (number | null)[]) => unknown, thisArg?: any): (number | null)[]; }
>x => !!x : (x: number | null) => boolean
>x : number | null
>!!x : boolean
>!x : boolean
>x : number | null
const filteredNumsNonNullish: number[] = numsOrNull.filter(x => x !== null); // should ok
>filteredNumsNonNullish : number[]
>numsOrNull.filter(x => x !== null) : number[]
>numsOrNull.filter : { <S extends number | null>(predicate: (value: number | null, index: number, array: (number | null)[]) => value is S, thisArg?: any): S[]; (predicate: (value: number | null, index: number, array: (number | null)[]) => unknown, thisArg?: any): (number | null)[]; }
>numsOrNull : (number | null)[]
>filter : { <S extends number | null>(predicate: (value: number | null, index: number, array: (number | null)[]) => value is S, thisArg?: any): S[]; (predicate: (value: number | null, index: number, array: (number | null)[]) => unknown, thisArg?: any): (number | null)[]; }
>x => x !== null : (x: number | null) => x is number
>x : number | null
>x !== null : boolean
>x : number | null
const evenSquaresInline: number[] = // should error
>evenSquaresInline : number[]
[1, 2, 3, 4]
>[1, 2, 3, 4] .map(x => x % 2 === 0 ? x * x : null) .filter(x => !!x) : (number | null)[]
>[1, 2, 3, 4] .map(x => x % 2 === 0 ? x * x : null) .filter : { <S extends number | null>(predicate: (value: number | null, index: number, array: (number | null)[]) => value is S, thisArg?: any): S[]; (predicate: (value: number | null, index: number, array: (number | null)[]) => unknown, thisArg?: any): (number | null)[]; }
>[1, 2, 3, 4] .map(x => x % 2 === 0 ? x * x : null) : (number | null)[]
>[1, 2, 3, 4] .map : <U>(callbackfn: (value: number, index: number, array: number[]) => U, thisArg?: any) => U[]
>[1, 2, 3, 4] : number[]
>1 : 1
>2 : 2
>3 : 3
>4 : 4
.map(x => x % 2 === 0 ? x * x : null)
>map : <U>(callbackfn: (value: number, index: number, array: number[]) => U, thisArg?: any) => U[]
>x => x % 2 === 0 ? x * x : null : (x: number) => number | null
>x : number
>x % 2 === 0 ? x * x : null : number | null
>x % 2 === 0 : boolean
>x % 2 : number
>x : number
>2 : 2
>0 : 0
>x * x : number
>x : number
>x : number
.filter(x => !!x); // tests truthiness, not non-nullishness
>filter : { <S extends number | null>(predicate: (value: number | null, index: number, array: (number | null)[]) => value is S, thisArg?: any): S[]; (predicate: (value: number | null, index: number, array: (number | null)[]) => unknown, thisArg?: any): (number | null)[]; }
>x => !!x : (x: number | null) => boolean
>x : number | null
>!!x : boolean
>!x : boolean
>x : number | null
const isTruthy = (x: number | null) => !!x;
>isTruthy : (x: number | null) => boolean
>(x: number | null) => !!x : (x: number | null) => boolean
>x : number | null
>!!x : boolean
>!x : boolean
>x : number | null
const evenSquares: number[] = // should error
>evenSquares : number[]
[1, 2, 3, 4]
>[1, 2, 3, 4] .map(x => x % 2 === 0 ? x * x : null) .filter(isTruthy) : (number | null)[]
>[1, 2, 3, 4] .map(x => x % 2 === 0 ? x * x : null) .filter : { <S extends number | null>(predicate: (value: number | null, index: number, array: (number | null)[]) => value is S, thisArg?: any): S[]; (predicate: (value: number | null, index: number, array: (number | null)[]) => unknown, thisArg?: any): (number | null)[]; }
>[1, 2, 3, 4] .map(x => x % 2 === 0 ? x * x : null) : (number | null)[]
>[1, 2, 3, 4] .map : <U>(callbackfn: (value: number, index: number, array: number[]) => U, thisArg?: any) => U[]
>[1, 2, 3, 4] : number[]
>1 : 1
>2 : 2
>3 : 3
>4 : 4
.map(x => x % 2 === 0 ? x * x : null)
>map : <U>(callbackfn: (value: number, index: number, array: number[]) => U, thisArg?: any) => U[]
>x => x % 2 === 0 ? x * x : null : (x: number) => number | null
>x : number
>x % 2 === 0 ? x * x : null : number | null
>x % 2 === 0 : boolean
>x % 2 : number
>x : number
>2 : 2
>0 : 0
>x * x : number
>x : number
>x : number
.filter(isTruthy);
>filter : { <S extends number | null>(predicate: (value: number | null, index: number, array: (number | null)[]) => value is S, thisArg?: any): S[]; (predicate: (value: number | null, index: number, array: (number | null)[]) => unknown, thisArg?: any): (number | null)[]; }
>isTruthy : (x: number | null) => boolean
const evenSquaresNonNull: number[] = // should ok
>evenSquaresNonNull : number[]
[1, 2, 3, 4]
>[1, 2, 3, 4] .map(x => x % 2 === 0 ? x * x : null) .filter(x => x !== null) : number[]
>[1, 2, 3, 4] .map(x => x % 2 === 0 ? x * x : null) .filter : { <S extends number | null>(predicate: (value: number | null, index: number, array: (number | null)[]) => value is S, thisArg?: any): S[]; (predicate: (value: number | null, index: number, array: (number | null)[]) => unknown, thisArg?: any): (number | null)[]; }
>[1, 2, 3, 4] .map(x => x % 2 === 0 ? x * x : null) : (number | null)[]
>[1, 2, 3, 4] .map : <U>(callbackfn: (value: number, index: number, array: number[]) => U, thisArg?: any) => U[]
>[1, 2, 3, 4] : number[]
>1 : 1
>2 : 2
>3 : 3
>4 : 4
.map(x => x % 2 === 0 ? x * x : null)
>map : <U>(callbackfn: (value: number, index: number, array: number[]) => U, thisArg?: any) => U[]
>x => x % 2 === 0 ? x * x : null : (x: number) => number | null
>x : number
>x % 2 === 0 ? x * x : null : number | null
>x % 2 === 0 : boolean
>x % 2 : number
>x : number
>2 : 2
>0 : 0
>x * x : number
>x : number
>x : number
.filter(x => x !== null);
>filter : { <S extends number | null>(predicate: (value: number | null, index: number, array: (number | null)[]) => value is S, thisArg?: any): S[]; (predicate: (value: number | null, index: number, array: (number | null)[]) => unknown, thisArg?: any): (number | null)[]; }
>x => x !== null : (x: number | null) => x is number
>x : number | null
>x !== null : boolean
>x : number | null
function isNonNull(x: number | null) {
>isNonNull : (x: number | null) => x is number
>x : number | null
return x !== null;
>x !== null : boolean
>x : number | null
}
// factoring out a boolean works thanks to aliased discriminants
function isNonNullVar(x: number | null) {
>isNonNullVar : (x: number | null) => x is number
>x : number | null
const ok = x !== null;
>ok : boolean
>x !== null : boolean
>x : number | null
return ok;
>ok : boolean
}
function isNonNullGeneric<T>(x: T) {
>isNonNullGeneric : <T>(x: T) => x is T & ({} | undefined)
>x : T
return x !== null;
>x !== null : boolean
>x : T
}
// Type guards can flow between functions
const myGuard = (o: string | undefined): o is string => !!o;
>myGuard : (o: string | undefined) => o is string
>(o: string | undefined): o is string => !!o : (o: string | undefined) => o is string
>o : string | undefined
>!!o : boolean
>!o : boolean
>o : string | undefined
const mySecondGuard = (o: string | undefined) => myGuard(o);
>mySecondGuard : (o: string | undefined) => o is string
>(o: string | undefined) => myGuard(o) : (o: string | undefined) => o is string
>o : string | undefined
>myGuard(o) : boolean
>myGuard : (o: string | undefined) => o is string
>o : string | undefined
// https://github.com/microsoft/TypeScript/issues/16069#issuecomment-1327449914
// This doesn't work because the false condition prevents type guard inference.
// Breaking up the filters does work.
type MyObj = { data?: string };
>MyObj : { data?: string | undefined; }
>data : string | undefined
type MyArray = { list?: MyObj[] }[];
>MyArray : { list?: MyObj[] | undefined; }[]
>list : MyObj[] | undefined
const myArray: MyArray = [];
>myArray : MyArray
>[] : never[]
const result = myArray
>result : any[][]
>myArray .map((arr) => arr.list) .filter((arr) => arr && arr.length) .map((arr) => arr // should error .filter((obj) => obj && obj.data) .map(obj => JSON.parse(obj.data)) // should error ) : any[][]
>myArray .map((arr) => arr.list) .filter((arr) => arr && arr.length) .map : <U>(callbackfn: (value: MyObj[] | undefined, index: number, array: (MyObj[] | undefined)[]) => U, thisArg?: any) => U[]
>myArray .map((arr) => arr.list) .filter((arr) => arr && arr.length) : (MyObj[] | undefined)[]
>myArray .map((arr) => arr.list) .filter : { <S extends MyObj[] | undefined>(predicate: (value: MyObj[] | undefined, index: number, array: (MyObj[] | undefined)[]) => value is S, thisArg?: any): S[]; (predicate: (value: MyObj[] | undefined, index: number, array: (MyObj[] | undefined)[]) => unknown, thisArg?: any): (MyObj[] | undefined)[]; }
>myArray .map((arr) => arr.list) : (MyObj[] | undefined)[]
>myArray .map : <U>(callbackfn: (value: { list?: MyObj[] | undefined; }, index: number, array: { list?: MyObj[] | undefined; }[]) => U, thisArg?: any) => U[]
>myArray : MyArray
.map((arr) => arr.list)
>map : <U>(callbackfn: (value: { list?: MyObj[] | undefined; }, index: number, array: { list?: MyObj[] | undefined; }[]) => U, thisArg?: any) => U[]
>(arr) => arr.list : (arr: { list?: MyObj[] | undefined; }) => MyObj[] | undefined
>arr : { list?: MyObj[] | undefined; }
>arr.list : MyObj[] | undefined
>arr : { list?: MyObj[] | undefined; }
>list : MyObj[] | undefined
.filter((arr) => arr && arr.length)
>filter : { <S extends MyObj[] | undefined>(predicate: (value: MyObj[] | undefined, index: number, array: (MyObj[] | undefined)[]) => value is S, thisArg?: any): S[]; (predicate: (value: MyObj[] | undefined, index: number, array: (MyObj[] | undefined)[]) => unknown, thisArg?: any): (MyObj[] | undefined)[]; }
>(arr) => arr && arr.length : (arr: MyObj[] | undefined) => number | undefined
>arr : MyObj[] | undefined
>arr && arr.length : number | undefined
>arr : MyObj[] | undefined
>arr.length : number
>arr : MyObj[]
>length : number
.map((arr) => arr // should error
>map : <U>(callbackfn: (value: MyObj[] | undefined, index: number, array: (MyObj[] | undefined)[]) => U, thisArg?: any) => U[]
>(arr) => arr // should error .filter((obj) => obj && obj.data) .map(obj => JSON.parse(obj.data)) : (arr: MyObj[] | undefined) => any[]
>arr : MyObj[] | undefined
>arr // should error .filter((obj) => obj && obj.data) .map(obj => JSON.parse(obj.data)) : any[]
>arr // should error .filter((obj) => obj && obj.data) .map : <U>(callbackfn: (value: MyObj, index: number, array: MyObj[]) => U, thisArg?: any) => U[]
>arr // should error .filter((obj) => obj && obj.data) : MyObj[]
>arr // should error .filter : { <S extends MyObj>(predicate: (value: MyObj, index: number, array: MyObj[]) => value is S, thisArg?: any): S[]; (predicate: (value: MyObj, index: number, array: MyObj[]) => unknown, thisArg?: any): MyObj[]; }
>arr : MyObj[] | undefined
.filter((obj) => obj && obj.data)
>filter : { <S extends MyObj>(predicate: (value: MyObj, index: number, array: MyObj[]) => value is S, thisArg?: any): S[]; (predicate: (value: MyObj, index: number, array: MyObj[]) => unknown, thisArg?: any): MyObj[]; }
>(obj) => obj && obj.data : (obj: MyObj) => string | undefined
>obj : MyObj
>obj && obj.data : string | undefined
>obj : MyObj
>obj.data : string | undefined
>obj : MyObj
>data : string | undefined
.map(obj => JSON.parse(obj.data)) // should error
>map : <U>(callbackfn: (value: MyObj, index: number, array: MyObj[]) => U, thisArg?: any) => U[]
>obj => JSON.parse(obj.data) : (obj: MyObj) => any
>obj : MyObj
>JSON.parse(obj.data) : any
>JSON.parse : (text: string, reviver?: ((this: any, key: string, value: any) => any) | undefined) => any
>JSON : JSON
>parse : (text: string, reviver?: ((this: any, key: string, value: any) => any) | undefined) => any
>obj.data : string | undefined
>obj : MyObj
>data : string | undefined
);
const result2 = myArray
>result2 : any[][]
>myArray .map((arr) => arr.list) .filter((arr) => !!arr) .filter(arr => arr.length) .map((arr) => arr // should ok .filter((obj) => obj) // inferring a guard here would require https://github.com/microsoft/TypeScript/issues/42384 .filter(obj => !!obj.data) .map(obj => JSON.parse(obj.data)) ) : any[][]
>myArray .map((arr) => arr.list) .filter((arr) => !!arr) .filter(arr => arr.length) .map : <U>(callbackfn: (value: MyObj[], index: number, array: MyObj[][]) => U, thisArg?: any) => U[]
>myArray .map((arr) => arr.list) .filter((arr) => !!arr) .filter(arr => arr.length) : MyObj[][]
>myArray .map((arr) => arr.list) .filter((arr) => !!arr) .filter : { <S extends MyObj[]>(predicate: (value: MyObj[], index: number, array: MyObj[][]) => value is S, thisArg?: any): S[]; (predicate: (value: MyObj[], index: number, array: MyObj[][]) => unknown, thisArg?: any): MyObj[][]; }
>myArray .map((arr) => arr.list) .filter((arr) => !!arr) : MyObj[][]
>myArray .map((arr) => arr.list) .filter : { <S extends MyObj[] | undefined>(predicate: (value: MyObj[] | undefined, index: number, array: (MyObj[] | undefined)[]) => value is S, thisArg?: any): S[]; (predicate: (value: MyObj[] | undefined, index: number, array: (MyObj[] | undefined)[]) => unknown, thisArg?: any): (MyObj[] | undefined)[]; }
>myArray .map((arr) => arr.list) : (MyObj[] | undefined)[]
>myArray .map : <U>(callbackfn: (value: { list?: MyObj[] | undefined; }, index: number, array: { list?: MyObj[] | undefined; }[]) => U, thisArg?: any) => U[]
>myArray : MyArray
.map((arr) => arr.list)
>map : <U>(callbackfn: (value: { list?: MyObj[] | undefined; }, index: number, array: { list?: MyObj[] | undefined; }[]) => U, thisArg?: any) => U[]
>(arr) => arr.list : (arr: { list?: MyObj[] | undefined; }) => MyObj[] | undefined
>arr : { list?: MyObj[] | undefined; }
>arr.list : MyObj[] | undefined
>arr : { list?: MyObj[] | undefined; }
>list : MyObj[] | undefined
.filter((arr) => !!arr)
>filter : { <S extends MyObj[] | undefined>(predicate: (value: MyObj[] | undefined, index: number, array: (MyObj[] | undefined)[]) => value is S, thisArg?: any): S[]; (predicate: (value: MyObj[] | undefined, index: number, array: (MyObj[] | undefined)[]) => unknown, thisArg?: any): (MyObj[] | undefined)[]; }
>(arr) => !!arr : (arr: MyObj[] | undefined) => arr is MyObj[]
>arr : MyObj[] | undefined
>!!arr : boolean
>!arr : boolean
>arr : MyObj[] | undefined
.filter(arr => arr.length)
>filter : { <S extends MyObj[]>(predicate: (value: MyObj[], index: number, array: MyObj[][]) => value is S, thisArg?: any): S[]; (predicate: (value: MyObj[], index: number, array: MyObj[][]) => unknown, thisArg?: any): MyObj[][]; }
>arr => arr.length : (arr: MyObj[]) => number
>arr : MyObj[]
>arr.length : number
>arr : MyObj[]
>length : number
.map((arr) => arr // should ok
>map : <U>(callbackfn: (value: MyObj[], index: number, array: MyObj[][]) => U, thisArg?: any) => U[]
>(arr) => arr // should ok .filter((obj) => obj) // inferring a guard here would require https://github.com/microsoft/TypeScript/issues/42384 .filter(obj => !!obj.data) .map(obj => JSON.parse(obj.data)) : (arr: MyObj[]) => any[]
>arr : MyObj[]
>arr // should ok .filter((obj) => obj) // inferring a guard here would require https://github.com/microsoft/TypeScript/issues/42384 .filter(obj => !!obj.data) .map(obj => JSON.parse(obj.data)) : any[]
>arr // should ok .filter((obj) => obj) // inferring a guard here would require https://github.com/microsoft/TypeScript/issues/42384 .filter(obj => !!obj.data) .map : <U>(callbackfn: (value: MyObj, index: number, array: MyObj[]) => U, thisArg?: any) => U[]
>arr // should ok .filter((obj) => obj) // inferring a guard here would require https://github.com/microsoft/TypeScript/issues/42384 .filter(obj => !!obj.data) : MyObj[]
>arr // should ok .filter((obj) => obj) // inferring a guard here would require https://github.com/microsoft/TypeScript/issues/42384 .filter : { <S extends MyObj>(predicate: (value: MyObj, index: number, array: MyObj[]) => value is S, thisArg?: any): S[]; (predicate: (value: MyObj, index: number, array: MyObj[]) => unknown, thisArg?: any): MyObj[]; }
>arr // should ok .filter((obj) => obj) : MyObj[]
>arr // should ok .filter : { <S extends MyObj>(predicate: (value: MyObj, index: number, array: MyObj[]) => value is S, thisArg?: any): S[]; (predicate: (value: MyObj, index: number, array: MyObj[]) => unknown, thisArg?: any): MyObj[]; }
>arr : MyObj[]
.filter((obj) => obj)
>filter : { <S extends MyObj>(predicate: (value: MyObj, index: number, array: MyObj[]) => value is S, thisArg?: any): S[]; (predicate: (value: MyObj, index: number, array: MyObj[]) => unknown, thisArg?: any): MyObj[]; }
>(obj) => obj : (obj: MyObj) => MyObj
>obj : MyObj
>obj : MyObj
// inferring a guard here would require https://github.com/microsoft/TypeScript/issues/42384
.filter(obj => !!obj.data)
>filter : { <S extends MyObj>(predicate: (value: MyObj, index: number, array: MyObj[]) => value is S, thisArg?: any): S[]; (predicate: (value: MyObj, index: number, array: MyObj[]) => unknown, thisArg?: any): MyObj[]; }
>obj => !!obj.data : (obj: MyObj) => boolean
>obj : MyObj
>!!obj.data : boolean
>!obj.data : boolean
>obj.data : string | undefined
>obj : MyObj
>data : string | undefined
.map(obj => JSON.parse(obj.data))
>map : <U>(callbackfn: (value: MyObj, index: number, array: MyObj[]) => U, thisArg?: any) => U[]
>obj => JSON.parse(obj.data) : (obj: MyObj) => any
>obj : MyObj
>JSON.parse(obj.data) : any
>JSON.parse : (text: string, reviver?: ((this: any, key: string, value: any) => any) | undefined) => any
>JSON : JSON
>parse : (text: string, reviver?: ((this: any, key: string, value: any) => any) | undefined) => any
>obj.data : string | undefined
>obj : MyObj
>data : string | undefined
);
// https://github.com/microsoft/TypeScript/issues/16069#issuecomment-1183547889
type Foo = {
>Foo : { foo: string; }
foo: string;
>foo : string
}
type Bar = Foo & {
>Bar : Foo & { bar: string; }
bar: string;
>bar : string
}
const list: (Foo | Bar)[] = [];
>list : (Foo | Bar)[]
>[] : never[]
const resultBars: Bar[] = list.filter((value) => 'bar' in value); // should ok
>resultBars : Bar[]
>list.filter((value) => 'bar' in value) : Bar[]
>list.filter : { <S extends Foo | Bar>(predicate: (value: Foo | Bar, index: number, array: (Foo | Bar)[]) => value is S, thisArg?: any): S[]; (predicate: (value: Foo | Bar, index: number, array: (Foo | Bar)[]) => unknown, thisArg?: any): (Foo | Bar)[]; }
>list : (Foo | Bar)[]
>filter : { <S extends Foo | Bar>(predicate: (value: Foo | Bar, index: number, array: (Foo | Bar)[]) => value is S, thisArg?: any): S[]; (predicate: (value: Foo | Bar, index: number, array: (Foo | Bar)[]) => unknown, thisArg?: any): (Foo | Bar)[]; }
>(value) => 'bar' in value : (value: Foo | Bar) => value is Bar
>value : Foo | Bar
>'bar' in value : boolean
>'bar' : "bar"
>value : Foo | Bar
function isBarNonNull(x: Foo | Bar | null) {
>isBarNonNull : (x: Foo | Bar | null) => x is Bar
>x : Foo | Bar | null
return ('bar' in x!);
>('bar' in x!) : boolean
>'bar' in x! : boolean
>'bar' : "bar"
>x! : Foo | Bar
>x : Foo | Bar | null
}
const fooOrBar = list[0];
>fooOrBar : Foo | Bar
>list[0] : Foo | Bar
>list : (Foo | Bar)[]
>0 : 0
if (isBarNonNull(fooOrBar)) {
>isBarNonNull(fooOrBar) : boolean
>isBarNonNull : (x: Foo | Bar | null) => x is Bar
>fooOrBar : Foo | Bar
const t: Bar = fooOrBar; // should ok
>t : Bar
>fooOrBar : Bar
}
// https://github.com/microsoft/TypeScript/issues/38390#issuecomment-626019466
// Ryan's example (currently legal):
const a = [1, "foo", 2, "bar"].filter(x => typeof x === "string");
>a : string[]
>[1, "foo", 2, "bar"].filter(x => typeof x === "string") : string[]
>[1, "foo", 2, "bar"].filter : { <S extends string | number>(predicate: (value: string | number, index: number, array: (string | number)[]) => value is S, thisArg?: any): S[]; (predicate: (value: string | number, index: number, array: (string | number)[]) => unknown, thisArg?: any): (string | number)[]; }
>[1, "foo", 2, "bar"] : (string | number)[]
>1 : 1
>"foo" : "foo"
>2 : 2
>"bar" : "bar"
>filter : { <S extends string | number>(predicate: (value: string | number, index: number, array: (string | number)[]) => value is S, thisArg?: any): S[]; (predicate: (value: string | number, index: number, array: (string | number)[]) => unknown, thisArg?: any): (string | number)[]; }
>x => typeof x === "string" : (x: string | number) => x is string
>x : string | number
>typeof x === "string" : boolean
>typeof x : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function"
>x : string | number
>"string" : "string"
a.push(10);
>a.push(10) : number
>a.push : (...items: string[]) => number
>a : string[]
>push : (...items: string[]) => number
>10 : 10
// Defer to explicit type guards, even when they're incorrect.
function backwardsGuard(x: number|string): x is number {
>backwardsGuard : (x: number | string) => x is number
>x : string | number
return typeof x === 'string';
>typeof x === 'string' : boolean
>typeof x : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function"
>x : string | number
>'string' : "string"
}
// Partition tests. The "false" case matters.
function isString(x: string | number) {
>isString : (x: string | number) => x is string
>x : string | number
return typeof x === 'string';
>typeof x === 'string' : boolean
>typeof x : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function"
>x : string | number
>'string' : "string"
}
declare let strOrNum: string | number;
>strOrNum : string | number
if (isString(strOrNum)) {
>isString(strOrNum) : boolean
>isString : (x: string | number) => x is string
>strOrNum : string | number
let t: string = strOrNum; // should ok
>t : string
>strOrNum : string
} else {
let t: number = strOrNum; // should ok
>t : number
>strOrNum : number
}
function flakyIsString(x: string | number) {
>flakyIsString : (x: string | number) => boolean
>x : string | number
return typeof x === 'string' && Math.random() > 0.5;
>typeof x === 'string' && Math.random() > 0.5 : boolean
>typeof x === 'string' : boolean
>typeof x : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function"
>x : string | number
>'string' : "string"
>Math.random() > 0.5 : boolean
>Math.random() : number
>Math.random : () => number
>Math : Math
>random : () => number
>0.5 : 0.5
}
if (flakyIsString(strOrNum)) {
>flakyIsString(strOrNum) : boolean
>flakyIsString : (x: string | number) => boolean
>strOrNum : string | number
let t: string = strOrNum; // should error
>t : string
>strOrNum : string | number
} else {
let t: number = strOrNum; // should error
>t : number
>strOrNum : string | number
}
function isDate(x: object): 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"
}
// String escaping issue (please help!)
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
// could infer "x is number | true" but don't; debateable whether that's helpful.
const numOrBoolean = (x: number | boolean) => typeof x !== 'number' && x;
>numOrBoolean : (x: number | boolean) => x is true
>(x: number | boolean) => typeof x !== 'number' && x : (x: number | boolean) => x is 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"
}
@@ -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());
@@ -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
@@ -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;
+211
View File
@@ -0,0 +1,211 @@
// @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<T>(x: T) {
return x !== null;
}
// Type guards can flow between functions
const myGuard = (o: string | undefined): o is string => !!o;
const mySecondGuard = (o: string | undefined) => myGuard(o);
// https://github.com/microsoft/TypeScript/issues/16069#issuecomment-1327449914
// This doesn't work because the false condition prevents type guard inference.
// Breaking up the filters does work.
type MyObj = { data?: string };
type MyArray = { list?: MyObj[] }[];
const myArray: MyArray = [];
const result = myArray
.map((arr) => arr.list)
.filter((arr) => arr && arr.length)
.map((arr) => arr // should error
.filter((obj) => obj && obj.data)
.map(obj => JSON.parse(obj.data)) // should error
);
const result2 = myArray
.map((arr) => arr.list)
.filter((arr) => !!arr)
.filter(arr => arr.length)
.map((arr) => arr // should ok
.filter((obj) => obj)
// inferring a guard here would require https://github.com/microsoft/TypeScript/issues/42384
.filter(obj => !!obj.data)
.map(obj => JSON.parse(obj.data))
);
// https://github.com/microsoft/TypeScript/issues/16069#issuecomment-1183547889
type Foo = {
foo: string;
}
type Bar = Foo & {
bar: string;
}
const list: (Foo | Bar)[] = [];
const resultBars: Bar[] = list.filter((value) => 'bar' in value); // should ok
function isBarNonNull(x: Foo | Bar | null) {
return ('bar' in x!);
}
const fooOrBar = list[0];
if (isBarNonNull(fooOrBar)) {
const t: Bar = fooOrBar; // should ok
}
// https://github.com/microsoft/TypeScript/issues/38390#issuecomment-626019466
// Ryan's example (currently legal):
const a = [1, "foo", 2, "bar"].filter(x => typeof x === "string");
a.push(10);
// Defer to explicit type guards, even when they're incorrect.
function backwardsGuard(x: number|string): x is number {
return typeof x === 'string';
}
// Partition tests. The "false" case matters.
function isString(x: string | number) {
return typeof x === 'string';
}
declare let strOrNum: string | number;
if (isString(strOrNum)) {
let t: string = strOrNum; // should ok
} else {
let t: number = strOrNum; // should ok
}
function flakyIsString(x: string | number) {
return typeof x === 'string' && Math.random() > 0.5;
}
if (flakyIsString(strOrNum)) {
let t: string = strOrNum; // should error
} else {
let t: number = strOrNum; // should error
}
function isDate(x: object): 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';
}
// String escaping issue (please help!)
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;
// could infer "x is number | true" but don't; debateable whether that's helpful.
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';
}
@@ -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"
});