Skip to content

ReadonlyArray.some does not perform type narrowing #47574

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
JUSTIVE opened this issue Jan 24, 2022 · 5 comments
Closed

ReadonlyArray.some does not perform type narrowing #47574

JUSTIVE opened this issue Jan 24, 2022 · 5 comments
Labels
Design Limitation Constraints of the existing architecture prevent this from being fixed

Comments

@JUSTIVE
Copy link

JUSTIVE commented Jan 24, 2022

Bug Report

πŸ”Ž Search Terms

type narrowing, array, Array.some

πŸ•— Version & Regression Information

seems to be every version of typescript shows this behavior

⏯ Playground Link

Playground link with relevant code

πŸ’» Code

I was writing some kind of type narrowing/validating code with Arrays, and found out that array.some could not narrow types!

I embedded playground code with comments and also pasted it down here.
P.S. I already saw #14963 and it says its already fixed with ReadonlyArray, but not worked for me

function isRight<T>(sideValue:Side<T>):sideValue is Right<T>{
  return sideValue.sideType==='Right'
}

function anyRight<T>(sideValueArray:ReadonlyArray<Side<T>>):sideValueArray is ReadonlyArray<Right<T>>{
  return sideValueArray.some(isRight)
}

function usage(){
  const R3:Right<number> = {sideType:"Right"}
  const L5:Left<number> = {sideType:"Left",value:5}
  const R7:Right<number> = {sideType:"Right"}

  //declare values as ReadonlyArray in order to prevent modify memebers
  const values:ReadonlyArray<Side<number>> = [R3,L5,R7]
  //early return in case of any of them is Right
  if(anyRight(values)) return null

  //all members in "values" should be Left<number> here,
  //but typescript cannot filter out possibilities of member of values could be Right<number>
  return values.map(x=>x.value)
}

πŸ™ Actual behavior

ReadonlyArray.any | ReadonlyArray.some could not filter out specific types with given typeguard functions

πŸ™‚ Expected behavior

ReadonlyArray.any | ReadonlyArray.some should filter out specific types with given typeguard functions

@MartinJohns
Copy link
Contributor

Duplicate of #46894. There is no way for TypeScript to type an array where one of the values is of a specific type.

P.S. I already saw #14963 and it says its already fixed with ReadonlyArray, but not worked for me

That issue is about every, not about some.

@JUSTIVE
Copy link
Author

JUSTIVE commented Jan 24, 2022

Yes, of course, there's no way that which member of an array is of a specific type, but from the code above, in case any of member is specific type then returned, lines below the return statement must not be the specified type.

@jcalz
Copy link
Contributor

jcalz commented Jan 24, 2022

I know what some() is but I don't know what any() is. Is that from a different programming language? Maybe you want to edit the title?


I don't think you want anyRight to return sideValueArray is ReadonlyArray<Right<T>>, which implies that a true result means that all the elements of sideValueArray are Right<T>. For all we know it could just be the first element that's a Right<T> and then the compiler will have unsafely assumed that all the rest of the elements are Right<T>. So the current return type is a problem. But there's no way to write it correctly in the language.

There are unfortunately no negated types in TypeScript, so there's no representation of the sort of narrowing that happens primarily when a type guard function returns false. It would be nice if you could have some<S extends T>(pred: (val: T)=>value is not S): this is not readonly S[]. If that were possible then anyRight could return sideValueArray is not ReadonlyArray<Left<T>> and maybe a false result would narrow values to ReadonlyArray<Left<number>> as desired. But it's not possible so we can't do this.

That means the main problem here isn't some bug in some(), but lack of language support for negated types or perhaps a "fine-grained else type guard" as requested in #15048.


Note that in this case you're fighting against the language. Assuming you want to make progress before waiting for TypeScript to change, you will be much happier if you refactor to isLeft() and allLeft() instead of isRight() and anyRight(), like this (Playground link). That puts the narrowing behavior on the true branch instead of the false branch which is easier to represent.

@JUSTIVE JUSTIVE changed the title ReadonlyArray.any does not perform type narrowing ReadonlyArray.some does not perform type narrowing Jan 24, 2022
@RyanCavanaugh RyanCavanaugh added Working as Intended The behavior described is the intended behavior; this is not a bug Design Limitation Constraints of the existing architecture prevent this from being fixed and removed Working as Intended The behavior described is the intended behavior; this is not a bug labels Jan 24, 2022
@RyanCavanaugh
Copy link
Member

To supplement @jcalz 's correct response, the key thing is that user-defined type guards (UDTGs) can narrow unions (including in the false branch), but Array<Side<T>> is not a union type - it's a type containing a union, which is a different beast. UDTGs can also narrow via intersection, which is why allLeft works.

The narrowing of Array<A | B> to Array<B> by excluding A is sound for arrays because of what arrays do, but isn't universally valid for an arbitrary T<U | V>.

@JUSTIVE
Copy link
Author

JUSTIVE commented Jan 24, 2022

@jcalz Thanks for your kind explanations. First of all, I confused some with any as you mentioned(maybe with python), so I changed it. thanks. and actually, the example I wrote above is a minified version of the problem which I'm handling in my company's code. with your help, I refactored it, but it's still not narrowing type correctly. Think I should find out what I'm missing here.
@RyanCavanaugh also thanks for your kindness. if I want to separate Array<a|b> with UDTGs, should I write UDTG like
(x:Array<a>|Array<b>)=>x is Array<a>?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Design Limitation Constraints of the existing architecture prevent this from being fixed
Projects
None yet
Development

No branches or pull requests

4 participants