ts-reset icon indicating copy to clipboard operation
ts-reset copied to clipboard

Array.isArray problem with narrowed type

Open plehnen opened this issue 2 years ago • 9 comments

Hi, I recently saw your YT video and wanted to add ts-reset to our project. But there is an issue which I find difficult to overcome:

Basically the problem is that isArray is always adding the type unknown[] to my variable, which I know is of type T | T[]. So when I check whether it's an array, I should know it's T[], but TS now thinks it's T[] & unknown[] which is a problem.

I tried to replicate it here: https://www.typescriptlang.org/play?ts=4.9.5#code/JYOwLgpgTgZghgYwgAgIJSnAngYQPYgDOYUArgmHlMgN4BQyywh6mWAFHFAOYBcycEFgCU-LtyaFkpEAGsQeAO4gA2gF0A3HQC+dOmCwAHFAFUQXNgB4AKgD5kAXmTXkEAB6QQAEymtsl0BhoZBN7AH4Q5H5rLToYGQpgAmRIYhtbdgA3OAAbUgho4Vo6AEhShAJiJhAQYKdLAA1XDwhvKTMLfzsMzP4G9SKHe3oSkoB6MeRS3TKS4Bhkdj8sADpmZazc-OEikbma6E28iGEtEt1tIA

Any advice maybe? :)

plehnen avatar Feb 22 '23 11:02 plehnen

Isn't the test suppose to be like this? https://www.typescriptlang.org/play?ts=4.9.5&ssl=14&ssc=2&pln=1&pc=1#code/JYOwLgpgTgZghgYwgAgIJSnAngYQPYgDOYUArgmHlMgN4BQAkMIeplgDwAqyEAHpCAAmhZKRABrEHgDuIANoBdZAB9kUCHEEEANllESpsxQD4AFHCgBzAFzJuqsZJkgAlLYuXkzO3QC+dOhgxCmACZEhiLjMANzhtUghbeztFF1pGBAJiLxAQaGQAXmRTaKTUwuN0hgYAehrGf0ZgGGLWbAA6ZjasEriElzT6atj4iEYmXOhe0ZcGvyA

DeepDoge avatar Feb 22 '23 12:02 DeepDoge

Getting somewhere in #51, but it's nasty. Need a bit of help here.

@plehnen Test looks correct to me, well-caught. This is likely why the definitions themselves return any[].

mattpocock avatar Feb 22 '23 12:02 mattpocock

@mattpocock Unarray is creating a new array from a type. but the generic T is extending array, its might not only be an array.

So T can be for example number[] & { foo: string } Which in that case inner would expect number[], which would not work. Playground

Array.isArray(Object.assign([], { foo: "bar" })) would return true type _ = number[] & { foo: string } extends number[] ? true : false would also give true

DeepDoge avatar Feb 22 '23 13:02 DeepDoge

@DeepDoge Your meaning is unclear from the comment above. I don't see anything wrong with OP's issue.

mattpocock avatar Feb 22 '23 14:02 mattpocock

@mattpocock well then this fixes OP's issue:

isArray<T>(arg: T | T[]): arg is T[] extends T ? T[] : any[];

it takes advantage of these returning true

type _ = any[] extends any ? true : false
type _ = unknown[] extends unknown ? true : false 

while these returns false

type _ = number[] extends number ? true : false
type _ = string[] extends string ? true : false 
type _ = {}[] extends {} ? true : false 

It passes all test. If the input is any which is what a generic type by default is, it will return any[] If the input it unknown it will return unknown[]

DeepDoge avatar Feb 22 '23 15:02 DeepDoge

@DeepDoge Amazing work! Truly, thanks for your patience and skill in figuring this out. Feel free to PR (I'd love your name on the contributors list) and we can ship this.

mattpocock avatar Feb 22 '23 15:02 mattpocock

I'm confused, if Array.isArray returns true on a variable of type T | T[], it should not narrow the type of the variable to T[] but to unknown[], because it can very well be the case that T itself is unknown[]. For instance if T happens to be number[] and we apply Array.isArray to an array of numbers, narrowing its type to T[] (that is number[][]) is just wrong.

guillaumebrunerie avatar Feb 22 '23 22:02 guillaumebrunerie

@guillaumebrunerie When you do .isArray(number[]) or .isArray(number), T is a number number doesn't extend number[] so arg is any[] not T[], which is the default behavior, makes the final type number[]

if input is unknown or unknown[], T is a unknown, unknown[] extends unknown, so arg is T[] which is unknown[] Similarly: if input is any or any[], T is a any, any[] extends any, so arg is T[] which is any[]

Basically, all this is doing is if the input is unknown or unknown[] it gives unknown[] and not any[], that's all, everything else is the same default behavior.

if you have a function like:

 foo<T>(foo: T | T[]): T

when you give it number or number[] it returns number

DeepDoge avatar Feb 23 '23 01:02 DeepDoge

So can the PR https://github.com/total-typescript/ts-reset/pull/56 be merged now? I'd love to get those changes and start using ts-reset :)

plehnen avatar Feb 28 '23 06:02 plehnen