admin管理员组

文章数量:1312811

Consider we have a union type that represents one of three different string values.

type Animal = 'bird' | 'cat' | 'dog';

Now I would like to create a dog and check what kind of animal it is to create the correct noise it performs.

let oscar: Animal = 'dog';

switch (oscar) {
  case 'bird':
    console.log('tweet');
    break;
  case 'cat':
    console.log('meow');
    break;
  case 'dog':
    console.log('bark');
    break;
}

This code will result in a TypeScript error: Type '"bird"' is not parable to type '"dog"'.ts(2678) (analog with cat). However, if I use an explicit type cast on the variable oscar, it works without problems:

switch (oscar as Animal) {
  case 'bird':
    ...
  case 'cat':
    ...
  case 'dog':
    ...
}

Can you please explain to me why the first two switch statements fail if I use an explicit value for oscar?

I could understand the error if I declared Oscar as a constant: const oscar = 'dog';, because in that case it would always be a dog and nothing else. However, just imagine for a moment that Oscar could bee a cat if a wizard would perform a certain spell:

let oscar: Animal = 'dog';

while(true) {
  switch (oscar) {
  case 'bird':
    ...
  case 'cat':
    ...
  case 'dog':
    console.log('bark');

    // here es the wizard
    if(wizard.performsSpell('makeOscarBeeACat')) {
      oscar = 'cat';  // that should be valid, because oscar is of type Animal
    }

    break;
  }
}

Do I misunderstand something about the assignment of the variable oscar, or is this simply a TypeScript bug?

Consider we have a union type that represents one of three different string values.

type Animal = 'bird' | 'cat' | 'dog';

Now I would like to create a dog and check what kind of animal it is to create the correct noise it performs.

let oscar: Animal = 'dog';

switch (oscar) {
  case 'bird':
    console.log('tweet');
    break;
  case 'cat':
    console.log('meow');
    break;
  case 'dog':
    console.log('bark');
    break;
}

This code will result in a TypeScript error: Type '"bird"' is not parable to type '"dog"'.ts(2678) (analog with cat). However, if I use an explicit type cast on the variable oscar, it works without problems:

switch (oscar as Animal) {
  case 'bird':
    ...
  case 'cat':
    ...
  case 'dog':
    ...
}

Can you please explain to me why the first two switch statements fail if I use an explicit value for oscar?

I could understand the error if I declared Oscar as a constant: const oscar = 'dog';, because in that case it would always be a dog and nothing else. However, just imagine for a moment that Oscar could bee a cat if a wizard would perform a certain spell:

let oscar: Animal = 'dog';

while(true) {
  switch (oscar) {
  case 'bird':
    ...
  case 'cat':
    ...
  case 'dog':
    console.log('bark');

    // here es the wizard
    if(wizard.performsSpell('makeOscarBeeACat')) {
      oscar = 'cat';  // that should be valid, because oscar is of type Animal
    }

    break;
  }
}

Do I misunderstand something about the assignment of the variable oscar, or is this simply a TypeScript bug?

Share Improve this question edited Jan 30, 2020 at 14:54 Lundin 215k46 gold badges277 silver badges432 bronze badges asked Jan 30, 2020 at 14:52 SparkFountainSparkFountain 2,2701 gold badge18 silver badges36 bronze badges
Add a ment  | 

1 Answer 1

Reset to default 8

What you might be misunderstanding is that TypeScript 2.0 and above has a feature called control-flow based type analysis, implemented in microsoft/TypeScript#8010. One of the effects of this feature is that

An assignment (including an initializer in a declaration) of a value of type S to a variable of type T changes the type of that variable to T narrowed by S in the code path that follows the assignment. [...] The type T narrowed by S is puted as follows: [...] If T is a union type, the result is the union of each constituent type in T to which S is assignable.

That means the statement

let oscar: Animal = 'dog';

is interpreted as: "the variable oscar has the type Animal, a union type. It has been assigned a value of the string literal type "dog", so until it gets reassigned, we will treat the variable oscar as the type Animal narrowed by "dog", which is just "dog".

And therefore in your switch/case statement:

case 'bird': // error!
//   ~~~~~~ <-- Type '"bird"' is not parable to type '"dog"'

You get the error about trying to pare a string literal "bird" to a string literal "dog". The piler knows that the 'bird' case is impossible because you have not reassigned oscar to something patible with 'bird'.

Even in your wizard case, the piler understands that when it reaches the switch/case statement, oscar can only be "cat" or "dog" and not "bird":

case 'bird': // error! 
//   ~~~~~~ <-- Type '"bird"' is not parable to type '"cat" | "dog"'

This is all probably good news; the piler is catching cases that can never happen. For many situations these are genuine bugs.

If you want the piler not to realize that oscar is definitely "dog" and only know that it's an Animal (as, say, a placeholder until you write code that makes it genuinely possible for it to be any member of Animal), you can use a type assertion in the assignment itself:

let oscar: Animal = 'dog' as Animal;

Now all your other code will pile without error. You can even forget the annotation since it wasn't helping you:

let oscar = 'dog' as Animal;

Okay, hope that helps; good luck!

Playground link to code

本文标签: javascriptVariable of union type causes error in switch statementStack Overflow