Skip to content

Subtype narrowing bug with falsey types (''/0/[]) #39105

@chancancode

Description

@chancancode

TypeScript Version: 3.9.2

Search Terms: some combination of subtype, narrowing, type assertion, undefined, null, empty string, empty tuple

Code

declare function isEmptyString(value: string): value is '';
declare function isMaybeEmptyString(value: string | null | undefined): value is '' | null | undefined;

declare function isZero(value: number): value is 0;
declare function isMaybeZero(value: number | null | undefined): value is 0 | null | undefined;

declare function isEmptyArray<T>(value: T[]): value is [];
declare function isMaybeEmptyArray<T>(value: T[] | null | undefined): value is [] | null | undefined;

const TEST_CASES = [
    (value: string) => {
        if (isEmptyString(value)) {
            value // ""
        } else {
            value // string
        }

        if (isMaybeEmptyString(value)) {
            value // ""
        } else {
            value // string
        }
    },

    (value?: string) => {
        if (isMaybeEmptyString(value)) {
            value // EXPECTED '' | undefined ; ACTUAL undefined
        } else {
            value // string
        }
    },

    (value: number) => {
        if (isZero(value)) {
            value // 0
        } else {
            value // number
        }

        if (isMaybeZero(value)) {
            value // 0
        } else {
            value // number
        }
    },

    (value?: number) => {
        if (isMaybeZero(value)) {
            value // EXPECTED 0 | undefined ; ACTUAL undefined
        } else {
            value // number
        }
    },

    (value: string[]) => {
        if (isEmptyArray(value)) {
            value // []
        } else {
            value // string[]
        }

        if (isMaybeEmptyArray(value)) {
            value // EXPECTED [] ; ACTUAL string[] & [] (GOOD ENOUGH but why is it different than the above?)
        } else {
            value // string[]
        }
    },

    (value?: string[]) => {
        if (isMaybeEmptyArray(value)) {
            value // EXPECTED [] | undefined ; ACTUAL undefined
        } else {
            value // string[]
        }
    },
];

Expected behavior: (inlined)

Actual behavior: (inlined)

TL;DR the type system is apparently expressive enough to express this, until null/undefined got involved

Playground Link: https://www.typescriptlang.org/play/index.html?ssl=1&ssc=1&pln=76&pc=3#code/CYUwxgNghgTiAEAzArgOzAFwJYHtXywGcBRAWwAcMBPAZQxi1QHMAKANygmRAC55D6jJgEo+HLgiLwA5NIDcAKFCRYCFOmx4ChALJQqAIxBlKtQc3adufAQ2bwAPvFTIIER-DShEjEMFHw4tzaMtIeLm4eXiA+qH6KSuDQcEhomLj4RABaIDA4lhJ8LqRGMAFBkoTwAAyKyslqaZqZuvpGOXkF1s7IJbnhru5O0bF+5VaVNQORw6jevsAJ9aqpGhnaJtQAgjAw+gA8ACoAfF288IcA2gC64xIhN3VJK+rpWkR6hsYU27sHJ2c+FdrtMhp45jEFndglIbqCohDRosFAowHgBBdiDRDgB9ADCWxoWPgAF54JcFPAqfBAfxzCJScd4ABvSnU9lYRA0oibMx2VgVYTCFls9liwITeAAeil8AARHLRWKAL7wEAQQgIVni8UVaWy2xCJXs5UonUELksD5tb6mOj8s5CkXm9l6mXyxXm1XqzXOl1Ut0G+nG6mmk0AGjN1LOAH4bPThSSmdqdZzua0vrz7UJHcKU-7A-BiAANAAKxDxh2IABFQgj5nFgPA5PAtpWAKpbAAy4IbfhDVO9Gq1A7FhcNzFHYdDkaVtOKpUTydHaathA6+UFedHrsl7uqU7Vw79Bb3soXuSnUfFq+tXw3uZPLsLB69R99+efZ56fRgV4j140hUcY-oujJPhylp3u0uSbhMTqfuahYluWlY1lMsx9k2LZtocnY9iMCyHj6I7+gG34Xn+XpKsqs7srSE5MDcS4QdSt4kD8VA7HsVCPohOqFjcxHHvxurfoxQnUSuUEZkYvLcfofE7tSyFlhWVa1nCOEdt2dL8nCABk5IgiwADiADy5m1sQABy5ntqZAAS8AGMgGDwAA7gAFlQIRYO5wCcoguQgKg7kYF5UD4BFCBQAYOBsCAMbCMJH7KeR9zuhJ1z-jOgGxvG+m3OBokWumnxyZxCm8VurFiZlsooep6FwphkKNs2rY6QRiJEW+JF1WO4n0pJOrToOkbXHIQA

Related Issues:

#18196
#18413
#27103
#27157

Maybe same as #31156, not 100% sure

Metadata

Metadata

Assignees

No one assigned

    Labels

    BugA bug in TypeScript

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions