Repository navigation
Proposal: Add Overloads to Array.prototype.includes and ReadonlyArray.prototype.includes for Improved Type Safety and UsabilityΒ #61618
Description
π Search Terms
ReadonlyArray includes type-guard
β Viability Checklist
- This wouldn't be a breaking change in existing TypeScript/JavaScript code
- This wouldn't change the runtime behavior of existing JavaScript code
- This could be implemented without emitting different JS based on the types of the expressions
- This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, new syntax sugar for JS, etc.)
- This isn't a request to add a new utility type: https://github.com/microsoft/TypeScript/wiki/No-New-Utility-Types
- This feature would agree with the rest of our Design Goals: https://github.com/Microsoft/TypeScript/wiki/TypeScript-Design-Goals
β Suggestion
Add an overload to Array.prototype.includes and ReadonlyArray.prototype.includes that narrows the type of the searched value when the array is a literal tuple (e.g., declared with as const) containing only primitive literal values such as strings, numbers, booleans, null, or undefined.
This overload enables stricter type checking for includes calls and allows using includes as a type guard only when it is safe and meaningful to do so, avoiding false assumptions when working with general arrays.
interface ReadonlyArray<T> {
includes<U>(
s: U,
...args: IsLiteralArray<this> extends true
? T extends U
? [fromIndex?: number]
: never
: never
): s is IsLiteralArray<this> extends true ? (T extends U ? T : never) : never;
includes<U>(
s: U,
...args: T extends U ? [fromIndex?: number] : never
): boolean;
}
interface Array<T> {
includes<U>(
s: U,
...args: T extends U ? [fromIndex?: number] : never
): T extends U ? boolean : false;
}Supporting Utility Types:
type IsUnion<T> = (T extends T ? (p: T) => 0 : never) extends (p: T) => 0
? false
: true;
type IsLiteral<T> = IsUnion<T> extends true
? false
: T extends null | undefined | boolean
? true
: T extends PropertyKey
? {} extends Record<T, unknown>
? false
: true
: T extends bigint
? {} extends Record<`${T}`, unknown>
? false
: true
: false;
type IsLiteralArray<T extends readonly unknown[]> = IsUnion<T> extends true
? false
: IsLiteral<T['length']> extends true
? T extends readonly [infer F, ...infer R]
? IsLiteral<F> extends true
? IsLiteralArray<R>
: false
: true
: false;You can try it in the TypeScript Playground here
π Motivating Example
Currently, code like the following produces a type error, even though it is semantically correct:
const KEYS = ['aaa', 'bbb', 'ccc'] as const;
function handleKey(key: string) {
if (KEYS.includes(key)) {
// This should narrow key to 'aaa' | 'bbb' | 'ccc'
doSomething(key);
}
}To avoid this error, users often resort to unsafe casts or unnecessary widening of the array's type. On the other hand, relaxing the parameter type (e.g., using unknown) can lead to incorrect code such as:
const KEYS = ['aaa', 'bbb', 'ccc'] as const;
function handleKey(key: number) {
if (KEYS.includes(key)) {
// This is likely a bug, but it won't be caught
}
}The proposed overload solves this by enabling proper narrowing only when it is safe (i.e., when the array is a literal tuple of literals), and producing compile-time errors when there is no possible match.
π» Use Cases
- What do you want to use this for?
- What shortcomings exist with current approaches?
- What workarounds are you using in the meantime?
1. What do you want to use this for?
To safely narrow the type of a variable when checking its inclusion in a fixed set of literal values using .includes, especially in configuration validation, value guards, and API parameter checks.
2. What shortcomings exist with current approaches?
includesdoes not currently act as a type guard even when used with literal tuples.- It requires manual guards or unsafe type assertions.
- Overly permissive typing (e.g., accepting
unknown) eliminates type safety and allows obvious mistakes.
3. What workarounds are you using in the meantime?
- Writing custom type guards (
key is 'a' | 'b' | 'c') - Using
Set.has()instead of arrays - Using type assertions like
as any - Duplicating union types and literal arrays
Activity
MartinJohns commentedon Apr 26, 2025
You mistakingly checked this point:
sugoroku-y commentedon Apr 26, 2025
Sorry.
The type function is only to determine if it is a literal fixed array, so I did not intend to add that as a utility type, but if it is used in a global type, I would have to add it.
I unchecked it.
Thank you.
RyanCavanaugh commentedon Apr 28, 2025
We try to keep the standard library as straightforward as possible. While this appears to be safe, it's also extremely complex, and very difficult for anyone looking at it to understand what's going on. You're free to add these to your own project through declaration merging but this isn't a great fit for our overall design philosophy.
sugoroku-y commentedon Apr 29, 2025
I have understood your overall design philosophy.
I close this issue.