You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
isArray insufficiently narrowing when dealing with... unions of specific generics? #1238
Namely, the type of value in f is unknown rather than A<T>. In the real code, it's much more complex types from drizzle - This is as much as I was able to simplify the code to reproduce the problem.
Some previous developer here worked around the problem by writing out own isArray (above) which seems to get around the problem in a pretty simple way.
This could definitely be TS working as intended, somewhere along the line. Could easily some compromises you've made in remeda for other use cases. But bringing it up in case you weren't aware.
I think that this is due to a limitation of TypeScript in general, when it comes to tracking generic parameter types throughout a function's logic. The gist is that TypeScript infers "early", converting anything derived from a generic parameter to what they are in the type's definition, instead of keeping it generic and coupled with the type-param it was inferred from.
The easiest example would be:
functionfoo<Textendsreadonlyunknown[],Sextendsreadonlyunknown[]>(ts: T,ss: S,callbackT: (item: T[number]|undefined)=>void,callbackS: (item: S[number]|undefined)=>void,): void{constt=ts[0];// ^? const t: unknownconsts=ss[0];// ^? const s: unknown// These just happen to work because the generic typing *happens* to work.callbackT(t);callbackS(s);// Because this is broken and TypeScript isn't saying anything about it...callbackT(s);callbackS(t);}
The only reason using the native Array.isArray works in your example is that it narrows the array down to any[] instead of unknown[] so you are effectively turning off type-checking by using it. This is one of the reasons that the Remeda isArray returns unknown[] to begin with, and why it's safer to use than the native one.
functionbar<T,SextendsT|readonlyT[]>(data: S): [T,T]{constremedaIsArray=isArray(data) ? data[0] : data;// ^? const remedaIsArray: unknownconstnativeIsArray=Array.isArray(data) ? data[0] : data;// ^? const nativeIsArray: any// TypeScript would surface `remedaIsArray` as being wrong (because `unknown` is not `T`)// but it wouldn't surface nativeIsArray as being wrong because type checking is turned off// and not because it's correctly type-checked.return[remedaIsArray,nativeIsArray];}
Here's my issue in the TypeScript project which you can thumb up to help prioritize: microsoft/TypeScript#61750
I wanted to double-check myself on this and now think that my explanation above is incorrect and that this is a real bug, but I can't find a way to fix it without breaking "headless" invocations of the function (e.g. filter(DATA, isArray)) and that is a big enough breaking change that I can't do it without bumping the major version for Remeda.
I have a plan to remove "headless" invocations from our type-guards (isString, isNumber, etc...) for Remeda V3 anyway, so this is definetly planned.
I'll keep this issue open for now so I don't forget looking into it once we start working on v3.
Thanks for re-opening. Yeah the Array.isArray in my workaround shouldn't be affecting the typing of isArray2 at all. Too bad you haven't been able to fix it though. Isn't TypeScript fun sometimes? I'll keep an eye out for V3!
Activity
eranhirsch commentedon Dec 28, 2025
I think that this is due to a limitation of TypeScript in general, when it comes to tracking generic parameter types throughout a function's logic. The gist is that TypeScript infers "early", converting anything derived from a generic parameter to what they are in the type's definition, instead of keeping it generic and coupled with the type-param it was inferred from.
The easiest example would be:
The only reason using the native
Array.isArrayworks in your example is that it narrows the array down toany[]instead ofunknown[]so you are effectively turning off type-checking by using it. This is one of the reasons that the RemedaisArrayreturnsunknown[]to begin with, and why it's safer to use than the native one.Here's my issue in the TypeScript project which you can thumb up to help prioritize: microsoft/TypeScript#61750
https://www.typescriptlang.org/play/?#code/JYWwDg9gTgLgBAbzsAzgQSlAhgTzgXzgDMoIQ4AiKAUxGoBMsKBuAKFaIFcA7AYxmARuxCBAA8AFTjUAHjGrd6KODSz0hAGzw8A1twgB3bgG0AugBo4AZWlyFSldTWbt3PYZOmAfAApWcOBgUAC44CXN-OBQQ6wiA3iwNDQAjLF4dCVCfYHkQUIljbk4QZOooUzgAHzgeemoiYG4GAEo4AF4vOAA3CGB6OLgEpNT0qyyc2lCrQuLS8qqaxXrGlvbOnr6I5tCN+kRI3iEUeHg2wJRjAAZTNgCAejuAgIA9AH4Do-hlM+irm8iHk84G92PdHhIABbUFDUOAAK04xzgEKwYDACkCEDgBmgOjgpQSiNhMChcAA5goysBeIEcGBGmS4AAqFFohQoJmY7G4gB0B0SKTSGR8MGat0GApGOisPhQYtBcEBACFqISYYEIahkMpkqQdBisIownTqFZeFBgGB4KhuAByL64BlwQ04ElOrDJCCca0wHl+-nDIUSWXy+KSoUy0VsfDsLh8ARCfFYKCSSw2WTyRTKKTVVTqbhaMJmXyMGBYKbbODGcJhCoID7cJE0OiMACS6EwuHa2ow2BwPlLWFarzgg7+cFCg-FgKeIPinzg3CwAi61Hbva7Zw3OB5qG3A+XQ7gI7H1wno8P08es-ekRoME4UGExmbDCw687OEsS5Xa47ff+GMgA
eranhirsch commentedon Dec 29, 2025
I wanted to double-check myself on this and now think that my explanation above is incorrect and that this is a real bug, but I can't find a way to fix it without breaking "headless" invocations of the function (e.g.
filter(DATA, isArray)) and that is a big enough breaking change that I can't do it without bumping the major version for Remeda.I have a plan to remove "headless" invocations from our type-guards (isString, isNumber, etc...) for Remeda V3 anyway, so this is definetly planned.
I'll keep this issue open for now so I don't forget looking into it once we start working on v3.
abrenneke commentedon Dec 29, 2025
Thanks for re-opening. Yeah the
Array.isArrayin my workaround shouldn't be affecting the typing ofisArray2at all. Too bad you haven't been able to fix it though. Isn't TypeScript fun sometimes? I'll keep an eye out for V3!