Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

TypeScript: Type guard does not work with switch statement when putting object into variable

Tags:

typescript

I don't understand why type guard doesn't work in the example below...

When we have these interfaces,

interface ParamA {
    name: 'A';
    aaa: boolean;
}

interface ParamB {
    name: 'B';
    bbb: number;
}

Good

function func(param: ParamA | ParamB) {
    switch (param.name) {
        case 'A':
            const aaa = param.aaa;
            console.log(aaa); // boolean
            break;
        case 'B':
            const bbb = param.bbb;
            console.log(bbb); // number
            break;
        default:
            break;
    }
}

Bad

function func(param: ParamA | ParamB) {
    const name = param.name; // just rewrite here
    switch (name) {
        case 'A':
            const aaa = param.aaa;
            //                ^^^
            console.log(aaa);
            break;
        case 'B':
            const bbb = param.bbb;
            //                ^^^
            console.log(bbb);
            break;
        default:
            break;
    }
}

Compiler throws errors like Property 'aaa' does not exist on type 'ParamB'. I do not think there should be a difference in behaviors whether I can put it in a variable.

The version of TypeScript using is 2.8.3.

Can anyone explain this?

like image 834
tsk Avatar asked May 18 '18 03:05

tsk


People also ask

What is narrowing in TypeScript?

TypeScript follows possible paths of execution that our programs can take to analyze the most specific possible type of a value at a given position. It looks at these special checks (called type guards) and assignments, and the process of refining types to more specific types than declared is called narrowing.

What is type assertion in TypeScript?

In Typescript, Type assertion is a technique that informs the compiler about the type of a variable. Type assertion is similar to typecasting but it doesn't reconstruct code. You can use type assertion to specify a value's type and tell the compiler not to deduce it.


2 Answers

switch statement serves as type guard in 'good' snippet and doesn't do that in 'bad' one.

I do not think there should be a difference in behaviors whether I can put it in a variable.

There's no difference in behaviour, there's a difference in how TypeScript is able to analyze this code. Since the code is statically analyzed, the ways how param type can be narrowed are limited.

In order for param to be narrowed down to ParamA or ParamB it should be mentioned in type guard. Since it wasn't but name variable was, param won't be narrowed down, its type will need to be asserted in this case:

...
switch (name) {
    case 'A':
        const aaa = (<ParamA>param).aaa;
...
like image 67
Estus Flask Avatar answered Oct 08 '22 19:10

Estus Flask


Update 2021-08:

Typescript 4.4 got smarter and your example work out of the box :-)

Old answer

Assigning the property to a variable looses the connection to the object. This would be very hard to follow for the compiler as you could change the string and the object variable independently:

var param: ParamA | ParamB;
param = someParamA;
const name = param.name;
param = someParamB;
switch (name) { // boom

So if you know for sure that you have not changed the variable you can use the type assertion but i would not do that for maintenance reason.

like image 4
HolgerJeromin Avatar answered Oct 08 '22 19:10

HolgerJeromin