Skip to content

Regression in conditional type predicate  #51242

@graphemecluster

Description

@graphemecluster

Bug Report

Search Terms: type assertion, type comparison, related to, generic parameter

Version & Regression Information: This changed between versions 4.7 and 4.8

Code: Playground Link

I discovered this issue in my PR #50454 when I am looking for a possible solution to the typing of Array.isArray.
Consider the following code:

declare function f0<T>(arg: T): arg is Extract<T, readonly any[]>;
declare function f1<T>(arg: T): arg is Extract<T, any[]>;
declare function f2<T>(arg: T): arg is {} extends T ? T & any[] : Extract<T, readonly any[]>;
declare function f3<T>(arg: T): arg is {} extends T ? T & any[] : Extract<T, any[]>;
declare function f4<T>(arg: T): arg is T extends any ? {} extends T ? T & any[] : Extract<T, readonly any[]> : never;
declare function f5<T>(arg: T): arg is T extends any ? {} extends T ? T & any[] : Extract<T, any[]> : never;
declare function f6<T>(arg: T): arg is T extends any ? Extract<T, readonly any[]> : never;
declare function f7<T>(arg: T): arg is T extends any ? Extract<T, any[]> : never;
declare function f8<T>(arg: T): arg is T extends readonly any[] ? T : never;
declare function f9<T>(arg: T): arg is T extends any[] ? T : never;
declare function fa<T>(arg: T): arg is {} extends T ? T & any[] : T extends readonly any[] ? T : never;
declare function fb<T>(arg: T): arg is {} extends T ? T & any[] : T extends any[] ? T : never;
declare function fc<T>(arg: T): arg is T extends any ? {} extends T ? T & any[] : T extends readonly any[] ? T : never : never;
declare function fd<T>(arg: T): arg is T extends any ? {} extends T ? T & any[] : T extends any[] ? T : never : never;
declare function fe<T>(arg: T): arg is T extends any ? T extends readonly any[] ? T : never : never;
declare function ff<T>(arg: T): arg is T extends any ? T extends any[] ? T : never : never;

function case1<T extends any>(a: T | T[]): T[] {
	return f(a) ? a : [a];
}
function case2<T extends unknown>(a: T | T[]): T[] {
	return f(a) ? a : [a];
}
function case3<T>(a: T | T[]): T[] {
	return f(a) ? a : [a];
}

In the above, f4 is the simplified version of the type I used in the PR.

Actual behavior:

From 4.8, the results vary from function to function. In case 3, an error occurs when f is f4 but not in case 1 and case 2 or the case when f is f2 where T is not distributed using T extends any.
Here is a table of whether a ts2322 error occurs in each cases:

f Case 1 Case 2 Case 3
f0 🛑 🛑 🛑
f1
f2
f3
f4 🛑
f5
f6 🛑 🛑 🛑
f7

For the latter half, f(n+8) is simply fn with Extract expanded out, but I could hardly understand why the behavior changes:

f Case 1 Case 2 Case 3
f8 🛑 🛑 🛑
f9
fa
fb
fc 🛑
fd 🛑
fe 🛑 🛑 🛑
ff 🛑 🛑 🛑

(Tested in v4.9.0-dev.20221020 and 4.8.4)

Prior to 4.8, ALL the cases passed:

f Case 1 Case 2 Case 3
f0
f1
f2
f3
f4
f5
f6
f7
f8
f9
fa
fb
fc
fd
fe
ff

(Tested in 4.7.4 and 4.6.4)

(Note: some of the test functions above are for control (i.e. comparison) only.)

Expected behavior:

For each row, the results of case 1, case 2 and case 3 should be identical. (f4, fc, fd)
For each column, the results of fn and f(n+8) should be identical. (f5, fd, f7, ff)

I understand that some of them must be desirable changes, so I am not expecting the results to be the same between 4.7 and 4.8, but the mismatched results due to the absent of generic constraint are really weird.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Not a DefectThis behavior is one of several equally-correct options

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions