diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 80b61edd3657a..823675666af65 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -38778,7 +38778,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } } - function getTypePredicateFromBody(func: FunctionLikeDeclaration): TypePredicate | undefined { + function getTypePredicateFromBody(func: FunctionLikeDeclaration, contextualTypePredicate?: IdentifierTypePredicate): TypePredicate | undefined { switch (func.kind) { case SyntaxKind.Constructor: case SyntaxKind.GetAccessor: @@ -38800,41 +38800,43 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { }); if (bailedEarly || !singleReturn || functionHasImplicitReturn(func)) return undefined; } - return checkIfExpressionRefinesAnyParameter(func, singleReturn); - } - - function checkIfExpressionRefinesAnyParameter(func: FunctionLikeDeclaration, expr: Expression): TypePredicate | undefined { - expr = skipParentheses(expr, /*excludeJSDocTypeAssertions*/ true); + const expr = skipParentheses(singleReturn, /*excludeJSDocTypeAssertions*/ true); const returnType = checkExpressionCached(expr); - if (!(returnType.flags & TypeFlags.Boolean)) return undefined; - - return forEach(func.parameters, (param, i) => { - const initType = getTypeOfSymbol(param.symbol); - if (!initType || initType.flags & TypeFlags.Boolean || !isIdentifier(param.name) || isSymbolAssigned(param.symbol) || isRestParameter(param)) { - // Refining "x: boolean" to "x is true" or "x is false" isn't useful. - return; - } - const trueType = checkIfExpressionRefinesParameter(func, expr, param, initType); - if (trueType) { - return createTypePredicate(TypePredicateKind.Identifier, unescapeLeadingUnderscores(param.name.escapedText), i, trueType); + if (contextualTypePredicate && returnType.flags & TypeFlags.BooleanLiteral) { + const param = func.parameters[contextualTypePredicate.parameterIndex]; + if (!isIdentifier(param.name)) { + return undefined; } - }); + const refinedType = (returnType as IntrinsicType).intrinsicName === "true" ? getTypeOfSymbol(param.symbol) : neverType; + return createTypePredicate(TypePredicateKind.Identifier, unescapeLeadingUnderscores(param.name.escapedText), contextualTypePredicate.parameterIndex, refinedType); + } + if (!(returnType.flags & TypeFlags.Boolean)) return undefined; + return contextualTypePredicate ? + getTypePredicateIfRefinesParameterAtIndex(func, expr, contextualTypePredicate, contextualTypePredicate.parameterIndex) : + forEach(func.parameters, (_, i) => getTypePredicateIfRefinesParameterAtIndex(func, expr, contextualTypePredicate, i)); } - function checkIfExpressionRefinesParameter(func: FunctionLikeDeclaration, expr: Expression, param: ParameterDeclaration, initType: Type): Type | undefined { + function getTypePredicateIfRefinesParameterAtIndex(func: FunctionLikeDeclaration, expr: Expression, contextualTypePredicate: IdentifierTypePredicate | undefined, parameterIndex: number): TypePredicate | undefined { + const param = func.parameters[parameterIndex]; + const initType = getTypeOfSymbol(param.symbol); + if (!initType || initType.flags & TypeFlags.Boolean || !isIdentifier(param.name) || isSymbolAssigned(param.symbol) || isRestParameter(param)) { + // Refining "x: boolean" to "x is true" or "x is false" isn't useful. + return; + } const antecedent = canHaveFlowNode(expr) && expr.flowNode || expr.parent.kind === SyntaxKind.ReturnStatement && (expr.parent as ReturnStatement).flowNode || createFlowNode(FlowFlags.Start, /*node*/ undefined, /*antecedent*/ undefined); const trueCondition = createFlowNode(FlowFlags.TrueCondition, expr, antecedent); const trueType = getFlowTypeOfReference(param.name, initType, initType, func, trueCondition); - if (trueType === initType) return undefined; - + if (!contextualTypePredicate && trueType === initType) { + return undefined; + } // "x is T" means that x is T if and only if it returns true. If it returns false then x is not T. // This means that if the function is called with an argument of type trueType, there can't be anything left in the `else` branch. It must reduce to `never`. const falseCondition = createFlowNode(FlowFlags.FalseCondition, expr, antecedent); const falseSubtype = getFlowTypeOfReference(param.name, initType, trueType, func, falseCondition); - return falseSubtype.flags & TypeFlags.Never ? trueType : undefined; + return falseSubtype.flags & TypeFlags.Never ? createTypePredicate(TypePredicateKind.Identifier, unescapeLeadingUnderscores(param.name.escapedText), parameterIndex, trueType) : undefined; } /** @@ -38978,10 +38980,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { inferFromAnnotatedParameters(signature, contextualSignature, inferenceContext!); } } - if (contextualSignature && !getReturnTypeFromAnnotation(node) && !signature.resolvedReturnType) { - const returnType = getReturnTypeFromBody(node, checkMode); - if (!signature.resolvedReturnType) { - signature.resolvedReturnType = returnType; + if (contextualSignature && !getReturnTypeFromAnnotation(node)) { + const returnType = signature.resolvedReturnType ?? getReturnTypeFromBody(node, checkMode); + signature.resolvedReturnType ??= returnType; + if (signature.resolvedReturnType.flags && TypeFlags.Boolean && contextualSignature.resolvedTypePredicate && contextualSignature.resolvedTypePredicate !== noTypePredicate && contextualSignature.resolvedTypePredicate.kind === TypePredicateKind.Identifier) { + signature.resolvedTypePredicate ??= getTypePredicateFromBody(node, contextualSignature.resolvedTypePredicate) ?? noTypePredicate; } } checkSignatureDeclaration(node); diff --git a/tests/baselines/reference/inferContextualTypePredicates1.errors.txt b/tests/baselines/reference/inferContextualTypePredicates1.errors.txt new file mode 100644 index 0000000000000..90e9f01166123 --- /dev/null +++ b/tests/baselines/reference/inferContextualTypePredicates1.errors.txt @@ -0,0 +1,45 @@ +inferContextualTypePredicates1.ts(17,7): error TS2322: Type '(a: string | null, b: string | null) => boolean' is not assignable to type '(a: string | null, b: string | null) => b is string'. + Signature '(a: string | null, b: string | null): boolean' must be a type predicate. + + +==== inferContextualTypePredicates1.ts (1 errors) ==== + type Foo = { type: "foo"; foo: number }; + type Bar = { type: "bar"; bar: string }; + + declare function skipIf( + as: A[], + predicate: (a: A) => a is B, + ): Exclude[]; + + declare const items: (Foo | Bar)[]; + + const r1 = skipIf(items, (item) => item.type === "foo"); // ok + const r2 = skipIf(items, (item) => item.type === "foo" || item.type === "bar"); // ok + const r3 = skipIf(items, (item) => false); // ok + const r4 = skipIf(items, (item) => true); // ok + + const pred1: (a: string | null, b: string | null) => b is string = (a, b) => typeof b === 'string'; // ok + const pred2: (a: string | null, b: string | null) => b is string = (a, b) => typeof a === 'string'; // error + ~~~~~ +!!! error TS2322: Type '(a: string | null, b: string | null) => boolean' is not assignable to type '(a: string | null, b: string | null) => b is string'. +!!! error TS2322: Signature '(a: string | null, b: string | null): boolean' must be a type predicate. + + export declare function every(array: readonly T[], callback: (element: T, index: number) => element is U): array is readonly U[]; + export declare function every(array: readonly T[] | undefined, callback: (element: T, index: number) => element is U): array is readonly U[] | undefined; + export declare function every(array: readonly T[] | undefined, callback: (element: T, index: number) => boolean): boolean; + + type Type = { + kind: number; + flags: number; + }; + + function testEvery(typeSet: Type[]) { + if (every(typeSet, (t) => t.kind === 1)) { + return 1; + } + if (every(typeSet, (t) => t.kind === 2)) { + return 2; + } + return -1; + } + \ No newline at end of file diff --git a/tests/baselines/reference/inferContextualTypePredicates1.symbols b/tests/baselines/reference/inferContextualTypePredicates1.symbols new file mode 100644 index 0000000000000..052286a7a24b8 --- /dev/null +++ b/tests/baselines/reference/inferContextualTypePredicates1.symbols @@ -0,0 +1,172 @@ +//// [tests/cases/compiler/inferContextualTypePredicates1.ts] //// + +=== inferContextualTypePredicates1.ts === +type Foo = { type: "foo"; foo: number }; +>Foo : Symbol(Foo, Decl(inferContextualTypePredicates1.ts, 0, 0)) +>type : Symbol(type, Decl(inferContextualTypePredicates1.ts, 0, 12)) +>foo : Symbol(foo, Decl(inferContextualTypePredicates1.ts, 0, 25)) + +type Bar = { type: "bar"; bar: string }; +>Bar : Symbol(Bar, Decl(inferContextualTypePredicates1.ts, 0, 40)) +>type : Symbol(type, Decl(inferContextualTypePredicates1.ts, 1, 12)) +>bar : Symbol(bar, Decl(inferContextualTypePredicates1.ts, 1, 25)) + +declare function skipIf( +>skipIf : Symbol(skipIf, Decl(inferContextualTypePredicates1.ts, 1, 40)) +>A : Symbol(A, Decl(inferContextualTypePredicates1.ts, 3, 24)) +>B : Symbol(B, Decl(inferContextualTypePredicates1.ts, 3, 26)) +>A : Symbol(A, Decl(inferContextualTypePredicates1.ts, 3, 24)) + + as: A[], +>as : Symbol(as, Decl(inferContextualTypePredicates1.ts, 3, 40)) +>A : Symbol(A, Decl(inferContextualTypePredicates1.ts, 3, 24)) + + predicate: (a: A) => a is B, +>predicate : Symbol(predicate, Decl(inferContextualTypePredicates1.ts, 4, 10)) +>a : Symbol(a, Decl(inferContextualTypePredicates1.ts, 5, 14)) +>A : Symbol(A, Decl(inferContextualTypePredicates1.ts, 3, 24)) +>a : Symbol(a, Decl(inferContextualTypePredicates1.ts, 5, 14)) +>B : Symbol(B, Decl(inferContextualTypePredicates1.ts, 3, 26)) + +): Exclude[]; +>Exclude : Symbol(Exclude, Decl(lib.es5.d.ts, --, --)) +>A : Symbol(A, Decl(inferContextualTypePredicates1.ts, 3, 24)) +>B : Symbol(B, Decl(inferContextualTypePredicates1.ts, 3, 26)) + +declare const items: (Foo | Bar)[]; +>items : Symbol(items, Decl(inferContextualTypePredicates1.ts, 8, 13)) +>Foo : Symbol(Foo, Decl(inferContextualTypePredicates1.ts, 0, 0)) +>Bar : Symbol(Bar, Decl(inferContextualTypePredicates1.ts, 0, 40)) + +const r1 = skipIf(items, (item) => item.type === "foo"); // ok +>r1 : Symbol(r1, Decl(inferContextualTypePredicates1.ts, 10, 5)) +>skipIf : Symbol(skipIf, Decl(inferContextualTypePredicates1.ts, 1, 40)) +>items : Symbol(items, Decl(inferContextualTypePredicates1.ts, 8, 13)) +>item : Symbol(item, Decl(inferContextualTypePredicates1.ts, 10, 26)) +>item.type : Symbol(type, Decl(inferContextualTypePredicates1.ts, 0, 12), Decl(inferContextualTypePredicates1.ts, 1, 12)) +>item : Symbol(item, Decl(inferContextualTypePredicates1.ts, 10, 26)) +>type : Symbol(type, Decl(inferContextualTypePredicates1.ts, 0, 12), Decl(inferContextualTypePredicates1.ts, 1, 12)) + +const r2 = skipIf(items, (item) => item.type === "foo" || item.type === "bar"); // ok +>r2 : Symbol(r2, Decl(inferContextualTypePredicates1.ts, 11, 5)) +>skipIf : Symbol(skipIf, Decl(inferContextualTypePredicates1.ts, 1, 40)) +>items : Symbol(items, Decl(inferContextualTypePredicates1.ts, 8, 13)) +>item : Symbol(item, Decl(inferContextualTypePredicates1.ts, 11, 26)) +>item.type : Symbol(type, Decl(inferContextualTypePredicates1.ts, 0, 12), Decl(inferContextualTypePredicates1.ts, 1, 12)) +>item : Symbol(item, Decl(inferContextualTypePredicates1.ts, 11, 26)) +>type : Symbol(type, Decl(inferContextualTypePredicates1.ts, 0, 12), Decl(inferContextualTypePredicates1.ts, 1, 12)) +>item.type : Symbol(type, Decl(inferContextualTypePredicates1.ts, 1, 12)) +>item : Symbol(item, Decl(inferContextualTypePredicates1.ts, 11, 26)) +>type : Symbol(type, Decl(inferContextualTypePredicates1.ts, 1, 12)) + +const r3 = skipIf(items, (item) => false); // ok +>r3 : Symbol(r3, Decl(inferContextualTypePredicates1.ts, 12, 5)) +>skipIf : Symbol(skipIf, Decl(inferContextualTypePredicates1.ts, 1, 40)) +>items : Symbol(items, Decl(inferContextualTypePredicates1.ts, 8, 13)) +>item : Symbol(item, Decl(inferContextualTypePredicates1.ts, 12, 26)) + +const r4 = skipIf(items, (item) => true); // ok +>r4 : Symbol(r4, Decl(inferContextualTypePredicates1.ts, 13, 5)) +>skipIf : Symbol(skipIf, Decl(inferContextualTypePredicates1.ts, 1, 40)) +>items : Symbol(items, Decl(inferContextualTypePredicates1.ts, 8, 13)) +>item : Symbol(item, Decl(inferContextualTypePredicates1.ts, 13, 26)) + +const pred1: (a: string | null, b: string | null) => b is string = (a, b) => typeof b === 'string'; // ok +>pred1 : Symbol(pred1, Decl(inferContextualTypePredicates1.ts, 15, 5)) +>a : Symbol(a, Decl(inferContextualTypePredicates1.ts, 15, 14)) +>b : Symbol(b, Decl(inferContextualTypePredicates1.ts, 15, 31)) +>b : Symbol(b, Decl(inferContextualTypePredicates1.ts, 15, 31)) +>a : Symbol(a, Decl(inferContextualTypePredicates1.ts, 15, 68)) +>b : Symbol(b, Decl(inferContextualTypePredicates1.ts, 15, 70)) +>b : Symbol(b, Decl(inferContextualTypePredicates1.ts, 15, 70)) + +const pred2: (a: string | null, b: string | null) => b is string = (a, b) => typeof a === 'string'; // error +>pred2 : Symbol(pred2, Decl(inferContextualTypePredicates1.ts, 16, 5)) +>a : Symbol(a, Decl(inferContextualTypePredicates1.ts, 16, 14)) +>b : Symbol(b, Decl(inferContextualTypePredicates1.ts, 16, 31)) +>b : Symbol(b, Decl(inferContextualTypePredicates1.ts, 16, 31)) +>a : Symbol(a, Decl(inferContextualTypePredicates1.ts, 16, 68)) +>b : Symbol(b, Decl(inferContextualTypePredicates1.ts, 16, 70)) +>a : Symbol(a, Decl(inferContextualTypePredicates1.ts, 16, 68)) + +export declare function every(array: readonly T[], callback: (element: T, index: number) => element is U): array is readonly U[]; +>every : Symbol(every, Decl(inferContextualTypePredicates1.ts, 16, 99), Decl(inferContextualTypePredicates1.ts, 18, 145), Decl(inferContextualTypePredicates1.ts, 19, 169)) +>T : Symbol(T, Decl(inferContextualTypePredicates1.ts, 18, 30)) +>U : Symbol(U, Decl(inferContextualTypePredicates1.ts, 18, 32)) +>T : Symbol(T, Decl(inferContextualTypePredicates1.ts, 18, 30)) +>array : Symbol(array, Decl(inferContextualTypePredicates1.ts, 18, 46)) +>T : Symbol(T, Decl(inferContextualTypePredicates1.ts, 18, 30)) +>callback : Symbol(callback, Decl(inferContextualTypePredicates1.ts, 18, 66)) +>element : Symbol(element, Decl(inferContextualTypePredicates1.ts, 18, 78)) +>T : Symbol(T, Decl(inferContextualTypePredicates1.ts, 18, 30)) +>index : Symbol(index, Decl(inferContextualTypePredicates1.ts, 18, 89)) +>element : Symbol(element, Decl(inferContextualTypePredicates1.ts, 18, 78)) +>U : Symbol(U, Decl(inferContextualTypePredicates1.ts, 18, 32)) +>array : Symbol(array, Decl(inferContextualTypePredicates1.ts, 18, 46)) +>U : Symbol(U, Decl(inferContextualTypePredicates1.ts, 18, 32)) + +export declare function every(array: readonly T[] | undefined, callback: (element: T, index: number) => element is U): array is readonly U[] | undefined; +>every : Symbol(every, Decl(inferContextualTypePredicates1.ts, 16, 99), Decl(inferContextualTypePredicates1.ts, 18, 145), Decl(inferContextualTypePredicates1.ts, 19, 169)) +>T : Symbol(T, Decl(inferContextualTypePredicates1.ts, 19, 30)) +>U : Symbol(U, Decl(inferContextualTypePredicates1.ts, 19, 32)) +>T : Symbol(T, Decl(inferContextualTypePredicates1.ts, 19, 30)) +>array : Symbol(array, Decl(inferContextualTypePredicates1.ts, 19, 46)) +>T : Symbol(T, Decl(inferContextualTypePredicates1.ts, 19, 30)) +>callback : Symbol(callback, Decl(inferContextualTypePredicates1.ts, 19, 78)) +>element : Symbol(element, Decl(inferContextualTypePredicates1.ts, 19, 90)) +>T : Symbol(T, Decl(inferContextualTypePredicates1.ts, 19, 30)) +>index : Symbol(index, Decl(inferContextualTypePredicates1.ts, 19, 101)) +>element : Symbol(element, Decl(inferContextualTypePredicates1.ts, 19, 90)) +>U : Symbol(U, Decl(inferContextualTypePredicates1.ts, 19, 32)) +>array : Symbol(array, Decl(inferContextualTypePredicates1.ts, 19, 46)) +>U : Symbol(U, Decl(inferContextualTypePredicates1.ts, 19, 32)) + +export declare function every(array: readonly T[] | undefined, callback: (element: T, index: number) => boolean): boolean; +>every : Symbol(every, Decl(inferContextualTypePredicates1.ts, 16, 99), Decl(inferContextualTypePredicates1.ts, 18, 145), Decl(inferContextualTypePredicates1.ts, 19, 169)) +>T : Symbol(T, Decl(inferContextualTypePredicates1.ts, 20, 30)) +>array : Symbol(array, Decl(inferContextualTypePredicates1.ts, 20, 33)) +>T : Symbol(T, Decl(inferContextualTypePredicates1.ts, 20, 30)) +>callback : Symbol(callback, Decl(inferContextualTypePredicates1.ts, 20, 65)) +>element : Symbol(element, Decl(inferContextualTypePredicates1.ts, 20, 77)) +>T : Symbol(T, Decl(inferContextualTypePredicates1.ts, 20, 30)) +>index : Symbol(index, Decl(inferContextualTypePredicates1.ts, 20, 88)) + +type Type = { +>Type : Symbol(Type, Decl(inferContextualTypePredicates1.ts, 20, 125)) + + kind: number; +>kind : Symbol(kind, Decl(inferContextualTypePredicates1.ts, 22, 13)) + + flags: number; +>flags : Symbol(flags, Decl(inferContextualTypePredicates1.ts, 23, 15)) + +}; + +function testEvery(typeSet: Type[]) { +>testEvery : Symbol(testEvery, Decl(inferContextualTypePredicates1.ts, 25, 2)) +>typeSet : Symbol(typeSet, Decl(inferContextualTypePredicates1.ts, 27, 19)) +>Type : Symbol(Type, Decl(inferContextualTypePredicates1.ts, 20, 125)) + + if (every(typeSet, (t) => t.kind === 1)) { +>every : Symbol(every, Decl(inferContextualTypePredicates1.ts, 16, 99), Decl(inferContextualTypePredicates1.ts, 18, 145), Decl(inferContextualTypePredicates1.ts, 19, 169)) +>typeSet : Symbol(typeSet, Decl(inferContextualTypePredicates1.ts, 27, 19)) +>t : Symbol(t, Decl(inferContextualTypePredicates1.ts, 28, 22)) +>t.kind : Symbol(kind, Decl(inferContextualTypePredicates1.ts, 22, 13)) +>t : Symbol(t, Decl(inferContextualTypePredicates1.ts, 28, 22)) +>kind : Symbol(kind, Decl(inferContextualTypePredicates1.ts, 22, 13)) + + return 1; + } + if (every(typeSet, (t) => t.kind === 2)) { +>every : Symbol(every, Decl(inferContextualTypePredicates1.ts, 16, 99), Decl(inferContextualTypePredicates1.ts, 18, 145), Decl(inferContextualTypePredicates1.ts, 19, 169)) +>typeSet : Symbol(typeSet, Decl(inferContextualTypePredicates1.ts, 27, 19)) +>t : Symbol(t, Decl(inferContextualTypePredicates1.ts, 31, 22)) +>t.kind : Symbol(kind, Decl(inferContextualTypePredicates1.ts, 22, 13)) +>t : Symbol(t, Decl(inferContextualTypePredicates1.ts, 31, 22)) +>kind : Symbol(kind, Decl(inferContextualTypePredicates1.ts, 22, 13)) + + return 2; + } + return -1; +} + diff --git a/tests/baselines/reference/inferContextualTypePredicates1.types b/tests/baselines/reference/inferContextualTypePredicates1.types new file mode 100644 index 0000000000000..c2c5d9d06e07f --- /dev/null +++ b/tests/baselines/reference/inferContextualTypePredicates1.types @@ -0,0 +1,290 @@ +//// [tests/cases/compiler/inferContextualTypePredicates1.ts] //// + +=== inferContextualTypePredicates1.ts === +type Foo = { type: "foo"; foo: number }; +>Foo : Foo +> : ^^^ +>type : "foo" +> : ^^^^^ +>foo : number +> : ^^^^^^ + +type Bar = { type: "bar"; bar: string }; +>Bar : Bar +> : ^^^ +>type : "bar" +> : ^^^^^ +>bar : string +> : ^^^^^^ + +declare function skipIf( +>skipIf : (as: A[], predicate: (a: A) => a is B) => Exclude[] +> : ^ ^^ ^^^^^^^^^ ^^ ^^ ^^ ^^ ^^^^^ + + as: A[], +>as : A[] +> : ^^^ + + predicate: (a: A) => a is B, +>predicate : (a: A) => a is B +> : ^ ^^ ^^^^^ +>a : A +> : ^ + +): Exclude[]; + +declare const items: (Foo | Bar)[]; +>items : (Foo | Bar)[] +> : ^^^^^^^^^^^^^ + +const r1 = skipIf(items, (item) => item.type === "foo"); // ok +>r1 : Bar[] +> : ^^^^^ +>skipIf(items, (item) => item.type === "foo") : Bar[] +> : ^^^^^ +>skipIf : (as: A[], predicate: (a: A) => a is B) => Exclude[] +> : ^ ^^ ^^^^^^^^^ ^^ ^^ ^^ ^^ ^^^^^ +>items : (Foo | Bar)[] +> : ^^^^^^^^^^^^^ +>(item) => item.type === "foo" : (item: Foo | Bar) => item is Foo +> : ^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>item : Foo | Bar +> : ^^^^^^^^^ +>item.type === "foo" : boolean +> : ^^^^^^^ +>item.type : "foo" | "bar" +> : ^^^^^^^^^^^^^ +>item : Foo | Bar +> : ^^^^^^^^^ +>type : "foo" | "bar" +> : ^^^^^^^^^^^^^ +>"foo" : "foo" +> : ^^^^^ + +const r2 = skipIf(items, (item) => item.type === "foo" || item.type === "bar"); // ok +>r2 : never[] +> : ^^^^^^^ +>skipIf(items, (item) => item.type === "foo" || item.type === "bar") : never[] +> : ^^^^^^^ +>skipIf : (as: A[], predicate: (a: A) => a is B) => Exclude[] +> : ^ ^^ ^^^^^^^^^ ^^ ^^ ^^ ^^ ^^^^^ +>items : (Foo | Bar)[] +> : ^^^^^^^^^^^^^ +>(item) => item.type === "foo" || item.type === "bar" : (item: Foo | Bar) => item is Foo | Bar +> : ^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>item : Foo | Bar +> : ^^^^^^^^^ +>item.type === "foo" || item.type === "bar" : boolean +> : ^^^^^^^ +>item.type === "foo" : boolean +> : ^^^^^^^ +>item.type : "foo" | "bar" +> : ^^^^^^^^^^^^^ +>item : Foo | Bar +> : ^^^^^^^^^ +>type : "foo" | "bar" +> : ^^^^^^^^^^^^^ +>"foo" : "foo" +> : ^^^^^ +>item.type === "bar" : boolean +> : ^^^^^^^ +>item.type : "bar" +> : ^^^^^ +>item : Bar +> : ^^^ +>type : "bar" +> : ^^^^^ +>"bar" : "bar" +> : ^^^^^ + +const r3 = skipIf(items, (item) => false); // ok +>r3 : (Foo | Bar)[] +> : ^^^^^^^^^^^^^ +>skipIf(items, (item) => false) : (Foo | Bar)[] +> : ^^^^^^^^^^^^^ +>skipIf : (as: A[], predicate: (a: A) => a is B) => Exclude[] +> : ^ ^^ ^^^^^^^^^ ^^ ^^ ^^ ^^ ^^^^^ +>items : (Foo | Bar)[] +> : ^^^^^^^^^^^^^ +>(item) => false : (item: Foo | Bar) => item is never +> : ^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>item : Foo | Bar +> : ^^^^^^^^^ +>false : false +> : ^^^^^ + +const r4 = skipIf(items, (item) => true); // ok +>r4 : never[] +> : ^^^^^^^ +>skipIf(items, (item) => true) : never[] +> : ^^^^^^^ +>skipIf : (as: A[], predicate: (a: A) => a is B) => Exclude[] +> : ^ ^^ ^^^^^^^^^ ^^ ^^ ^^ ^^ ^^^^^ +>items : (Foo | Bar)[] +> : ^^^^^^^^^^^^^ +>(item) => true : (item: Foo | Bar) => item is Foo | Bar +> : ^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>item : Foo | Bar +> : ^^^^^^^^^ +>true : true +> : ^^^^ + +const pred1: (a: string | null, b: string | null) => b is string = (a, b) => typeof b === 'string'; // ok +>pred1 : (a: string | null, b: string | null) => b is string +> : ^ ^^ ^^ ^^ ^^^^^ +>a : string | null +> : ^^^^^^^^^^^^^ +>b : string | null +> : ^^^^^^^^^^^^^ +>(a, b) => typeof b === 'string' : (a: string | null, b: string | null) => b is string +> : ^ ^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>a : string | null +> : ^^^^^^^^^^^^^ +>b : string | null +> : ^^^^^^^^^^^^^ +>typeof b === 'string' : boolean +> : ^^^^^^^ +>typeof b : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>b : string | null +> : ^^^^^^^^^^^^^ +>'string' : "string" +> : ^^^^^^^^ + +const pred2: (a: string | null, b: string | null) => b is string = (a, b) => typeof a === 'string'; // error +>pred2 : (a: string | null, b: string | null) => b is string +> : ^ ^^ ^^ ^^ ^^^^^ +>a : string | null +> : ^^^^^^^^^^^^^ +>b : string | null +> : ^^^^^^^^^^^^^ +>(a, b) => typeof a === 'string' : (a: string | null, b: string | null) => boolean +> : ^ ^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>a : string | null +> : ^^^^^^^^^^^^^ +>b : string | null +> : ^^^^^^^^^^^^^ +>typeof a === 'string' : boolean +> : ^^^^^^^ +>typeof a : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>a : string | null +> : ^^^^^^^^^^^^^ +>'string' : "string" +> : ^^^^^^^^ + +export declare function every(array: readonly T[], callback: (element: T, index: number) => element is U): array is readonly U[]; +>every : { (array: readonly T[], callback: (element: T, index: number) => element is U): array is readonly U[]; (array: readonly T_1[] | undefined, callback: (element: T_1, index: number) => element is U_1): array is readonly U_1[] | undefined; (array: readonly T_1[] | undefined, callback: (element: T_1, index: number) => boolean): boolean; } +> : ^^^ ^^ ^^^^^^^^^ ^^ ^^ ^^ ^^ ^^^ ^^^^^^^^^^^^^^^^^^^^ ^^ ^^ ^^ ^^ ^^^ ^^^^^^^^ ^^ ^^ ^^ ^^^ ^^^ +>array : readonly T[] +> : ^^^^^^^^^^^^ +>callback : (element: T, index: number) => element is U +> : ^ ^^ ^^ ^^ ^^^^^ +>element : T +> : ^ +>index : number +> : ^^^^^^ + +export declare function every(array: readonly T[] | undefined, callback: (element: T, index: number) => element is U): array is readonly U[] | undefined; +>every : { (array: readonly T_1[], callback: (element: T_1, index: number) => element is U_1): array is readonly U_1[]; (array: readonly T[] | undefined, callback: (element: T, index: number) => element is U): array is readonly U[] | undefined; (array: readonly T_1[] | undefined, callback: (element: T_1, index: number) => boolean): boolean; } +> : ^^^^^^^^^^^^^^^^^^^^ ^^ ^^ ^^ ^^ ^^^ ^^^ ^^ ^^^^^^^^^ ^^ ^^ ^^ ^^ ^^^ ^^^^^^^^ ^^ ^^ ^^ ^^^ ^^^ +>array : readonly T[] | undefined +> : ^^^^^^^^^^^^^^^^^^^^^^^^ +>callback : (element: T, index: number) => element is U +> : ^ ^^ ^^ ^^ ^^^^^ +>element : T +> : ^ +>index : number +> : ^^^^^^ + +export declare function every(array: readonly T[] | undefined, callback: (element: T, index: number) => boolean): boolean; +>every : { (array: readonly T_1[], callback: (element: T_1, index: number) => element is U): array is readonly U[]; (array: readonly T_1[] | undefined, callback: (element: T_1, index: number) => element is U): array is readonly U[] | undefined; (array: readonly T[] | undefined, callback: (element: T, index: number) => boolean): boolean; } +> : ^^^^^^^^ ^^^^^^^^^ ^^ ^^ ^^ ^^ ^^^ ^^^^^^^^ ^^^^^^^^^ ^^ ^^ ^^ ^^ ^^^ ^^^ ^^ ^^ ^^ ^^ ^^^ ^^^ +>array : readonly T[] | undefined +> : ^^^^^^^^^^^^^^^^^^^^^^^^ +>callback : (element: T, index: number) => boolean +> : ^ ^^ ^^ ^^ ^^^^^ +>element : T +> : ^ +>index : number +> : ^^^^^^ + +type Type = { +>Type : Type +> : ^^^^ + + kind: number; +>kind : number +> : ^^^^^^ + + flags: number; +>flags : number +> : ^^^^^^ + +}; + +function testEvery(typeSet: Type[]) { +>testEvery : (typeSet: Type[]) => 1 | 2 | -1 +> : ^ ^^ ^^^^^^^^^^^^^^^ +>typeSet : Type[] +> : ^^^^^^ + + if (every(typeSet, (t) => t.kind === 1)) { +>every(typeSet, (t) => t.kind === 1) : boolean +> : ^^^^^^^ +>every : { (array: readonly T[], callback: (element: T, index: number) => element is U): array is readonly U[]; (array: readonly T[] | undefined, callback: (element: T, index: number) => element is U): array is readonly U[] | undefined; (array: readonly T[] | undefined, callback: (element: T, index: number) => boolean): boolean; } +> : ^^^ ^^ ^^^^^^^^^ ^^ ^^ ^^ ^^ ^^^ ^^^ ^^ ^^^^^^^^^ ^^ ^^ ^^ ^^ ^^^ ^^^ ^^ ^^ ^^ ^^ ^^^ ^^^ +>typeSet : Type[] +> : ^^^^^^ +>(t) => t.kind === 1 : (t: Type) => boolean +> : ^ ^^^^^^^^^^^^^^^^^^ +>t : Type +> : ^^^^ +>t.kind === 1 : boolean +> : ^^^^^^^ +>t.kind : number +> : ^^^^^^ +>t : Type +> : ^^^^ +>kind : number +> : ^^^^^^ +>1 : 1 +> : ^ + + return 1; +>1 : 1 +> : ^ + } + if (every(typeSet, (t) => t.kind === 2)) { +>every(typeSet, (t) => t.kind === 2) : boolean +> : ^^^^^^^ +>every : { (array: readonly T[], callback: (element: T, index: number) => element is U): array is readonly U[]; (array: readonly T[] | undefined, callback: (element: T, index: number) => element is U): array is readonly U[] | undefined; (array: readonly T[] | undefined, callback: (element: T, index: number) => boolean): boolean; } +> : ^^^ ^^ ^^^^^^^^^ ^^ ^^ ^^ ^^ ^^^ ^^^ ^^ ^^^^^^^^^ ^^ ^^ ^^ ^^ ^^^ ^^^ ^^ ^^ ^^ ^^ ^^^ ^^^ +>typeSet : Type[] +> : ^^^^^^ +>(t) => t.kind === 2 : (t: Type) => boolean +> : ^ ^^^^^^^^^^^^^^^^^^ +>t : Type +> : ^^^^ +>t.kind === 2 : boolean +> : ^^^^^^^ +>t.kind : number +> : ^^^^^^ +>t : Type +> : ^^^^ +>kind : number +> : ^^^^^^ +>2 : 2 +> : ^ + + return 2; +>2 : 2 +> : ^ + } + return -1; +>-1 : -1 +> : ^^ +>1 : 1 +> : ^ +} + diff --git a/tests/baselines/reference/targetTypeArgs.types b/tests/baselines/reference/targetTypeArgs.types index 654f04fe5a913..04e1362086a99 100644 --- a/tests/baselines/reference/targetTypeArgs.types +++ b/tests/baselines/reference/targetTypeArgs.types @@ -107,8 +107,8 @@ foo(function(x) { x }); > : ^ >every : { (predicate: (value: number, index: number, array: number[]) => value is S, thisArg?: any): this is S[]; (predicate: (value: number, index: number, array: number[]) => unknown, thisArg?: any): boolean; } > : ^^^ ^^^^^^^^^^^^^^^^^ ^^^ ^^^^^^^^^^ ^^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^ ^^^ ^ ^^^ ^^^ ^^^^^^^^^^ ^^ ^^ ^^^^^^^^^^^^^^^ ^^ ^^^ ^^^ ^^^ ->function(v,i,a) {return true;} : (v: number, i: number, a: number[]) => true -> : ^ ^^^^^^^^^^ ^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^ +>function(v,i,a) {return true;} : (v: number, i: number, a: number[]) => v is number +> : ^ ^^^^^^^^^^ ^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^ >v : number > : ^^^^^^ >i : number @@ -129,8 +129,8 @@ foo(function(x) { x }); > : ^^^ >every : { (predicate: (value: string, index: number, array: string[]) => value is S, thisArg?: any): this is S[]; (predicate: (value: string, index: number, array: string[]) => unknown, thisArg?: any): boolean; } > : ^^^ ^^^^^^^^^^^^^^^^^ ^^^ ^^^^^^^^^^ ^^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^ ^^^ ^ ^^^ ^^^ ^^^^^^^^^^ ^^ ^^ ^^^^^^^^^^^^^^^ ^^ ^^^ ^^^ ^^^ ->function(v,i,a) {return true;} : (v: string, i: number, a: string[]) => true -> : ^ ^^^^^^^^^^ ^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^ +>function(v,i,a) {return true;} : (v: string, i: number, a: string[]) => v is string +> : ^ ^^^^^^^^^^ ^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^ >v : string > : ^^^^^^ >i : number diff --git a/tests/cases/compiler/inferContextualTypePredicates1.ts b/tests/cases/compiler/inferContextualTypePredicates1.ts new file mode 100644 index 0000000000000..a2ed79d183fe6 --- /dev/null +++ b/tests/cases/compiler/inferContextualTypePredicates1.ts @@ -0,0 +1,39 @@ +// @strict: true +// @noEmit: true + +type Foo = { type: "foo"; foo: number }; +type Bar = { type: "bar"; bar: string }; + +declare function skipIf( + as: A[], + predicate: (a: A) => a is B, +): Exclude[]; + +declare const items: (Foo | Bar)[]; + +const r1 = skipIf(items, (item) => item.type === "foo"); // ok +const r2 = skipIf(items, (item) => item.type === "foo" || item.type === "bar"); // ok +const r3 = skipIf(items, (item) => false); // ok +const r4 = skipIf(items, (item) => true); // ok + +const pred1: (a: string | null, b: string | null) => b is string = (a, b) => typeof b === 'string'; // ok +const pred2: (a: string | null, b: string | null) => b is string = (a, b) => typeof a === 'string'; // error + +export declare function every(array: readonly T[], callback: (element: T, index: number) => element is U): array is readonly U[]; +export declare function every(array: readonly T[] | undefined, callback: (element: T, index: number) => element is U): array is readonly U[] | undefined; +export declare function every(array: readonly T[] | undefined, callback: (element: T, index: number) => boolean): boolean; + +type Type = { + kind: number; + flags: number; +}; + +function testEvery(typeSet: Type[]) { + if (every(typeSet, (t) => t.kind === 1)) { + return 1; + } + if (every(typeSet, (t) => t.kind === 2)) { + return 2; + } + return -1; +}