Skip to content

Proposal: Add Overloads to Array.prototype.includes and ReadonlyArray.prototype.includes for Improved Type Safety and UsabilityΒ #61618

Closed

Description

πŸ” Search Terms

ReadonlyArray includes type-guard

βœ… Viability Checklist

⭐ 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

  1. What do you want to use this for?
  2. What shortcomings exist with current approaches?
  3. 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?

  • includes does 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

  1. MartinJohns commented on Apr 26, 2025

    @MartinJohns
    Contributor

    You mistakingly checked this point:

    This isn't a request to add a new utility type: https://github.com/microsoft/TypeScript/wiki/No-New-Utility-Types

  2. sugoroku-y commented on Apr 26, 2025

    @sugoroku-y
    Author

    You mistakingly checked this point:

    This isn't a request to add a new utility type: https://github.com/microsoft/TypeScript/wiki/No-New-Utility-Types

    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.

  3. RyanCavanaugh commented on Apr 28, 2025

    @RyanCavanaugh
    Member

    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.

  4. sugoroku-y commented on Apr 29, 2025

    @sugoroku-y
    Author

    I have understood your overall design philosophy.

    I close this issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    DeclinedThe issue was declined as something which matches the TypeScript visionSuggestionAn idea for TypeScript

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions