-
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
.includes or .indexOf does not narrow the type #36275
Comments
This pattern doesn't occur often enough in ways that would produce useful narrowings to justify implementing whatever we'd have to do to detect it |
If #36352 were revisited, the interface Array<T> {
- includes(searchElement: T, fromIndex?: number): boolean;
+ includes(searchElement: any, fromIndex?: number): searchElement is T;
} Then, this would be fine: if(["text"].includes(message.type)) {
message.type; // "text"
} IMO this is a much better way to type it than the current restriction, considering that the way it's currently typed makes
|
@RyanCavanaugh What is the reason for practically making |
@JGJP We didn't make it behave this way; this is the behavior absent the implementation of a feature that would cause it behave the way the OP is proposing. None of the existing narrowing mechanics apply here; we're talking about probably a thousand lines of new code to correctly detect and narrow arrays based on these methods, along with a performance penalty paid by every program because the control flow graph would have to be more granular to set up the possible narrowings caused by any method call, along with a bug trail and confusion trail caused by people expecting non-method versions of these functions to also behave the same way. The proposed interface Array<T> {
includes2(searchElement: any, fromIndex?: number): searchElement is T;
}
declare let s: string | number;
if (["foo"].includes2(s)) {
} else {
// 's' might be "bar" or any other string that's not "foo"
s.toFixed();
} |
@RyanCavanaugh Thanks for your detailed reply. I can appreciate that it could be super difficult to implement what is suggested in this issue, but how about just not implicitly typing the array? Why are other variables implicitly typed as |
I'm not sure what you're proposing. The current typing detects many kinds of errors, like function fn(s: string) {
const bannedUsers = ["bob", "alice"];
if (bannedUsers.includes(s.toLowerCase)) {
// you are banned |
@RyanCavanaugh function fn(s: string | null) {
const bannedUsers = ["bob", "alice"]
if (bannedUsers.includes(s)) {
// you are banned
}
} Here,
It also won't let us do things like What solves these issues is changing the const bannedUsers = ["bob", "alice"] as any[] I would personally prefer it if |
@RyanCavanaugh I'm wondering why you're linking that issue, because it just shows the community making reasonable arguments and trying to come up with a solution, whereas your general stance seems to just be |
@JGJP Why doesn't TypeScript already have every feature it will eventually need? The answer is that we haven't designed or completed those features yet, which is why we have an issue tracker and are employing a large team of developers to work on it. Having humans work on the product at a finite speed is in fact the best we can do, for now. |
Here's a declaration you can put in your project if you want the proposed "anything goes" behavior interface Array<T> {
includes(element: unknown, startIndex?: number): boolean;
} |
@RyanCavanaugh Thanks for the example. So just to clarify, the issue here is that there is no way to express something like "if true, element is T, else element may or may not be T"? |
@iansan5653 correct; see #15048 |
@RyanCavanaugh what do you think about this example? const superset = ['a','b','c'] as const
const subset = ['a','b'] as const
type Test<T extends typeof superset[number] = typeof superset[number]> = {
'a': {a:1,t:T},
'b': {b:2,t:T},
'c': {c:3,t:T},
}[T]
const test = {} as unknown as Test
if (subset.includes(test.t)) // error
re microsoft/TypeScript-DOM-lib-generator#973 PS: Posted here because linked issue doesn't seem to have any discussion going and related question already was under discussion here |
More simple example:
const value = 'hello';
const allowedTypes = ['number', 'bigint'] as const;
const numberIsh = allowedTypes.includes(typeof value); |
Echo'ing @HolgerJeromin findings. Wound up here trying to find information on the below scenario. const varStr: string | number | object = 7;
if (["string", "number"].includes(typeof varStr)) {
const foo = varStr; // foo is still inferred as "const foo: string | number | object"
} |
#52451 is an alternative, appreciate if you can help fix tests, otherwise I'll check it out when I have time. |
This issue has been marked as "Too Complex" and has seen no recent activity. It has been automatically closed for house-keeping purposes. |
Shouldn't tsc be able to simply transform an expression like
to
Internally? |
That's not going to be a very common way to do things -- once you need it more that once, you'll move to the array to a local, and once it's not a literal, it's not sound to negatively narrow. You can legally write const arr: ('low' | 'medium' | 'high')[] = [];
if (arr.includes(quality)) {
// not hit, of course
} else {
// 'quality' can be anything; cannot safely conclude it is number here
} |
TypeScript Version: 3.7.x-dev.201xxxxx
Search Terms:
.includes type narrowing
,.indexOf type narrowing
Code
Expected behavior:
I expect
message
to narrow its type toTextMessage
insideif (['text'].indexOf(message.type) > -1)
.Same way it does inside
if (message.type === 'text')
Actual behavior:
message
is typed asTextMessage | ImageMessage
inside theif
blockPlayground Link: Provided
Related Issues: #9842
My argument is that
if (message.type === 'text')
should be considered equivalent toif (['text'].includes(message.type))
.It might seem irrelevant on a small example like this, but if the array (
['text']
) is large, the workaround is difficult to maintain.The text was updated successfully, but these errors were encountered: