Skip to content

Incorrect type narrowing after Array.isArray typeguard on union with generic #46534

Closed

Description

@timbodeit

Bug Report

🔎 Search Terms

  • label:Bug typeguard
  • label:Bug narrow* incorrect

🕗 Version & Regression Information

  • This is the behavior in every version I tried, and I reviewed the FAQ for entries about "Bugs" that have existed in TS for a long time
  • I tested versions:

⏯ Playground Link

💻 Code

function logStringAssumption(input: string) {
    console.log(`Type system is sure that typeof ${input} (aka '${typeof input}') must be 'string'`);
}
function test<I>(input: string[] | I) {
  if (Array.isArray(input)) {
      logStringAssumption(input[0]);
  }
}
test([12345]);

🙁 Actual behavior

  • In line 6 of the code above (line 8 in the screenshot), input is inferred to be of type string[]
  • Passing input[0] into logStringAssumption compiles successfully

Screen Shot 2021-10-26 at 12 09 13

🙂 Expected behavior

  • The typechecker should account for the fact, that I can be some other array type such as number[]
  • In line 6, input should be inferred to be of type string[] | (I & any[])
  • Passing input[0] should not compile as any is not assignable to string

Activity

  1. k-tten commented on Oct 26, 2021

    @k-tten

    You haven't defined the type I and thus TypeScript has no reason to assume that I is an array-like type?

    Ok nevermind I see the generic 😅

    I don't quite agree that input should be inferred as string[] | (I & any[]) tho

    I see where you're coming from, but I think this problem arises from design: why would a function accept string[] or any type (the generic)

  2. fatcerberus commented on Oct 26, 2021

    @fatcerberus

    Usually for a same reason you might write:

    function isNullish<T>(x: T | null | undefined): x is T {
        return x != null;
    }

    We don’t have negated types, so the closest you can get for now is taking the union of a generic with a concrete type and letting type inference exclude the concrete case.

  3. k-tten commented on Oct 26, 2021

    @k-tten

    Eh I don't see the similarity in your example and theirs. Would you mind sharing another example? 🥲

    I see some similarities but I think the type guard and null | undefined makes it a tad different.

  4. fatcerberus commented on Oct 26, 2021

    @fatcerberus

    The idea is the same - by accepting string[] | I as a parameter, the type inference machinery ensures I isn't instantiated with string[]. It's not perfect (you can instantiate the generic manually, e.g.), but it's the closest we can get without negated types.

  5. andrewbranch commented on Nov 4, 2021

    @andrewbranch
    Member

    Duplicate of #45686

  6. typescript-bot commented on Nov 7, 2021

    @typescript-bot
    Contributor

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

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

    DuplicateAn existing issue was already created

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions