microsoft / microsoft/TypeScript

Generic type narrowing for truthy check has inconsistent / incorrect behavior

Open
#59,717 6 comments 1 reaction 1 assignee View on GitHub

@ahejlsberg is already working on this.

Since Aug 30, 2024.

Needs Investigation
Dominant language
Go
Stars
111k
Forks
14.3k
Avg merge
1d 19h
Merged PRs (30d)
117

Description

🔎 Search Terms

generic truthy narrowing generic index

🕗 Version & Regression Information

This is the behavior in every version I tried, and I reviewed the FAQ for entries about narrowing truthy generic types

⏯ Playground Link

https://www.typescriptlang.org/play/?ts=5.7.0-dev.20240821&ssl=29&ssc=2&pln=1&pc=1#code/MYewdgzgLgBATgU1HAJgLhgJSSVAeaOASzAHMAaGAVzAGswQB3MAPhgF4YBvAXwG4AsAChhAMxrAoRcDFEgQeACowEADygIwKCDEIlSMAD6yAhgBsICIzDBUzZlgAoAbuYyKAlN2ExfMIqIwLuZeXD5+Ea5mgkIREQD08QB6APzhcTAARiZwwWYeMRm+iMgoANpRALocMACshX48KhZWYbFFUelxialdPML9IkLiYJLSYLLyAEx5GHpk1qLmlta29qFdAUFRG+0ZUQ0ZPWl7cdm5O4dxJbjlVTX1fc0rbUUwnacJyScRg4NiEikMnOs10UGIZF2EUQUCocAmBwGQA

💻 Code
const record: Record<string, unknown> = {};

function foo<T extends string | false | null>(val: T) {
    if (val) {
        val;
        //^?
        bar(val);
        record[val] = 5;
    } else {
        val
        //^?
    }
}

function foo2(val: string | false | null) {
    if (val) {
        val;
        //^?
        bar(val);
        record[val] = 5;
    } else {
        val
        //^?
    }
}

function bar(val: string) {
    return val;
}
🙁 Actual behavior

Error on line 8, cannot use val to index object type with string key.
Val appears to be narrowed to NonNullable<T> yet I can use it as string when calling functions?
Note: foo2 does not use generics and has more expected bahavior.

🙂 Expected behavior

type Falsy = false | null | undefined | 0 | "";
(might remove 0 | "" from this because that gets into negated types... false seems like a common enough one to matter though)

On line 5 inside of the if check, I expect val to be narrowed to Exclude<T, Falsy> but instead, is incorrectly (imo) narrowed to NonNullable<T> which does not exclude false.

What is exceptionally strange, is that the type narrowing behavior seems to differ depending on how val is used. I can pass it to the function that takes a string parameter, but when I try to use it to index an object on line 8, I get an error.

Additional information about the issue

This issue was discussed in detail by others more experienced than me in this typescript discord thread:
https://discord.com/channels/508357248330760243/1275942751724372110

Invite to server if you aren't a member:
https://discord.com/invite/typescript

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.