ts-reset
ts-reset copied to clipboard
Narrow types for certain cases of Array.includes
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!
}
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 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...
@schummar Thanks for the PR! Because of the downsides you pointed our in your description, I'll close it. You're right - there's no way around it. Type predicates can be a bit dodgy for that reason, so I want to avoid adding too many of them to ts-reset.
Fair enough. Thanks for looking into it.