-
Notifications
You must be signed in to change notification settings - Fork 9
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
[feature request] Better Array.prototype.includes
#17
Comments
Thank you for suggestion! I see that the suggested signatures make Let's add this. |
@uhyo Wait – By adding this it means that we will also need to deal with methods like interface Array<T> {
includes<U>(searchElement: T extends U ? U : T, fromIndex?: number): searchElement is T extends U ? T : T;
} |
@graphemecluster Thank you for pointing this out. Personally I wouldn't extend this to methods like |
I tacked this and couldn't get to a perfectly working implementation. 😇 As mentioned above, we should only narrow in then-branch of function checkNarrowing(val: "pika" | "chu" | "pikachu") {
const strarr: ("pika" | "chu")[] = [];
if (strarr.includes(val)) {
expectType<"pika" | "chu">(val);
} else {
expectType<"pika" | "chu" | "pikachu">(val);
}
} |
I think it's all right to narrow the type in the else branch as well. If Consider the following example. Playground Link const expectType = <A>(a: A): A => a;
type Subtype<A, B> = A extends B ? A : never;
type Supertype<A, B> = A extends B ? B : unknown;
interface Includes<A> {
includes: <B>(searchElement: Supertype<A, B>) => searchElement is Subtype<A, B>;
}
declare const array: Includes<"foo" | "bar">;
declare const subtypeElement: "foo";
declare const sametypeElement: "foo" | "bar";
declare const supertypeElement: "foo" | "bar" | "baz";
declare const othertypeElement: "baz";
if (array.includes(subtypeElement)) {
expectType<"foo">(subtypeElement);
} else {
expectType<never>(subtypeElement);
}
if (array.includes(sametypeElement)) {
expectType<"foo" | "bar">(sametypeElement);
} else {
expectType<never>(sametypeElement);
}
if (array.includes(supertypeElement)) {
expectType<"foo" | "bar">(supertypeElement);
} else {
expectType<"baz">(supertypeElement);
}
if (array.includes(othertypeElement)) {
expectType<never>(othertypeElement);
} else {
expectType<"baz">(othertypeElement);
} |
Oh, wait. No, I'm mistaken. Didn't see that |
@ssssota You might not require narrowing at all. Consider using the following type Option<A> = { some: A } | null;
const find = <A>(needle: unknown, haystack: A[]): Option<A> => {
for (const element of haystack) {
if (element === needle) {
return { some: element };
}
}
return null;
};
declare const haystack: ("foo" | "bar")[];
declare const needle: "foo" | "bar" | "baz";
const option = find(needle, haystack);
const expectType = <A>(a: A): A => a;
if (option !== null) {
expectType<{ some: "foo" | "bar" }>(option);
expectType<"foo" | "bar" | "baz">(needle);
} else {
expectType<null>(option);
expectType<"foo" | "bar" | "baz">(needle);
} |
@aaditmshah |
I just revisited this and thought of a solution (which’s overly ordinary that you may even have thought of it before) while replying @cm-ayf’s |
I don't think it's possible to refine the type of the interface Array<T> {
includes<U>(searchElement: T extends U ? U : T, fromIndex?: number): boolean;
} This would allow you to write the following. declare const array: ("foo" | "bar")[];
declare const searchElement: "foo" | "bar" | "baz";
if (array.includes(searchElement)) {
expectType<"foo" | "bar" | "baz">(searchElement);
} else {
expectType<"foo" | "bar" | "baz">(searchElement);
} This works because However, it would prevent the following. declare const array: ("foo" | "bar")[];
declare const searchElement: "baz";
if (array.includes(searchElement)) { // type error
expectType<"baz">(searchElement);
} else {
expectType<"baz">(searchElement);
} This doesn't work because |
I think the above might be too permissive, because it allows search elements like I'd like to suggest the following modification, which allows search elements like type IfLiteralGetPrimitive<T> =
T extends string ? string : T extends number ? number : T extends bigint ? bigint : T extends boolean ? boolean : T;
interface Array<T> {
includes<U extends IfLiteralGetPrimitive<T>>(searchElement: T extends U ? U : T, fromIndex?: number): boolean;
} Playground Link (I modified the original playground because it worked a bit differently and didn't permit the union - this playground matches the behavior I see when I add the overload to the global Array interface) |
@ehoogeveen-medweb This would prevent searching an array of |
type IfLiteralGetPrimitive<T> =
T extends string ? number | string | symbol : T extends number ? number : T extends bigint ? bigint : T extends boolean ? boolean : T; so that all object keys are allowed when an array contains strings. I usually work with somewhat narrow object types like Edit: I'm also not sure searching an array for a full |
would it be possible treat it's totally valid case to do this and build, e.g. a type guard from it const nums = [1,2,3] as const
nums.includes(4) |
Not sure if this is best thing that we can have, but somehow works: declare namespace NarrowBlocker {
const dummy: unique symbol;
}
interface Array<T> {
includes(searchElement: unknown, fromIndex?: number): searchElement is T & {[NarrowBlocker.dummy]: never};
}
function checkNarrowing(val: "pika" | "chu" | "pikachu") {
const strarr: ("pika" | "chu")[] = [];
if (strarr.includes(val)) {
val;
// ^?
} else {
val;
// ^?
}
} |
I've found this to work well: interface Array<T> {
includes(
searchElement: T | (WidenLiteral<T>),
fromIndex?: number,
): boolean;
}
interface ReadonlyArray<T> {
includes(
searchElement: T | (WidenLiteral<T>),
fromIndex?: number,
): searchElement is T;
}
type WidenLiteral<T> =
T extends string ? string
: T extends number ? number
: T extends boolean ? boolean
: T extends bigint ? bigint
: T extends symbol ? symbol
: NonNullable<T>; It does not narrow |
The standard TypeScript type requires the first argument of the includes function to be a value in an array.
This requires that the type of the argument be known beforehand for union-type arrays (e.g.
[1,2,3] as const
).We can see that this is not a semantic error, but we may find this inconvenient.
So please consider introducing the following types into this project:
The text was updated successfully, but these errors were encountered: