TypeScript 3.8.3 not narrowing union type in switch statement

444 Views Asked by At

Here's a playground link.

interface Animals {
    cat: {}
    dog: {}
}
function brokenSwitch<T extends keyof Animals>(animal: T) {
    switch (animal) {
        case "cat":
            break
        case "dog":
            break
        default:
            // the line below has a TS error.
            // Type "cat" | "dog" is not assignable to type "never".
            const remainingAnimal: never = animal
    }
}

In the above code, I would expect the type of animal in the default case to be never, but it is not.

In my real use case, I need the function to accept a generic similar to this function. How can I get TS to narrow down the type of animal successfully as we go through the switch block?

I notice that from TS 4.3.5 and on, the type is narrowed the way I'd expect (the error disappears), but I can't change the TS version of my project.

1

There are 1 best solutions below

0
On

Before TypeScript 4.3, the way to deal with your problem would be to widen animal from the generic type T to the specific type keyof Animals to which it is constrained. This is most easily done by assigning animal to a new variable which is annotated as the specific type. Such an assignment is seen as safe by the compiler. Afterward, you use the new variable which will then be narrowed as expected:

function repairedSwitch<T extends keyof Animals>(animal: T) {
    const _animal: keyof Animals = animal // widen
    switch (_animal) {
        case "cat":
            // is cat
            break
        case "dog":
            // is dog
            break
        default:
            const remainingAnimal: never = _animal // okay
    }
}

So that works, and the end of the answer to the question as asked.


Obligatory disclaimer:

Note that the "correct" answer is to upgrade to the latest TypeScript release, both to take advantage of the support for contextual narrowing of generic values added in TypeScript 4.3 and to avoid the rapidly increasing amount of technical debt that comes from a depending on a static version of a language under such active development. Bug fixes are rarely backported to previous versions (see microsoft/TypeScript#38237), so it's not really possible to subscribe to a "long-term support" version of TypeScript 3.8, which eventually becomes bug-free and is accompanied by a large amount of accurate documentation and other development resources.

You (the OP of this question) may have valid reasons for sticking with a static version of the language, but you (other people who read this answer in the future) should strongly consider upgrading if at all possible.


Playground link to code