From 332ecc306198bee90490344eb9ddb5fc03399086 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Fri, 20 Dec 2024 23:37:19 +0100 Subject: [PATCH 1/3] Infer non-narrowing predicates when the contextual signature is a type predicate --- src/compiler/checker.ts | 47 +++-- .../inferContextualTypePredicates1.errors.txt | 36 ++++ .../inferContextualTypePredicates1.symbols | 91 +++++++++ .../inferContextualTypePredicates1.types | 175 ++++++++++++++++++ .../inferContextualTypePredicates1.ts | 20 ++ 5 files changed, 343 insertions(+), 26 deletions(-) create mode 100644 tests/baselines/reference/inferContextualTypePredicates1.errors.txt create mode 100644 tests/baselines/reference/inferContextualTypePredicates1.symbols create mode 100644 tests/baselines/reference/inferContextualTypePredicates1.types create mode 100644 tests/cases/compiler/inferContextualTypePredicates1.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 80b61edd3657a..6ba276cf61d9d 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,35 @@ 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); - } - }); + 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 +38972,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..566c2f4315e97 --- /dev/null +++ b/tests/baselines/reference/inferContextualTypePredicates1.errors.txt @@ -0,0 +1,36 @@ +inferContextualTypePredicates1.ts(13,26): error TS2345: Argument of type '(item: Foo | Bar) => false' is not assignable to parameter of type '(a: Foo | Bar) => a is Foo | Bar'. + Signature '(item: Foo | Bar): false' must be a type predicate. +inferContextualTypePredicates1.ts(14,26): error TS2345: Argument of type '(item: Foo | Bar) => true' is not assignable to parameter of type '(a: Foo | Bar) => a is Foo | Bar'. + Signature '(item: Foo | Bar): true' must be a type predicate. +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 (3 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); // error + ~~~~~~~~~~~~~~~ +!!! error TS2345: Argument of type '(item: Foo | Bar) => false' is not assignable to parameter of type '(a: Foo | Bar) => a is Foo | Bar'. +!!! error TS2345: Signature '(item: Foo | Bar): false' must be a type predicate. + const r4 = skipIf(items, (item) => true); // error + ~~~~~~~~~~~~~~ +!!! error TS2345: Argument of type '(item: Foo | Bar) => true' is not assignable to parameter of type '(a: Foo | Bar) => a is Foo | Bar'. +!!! error TS2345: Signature '(item: Foo | Bar): true' must be a type predicate. + + 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. + \ 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..93bc796283858 --- /dev/null +++ b/tests/baselines/reference/inferContextualTypePredicates1.symbols @@ -0,0 +1,91 @@ +//// [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); // error +>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); // error +>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)) + diff --git a/tests/baselines/reference/inferContextualTypePredicates1.types b/tests/baselines/reference/inferContextualTypePredicates1.types new file mode 100644 index 0000000000000..c3d0c71e7de0c --- /dev/null +++ b/tests/baselines/reference/inferContextualTypePredicates1.types @@ -0,0 +1,175 @@ +//// [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); // error +>r3 : never[] +> : ^^^^^^^ +>skipIf(items, (item) => false) : never[] +> : ^^^^^^^ +>skipIf : (as: A[], predicate: (a: A) => a is B) => Exclude[] +> : ^ ^^ ^^^^^^^^^ ^^ ^^ ^^ ^^ ^^^^^ +>items : (Foo | Bar)[] +> : ^^^^^^^^^^^^^ +>(item) => false : (item: Foo | Bar) => false +> : ^ ^^^^^^^^^^^^^^^^^^^^^ +>item : Foo | Bar +> : ^^^^^^^^^ +>false : false +> : ^^^^^ + +const r4 = skipIf(items, (item) => true); // error +>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) => true +> : ^ ^^^^^^^^^^^^^^^^^^^^ +>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" +> : ^^^^^^^^ + diff --git a/tests/cases/compiler/inferContextualTypePredicates1.ts b/tests/cases/compiler/inferContextualTypePredicates1.ts new file mode 100644 index 0000000000000..4948fbe00f6b3 --- /dev/null +++ b/tests/cases/compiler/inferContextualTypePredicates1.ts @@ -0,0 +1,20 @@ +// @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); // error +const r4 = skipIf(items, (item) => true); // error + +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 From e10853f519479edf4bd92b2fef45ddbcf1cae390 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Sat, 21 Dec 2024 13:23:28 +0100 Subject: [PATCH 2/3] handle boolean literals --- src/compiler/checker.ts | 8 ++ .../inferContextualTypePredicates1.errors.txt | 35 +++-- .../inferContextualTypePredicates1.symbols | 85 ++++++++++- .../inferContextualTypePredicates1.types | 135 ++++++++++++++++-- .../inferContextualTypePredicates1.ts | 23 ++- 5 files changed, 259 insertions(+), 27 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 6ba276cf61d9d..823675666af65 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -38802,6 +38802,14 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } const expr = skipParentheses(singleReturn, /*excludeJSDocTypeAssertions*/ true); const returnType = checkExpressionCached(expr); + 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) : diff --git a/tests/baselines/reference/inferContextualTypePredicates1.errors.txt b/tests/baselines/reference/inferContextualTypePredicates1.errors.txt index 566c2f4315e97..90e9f01166123 100644 --- a/tests/baselines/reference/inferContextualTypePredicates1.errors.txt +++ b/tests/baselines/reference/inferContextualTypePredicates1.errors.txt @@ -1,12 +1,8 @@ -inferContextualTypePredicates1.ts(13,26): error TS2345: Argument of type '(item: Foo | Bar) => false' is not assignable to parameter of type '(a: Foo | Bar) => a is Foo | Bar'. - Signature '(item: Foo | Bar): false' must be a type predicate. -inferContextualTypePredicates1.ts(14,26): error TS2345: Argument of type '(item: Foo | Bar) => true' is not assignable to parameter of type '(a: Foo | Bar) => a is Foo | Bar'. - Signature '(item: Foo | Bar): true' must be a type predicate. 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 (3 errors) ==== +==== inferContextualTypePredicates1.ts (1 errors) ==== type Foo = { type: "foo"; foo: number }; type Bar = { type: "bar"; bar: string }; @@ -19,18 +15,31 @@ inferContextualTypePredicates1.ts(17,7): error TS2322: Type '(a: string | null, 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); // error - ~~~~~~~~~~~~~~~ -!!! error TS2345: Argument of type '(item: Foo | Bar) => false' is not assignable to parameter of type '(a: Foo | Bar) => a is Foo | Bar'. -!!! error TS2345: Signature '(item: Foo | Bar): false' must be a type predicate. - const r4 = skipIf(items, (item) => true); // error - ~~~~~~~~~~~~~~ -!!! error TS2345: Argument of type '(item: Foo | Bar) => true' is not assignable to parameter of type '(a: Foo | Bar) => a is Foo | Bar'. -!!! error TS2345: Signature '(item: Foo | Bar): true' must be a type predicate. + 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 index 93bc796283858..052286a7a24b8 100644 --- a/tests/baselines/reference/inferContextualTypePredicates1.symbols +++ b/tests/baselines/reference/inferContextualTypePredicates1.symbols @@ -59,13 +59,13 @@ const r2 = skipIf(items, (item) => item.type === "foo" || item.type === "bar"); >item : Symbol(item, Decl(inferContextualTypePredicates1.ts, 11, 26)) >type : Symbol(type, Decl(inferContextualTypePredicates1.ts, 1, 12)) -const r3 = skipIf(items, (item) => false); // error +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); // error +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)) @@ -89,3 +89,84 @@ const pred2: (a: string | null, b: string | null) => b is string = (a, b) => typ >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 index c3d0c71e7de0c..c2c5d9d06e07f 100644 --- a/tests/baselines/reference/inferContextualTypePredicates1.types +++ b/tests/baselines/reference/inferContextualTypePredicates1.types @@ -97,23 +97,23 @@ const r2 = skipIf(items, (item) => item.type === "foo" || item.type === "bar"); >"bar" : "bar" > : ^^^^^ -const r3 = skipIf(items, (item) => false); // error ->r3 : never[] -> : ^^^^^^^ ->skipIf(items, (item) => false) : never[] -> : ^^^^^^^ +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) => false -> : ^ ^^^^^^^^^^^^^^^^^^^^^ +>(item) => false : (item: Foo | Bar) => item is never +> : ^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ >item : Foo | Bar > : ^^^^^^^^^ >false : false > : ^^^^^ -const r4 = skipIf(items, (item) => true); // error +const r4 = skipIf(items, (item) => true); // ok >r4 : never[] > : ^^^^^^^ >skipIf(items, (item) => true) : never[] @@ -122,8 +122,8 @@ const r4 = skipIf(items, (item) => true); // error > : ^ ^^ ^^^^^^^^^ ^^ ^^ ^^ ^^ ^^^^^ >items : (Foo | Bar)[] > : ^^^^^^^^^^^^^ ->(item) => true : (item: Foo | Bar) => true -> : ^ ^^^^^^^^^^^^^^^^^^^^ +>(item) => true : (item: Foo | Bar) => item is Foo | Bar +> : ^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ >item : Foo | Bar > : ^^^^^^^^^ >true : true @@ -173,3 +173,118 @@ const pred2: (a: string | null, b: string | null) => b is string = (a, b) => typ >'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/cases/compiler/inferContextualTypePredicates1.ts b/tests/cases/compiler/inferContextualTypePredicates1.ts index 4948fbe00f6b3..a2ed79d183fe6 100644 --- a/tests/cases/compiler/inferContextualTypePredicates1.ts +++ b/tests/cases/compiler/inferContextualTypePredicates1.ts @@ -13,8 +13,27 @@ 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); // error -const r4 = skipIf(items, (item) => true); // error +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; +} From 1a368711ccef782f37a5d5df5831d90beec6dcc3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Sun, 22 Dec 2024 00:14:01 +0100 Subject: [PATCH 3/3] update baselines --- tests/baselines/reference/targetTypeArgs.types | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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