-
Notifications
You must be signed in to change notification settings - Fork 12.5k
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
Support ReadonlyArray.includes as a type guard #31018
Comments
Without #15048, this is a problematic change. Consider: const someStrings: ReadonlyArray<string> = ["a", "b", "c"];
function fn(x: number | string) {
if (someStrings.includes(x)) {
//
} else {
// here, x is assumed to be 'number', but that's wrong!
}
}
fn("d"); |
That’s why I only want this to apply when |
That has the same problem though. A type is always a bound on a value, not an exact specification const someStrings: ReadonlyArray<"a" | "b" | "c"> = ["a"];
function fn(x: "b" | "x") {
if (someStrings.includes(x)) {
//
} else {
// here, x is wrongly assumed to be "x"
}
}
fn("b"); |
Yeah, but you probably shouldn't be doing My use case is |
This issue has been marked 'Working as Intended' and has seen no recent activity. It has been automatically closed for house-keeping purposes. |
I encountered the same case. const OS_TYPE = ['Linux', 'macOS', 'Windows'] as const
type OSType = typeof OS_TYPE[number]
//user defined type guard
function isOSType(s: string):s is OSType{
if ((OS_TYPE as any as string[]).includes(s))
return true;
return false;
}
|
Try |
Here is a generic version of the typeguard: const isInArray = <T, A extends T>(
item: T,
array: ReadonlyArray<A>
): item is A => array.includes(item as A); |
I'm using workaround:
|
@RyanCavanaugh i would like to make that: const someStrings: = ["a", "b", "c"] as const;
function fn(x: number | string) {
if (someStrings.includes(x)) {
// 'a' | 'b' | 'c'
} else {
// ok, still string | number, why not?
}
}
fn("d"); |
I faced the same issue and found a workaround which is very simillar to the one @artem-popov suggested. But I use one const myList = ['1', '2'] as const;
function doSomething(value: string) {
// dirty hack to check if the value is one of entries of the list
const _value = myList.find(listValue => listValue === value);
if (_value) {
// _value is "1" | "2"
} else {
// _value is any other string
}
} |
BTW I don't understand why this issue was closed. Is there any sensible reasons why |
I agree this should reopened for the simple reason that the item passed to So, TypeScript strictness here doesn't actually improve the type safety of the code, it just makes it inconvenient to write the code. Also, I completely don't understand @RyanCavanaugh's example where he says, "here, Meanwhile, here is my current workaround. function belongsToArray<TValue>(value: unknown, allowedValues: ReadonlyArray<TValue>): value is TValue {
return (allowedValues as ReadonlyArray<unknown>).includes(value);
} I prefer casting the array to |
Facing this right now, instead I have to do this work-around: const notSureIfValid = 'some value'
const valid = ARRAY_OF_VALID_VALUES.find(
(c): c is typeof ARRAY_OF_VALID_VALUES[number] => c === notSureIfValid
)
// now you can check valid for `undefined` and you will get what you want
if (!valid) { return }
// typescript properly discriminates the 'undefined' value and the rest are left in the type so verbose... |
@Sleepful |
interface ReadonlyArray<T> {
/**
* Determines whether an array includes a certain element, returning true or false as appropriate.
* @param searchElement The element to search for.
* @param fromIndex The position in this array at which to begin searching for searchElement.
*/
includes(searchElement: T, fromIndex?: number): boolean;
} The type of This type locks you on only passing values that belong to the array and the return of So maybe a better type is interface ReadonlyArray<T> {
/**
* Determines whether an array includes a certain element, returning true or false as appropriate.
* @param searchElement The element to search for.
* @param fromIndex The position in this array at which to begin searching for searchElement.
*/
includes(searchElement: any, fromIndex?: number): searchElement is ReadonlyArray<T>[number];
} |
This article explains the issue nicely and offers a pretty good helper as a workaround: https://fettblog.eu/typescript-array-includes/ (see under the "Option 2: A helper with type assertions" header) |
it uses casting |
It doesn't need to be a fair check since it's a workaround. What's important is that the article explains well this particular shortcoming of Typescript and offers a cute workaround that's helpful until this shortcoming is fixed (or not). |
@doronhorwitz my point is any workaround is bad in typescript. Since you use const VALID_VALUES = ['a', 'b'];
function doStuffIfValid(x: string) {
if (VALID_VALUES.includes(x)) {
(x as unknown as typeof VALID_VALUES) // now x is typeof VALID_VALUES
}
} If you hid the casting somewhere deep, it's not better then any casting 'in place'. And this issue is from people, who don't like casting, because it's turning off fair checks and you can get runtime error. So pls stop offering cheats. We like typescript because it offers fair check (is most situations :-) ) |
"Cheats"... well that escalated quickly. |
Love this solution, IMO this makes intuitive sense, any chance this could be opened? |
Bumping this. Still aggravating that we have to cast around a very legitimate use case for |
The reasoning is correct, not sure why people are mad at me. The feature described in that comment is #15048. Without that functionality, there's no way to do what's being proposed -- |
The issue I and I believe several others have in this discussion is that In other words, I do not believe that this should result in a compiler error (playground link) const myArr = ["one", "two", "three"] as const;
const someString: string = "three";
myArr.includes(someString); |
That's a different problem than whether or not
This is a fine belief to have, and you are free to write interface Array<T> {
includes(arg: any): boolean;
} in a global .d.ts in your project to get that behavior. |
To be more precise/dynamic in this case the argument may be of type
|
Oh duh. Yeah, |
I was burned by same issue would it make sense to update public node |
This should be reopened; this is a feature gap and there's absolutely no reason other than "we don't feel like prioritizing this" not to address it. |
Search Terms
Suggestion
For cases where
ReadonlyArray
's type argument is a subtype of string or some other type handled byas const
, it’s useful to be able to do:Note that the above produces an error on line 3 about
string
not being assignable to'a' | 'b'
in theVALID_VALUES.includes(x)
call.Use Cases
I want to use this to improve type guards within code that interacts with untyped third‑party JavaScript.
Examples
some-module/index.ts
Right now,
VALID_VALUES
has to be expressed as:for the above to not cause compilation errors and the type guard to work properly.
Note that this only works correctly when
T
is a literal union type.untyped-third-party-code.js
Checklist
My suggestion meets these guidelines:
The text was updated successfully, but these errors were encountered: