ts-reset
ts-reset copied to clipboard
Array.isArray problem with narrowed type
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? :)
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
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 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 Your meaning is unclear from the comment above. I don't see anything wrong with OP's issue.
@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 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.
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
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
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 :)