Skip to content

Incorrect behavior of ArrayLike<infer T> | Iterable<infer T>? #49764

Closed

Description

Bug Report

Search Terms: assignability, extends, generics, array like, iterable

Version & Regression Information: This is the behavior in every version I tried

Code: Playground link with relevant code

// Non-distributive:
type Foo0 = ArrayLike<string> | Iterable<string> | number[] extends ArrayLike<any> | Iterable<any> ? true : false
//   ^?: type Foo0 = true
type Bar0 = ArrayLike<string> | Iterable<string> | number[] extends ArrayLike<infer T> | Iterable<infer T> ? T[] : never
//   ^?: type Bar0 = never
type Baz0 = ArrayLike<string> | Iterable<string> | number[] extends ArrayLike<infer T> | Iterable<infer U> ? T[] | U[] : never
//   ^?: type Baz0 = never

// Distributive:
type Foo1T<T> = T extends ArrayLike<any> | Iterable<any> ? true : false
type Foo1 = Foo1T<ArrayLike<string> | Iterable<string> | number[]>
//   ^?: type Foo1 = true
type Bar1T<T> = T extends ArrayLike<infer T> | Iterable<infer T> ? T[] : never
type Bar1 = Bar1T<ArrayLike<string> | Iterable<string> | number[]>
//   ^?: type Bar1 = number[] | string[]
type Baz1T<T> = T extends ArrayLike<infer T> | Iterable<infer U> ? T[] | U[] : never
type Baz1 = Baz1T<ArrayLike<string> | Iterable<string> | number[]>
//   ^?: type Baz1 = number[] | string[] | unknown[]

Expected Behavior: Types Bar0, Baz0 and Baz1 being number[] | string[].

Activity

  1. RyanCavanaugh commented on Jul 5, 2022

    @RyanCavanaugh
    Member

    This all looks correct to me. Can you make a more explicit case for why you consider this wrong?

    Aside: it's very confusing to have same-named variables in the same small repro

  2. graphemecluster commented on Jul 6, 2022

    @graphemecluster
    ContributorAuthor

    I was digging into the behavior of the Array-related types when I was making up a good definition of Array.isArray for, again, uhyo/better-typescript-lib.

    I've edited the comment. Hopefully it's clearer now.

    For the first 3 types, if Foo0 holds, I suspect that Bar0 and Baz0 also hold.

    For the last type, Baz1, I can't understand why it consists of an unknown[].

  3. RyanCavanaugh commented on Jul 7, 2022

    @RyanCavanaugh
    Member

    I see. I believe this misunderstanding is what infer does when the same type variable appears in multiple positions.

    What you're expecting is that these utterances act as separate paths that the checker can take, but that's not how it works. When you have the same infer T, TS is trying to find a type that fits both instances at once. It's effectively the same as if you had written

    function check<T>(x: ArrayLike<T> | Iterable<T>) { }
    // Fails
    check(null as any as ArrayLike<string> | Iterable<string> | number[]);

    which fails because there are three candidates collected for T - string, string, and number.

    Baz1 includes an unknown because when zero candidates get collected for T or U respectively, the resulting inference is unknown. An equivalent example would be

    type Foo<T> = T extends { a: infer A } | { b: infer B } ? ({aa: A} | { bb: B }) : never;
    type S = Foo<{a : string}>;

    There's nothing to occur here which would exclude the { bb: B } part of the union.

  4. typescript-bot commented on Jul 10, 2022

    @typescript-bot
    Contributor

    This issue has been marked 'Working as Intended' and has seen no recent activity. It has been automatically closed for house-keeping purposes.

  5. locked as resolved and limited conversation to collaborators on Oct 22, 2025
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

    Working as IntendedThe behavior described is the intended behavior; this is not a bug

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions