Skip to content
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

Narrow types for certain cases of Array.includes #130

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

schummar
Copy link

As discussed in #49 a simple : searchElement is T won't do, because the assertion is too strong and produces wrong types, especially in the "opposite" cases (e.g. else blocks).

My suggestion is to reenable the type guard only for cases where it is safe: Tuple with well known entries - so only entries that are literals without unions.

For example this is safe, because x is 1 | 2 | 3 if and only if the array contains it:

const values = [1, 2] as [1, 2] // or as const
declare const x: 2 | 3;

if (values.includes(x)) {
  console.log(x);
  //          ^? // 2
} else {
  console.log(x);
  //          ^? // 3
}

Known problem

There is one remaining problem that I know of, where the type guard would do the wrong thing and I cannot imagine a way to prevent this. But depending on how you look at it, it might be ok, since it seems to me that this is actually due to a wider TypeScript problem (bug?)

If the array itself is a union:

const values2 = [1, 2] as [1, 2] | [2, 3]
declare const x2: 3 | 4

if (values2.includes(x2)) {
  console.log(x2);
  //          ^? // 3
} else {
  console.log(x2);
  //          ^? // 4 <-- wrong! if x is 3 we would still hit the else case
}

But the same problem arises with other type guards as well:

const f1 = (x: number): x is 1 => x === 1;
const f2 = (x: number): x is 2 => x === 2;
const f = f1 as typeof f1 | typeof f2;
const n = 2 as 1 | 2 | 3;

if (f(n)) {
  console.log(n);
  //          ^? // 1 | 2
} else {
  console.log(n);
  //          ^? // 3 <-- wrong!
}

Playground

@MiroslavPetrik
Copy link

MiroslavPetrik commented Nov 8, 2023

Damn this is good, I ignored the else behavior totally, seems like it works (for the last example) like

type ElsePath = Exclude<1 | 2 | 3, 1 | 2>
//   ^? 3

@schummar was this reported as a bug?

@schummar
Copy link
Author

schummar commented Nov 8, 2023

@schummar was this reported as a bug?

Not that I know of, but it's hard to tell, since there are so many open issues in the TypeScript repo. Maybe I should create one...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants