Typescript: Type 'Enum' is not assignable to type 'Enum.A'

Created on 24 Oct 2018  ·  7Comments  ·  Source: microsoft/TypeScript

TypeScript Version: <=3.1.3, 3.2.0-dev.20181019

Search Terms: discriminator, enum intersection, enum value not assignable to enum of same type

Code

enum Enum {
  A = 'A',
  B = 'B',
}

class Entity<T extends Enum> {
  public entityType: T;
}

class A extends Entity<Enum.A> {
  public entityType = Enum.A; // error
}

Expected behavior:

I would expect this to compile as Enum.A is assignable to the entityType property (which is of the same type).

Actual behavior:

Instead of inferring the assignment of Enum.A as the specific type of Enum.A, it appears that no type inference is made at all. Thus, compilation fails as Enum is not assignable to Enum.A.

[ts]
Property 'entityType' in type 'A' is not assignable to the same property in base type 'EntityBase<IEntityA>'.
  Type 'Enum' is not assignable to type 'Enum.A'.
(property) A.entityType: Enum

Workaround is to add an explicit type annotation on the inherited class property, but this seems unnecessary as the compiler is already aware of this type information:

class A extends Entity<Enum.A> {
  public entityType: Enum.A = Enum.A; // works
}
Bug Contextual Types Duplicate

Most helpful comment

Am I crazy, or is this basically unsolved with no progress? Bummer, being able to do this cleanly would be really helpful.

All 7 comments

Yeah, this is caused by the whole "a class isn't contextually typed by it's base class" issue that we've talked about a number of times. @sandersn do you know which issues specifically that was?

The most recent attempt is #10570 I think; links to the rest of the discussion are there.

A somewhat amusing, and a little frustrating secondary bug coming out of this is as follows:

consider this code:

 enum letter {
    a = 1, b,c,d
  }

  type message = [letter.a, string] | [letter.b, string] | [letter.c, string] | [letter.d, string]

  const f = <T extends letter>(letter: T, etc: string): message => [letter, etc]

  type f = <T extends letter>(letter: T) => [T, string] // error here

This gives us the error:

Type 'T' is not assignable to type 'letter.a | letter.b | letter.c'.
  Type 'letter' is not assignable to type 'letter.a | letter.b | letter.c'.
    Type 'T' is not assignable to type 'letter.c'.
      Type 'letter' is not assignable to type 'letter.c'.

Which is an instance of this issue. This is despite the fact that validMessage contains every single letter, so every valid [letter, string] must be assignable to validmessage.

Then, we can try to come up with a union type that specifies every valid letter in validMessage:

  type message = [letter.a, string] | [letter.b, string] | [letter.c, string] | [letter.d, string]
  type messageLetter = letter.a | letter.b | letter.c | letter.d

  const f = <T extends messageLetter>(letter: T, etc: string): message => [letter, etc]

  type f = <T extends messageLetter>(letter: T) => [T, string] // error!

With a little mapping, we can even automate this:

  enum letter {
    a = 1, b,c,d
  }

  type messages = {
    a: [letter.a, string],
    b: [letter.b, string],
    c: [letter.c, string],
    d: [letter.d, string]
  }

  // [letter.a, string] | [letter.b, string], ...
  type message = messages[keyof messages]
  type messageLetter = messages[keyof messages][0]

  const f = <T extends messageLetter>(letter: T, etc: string): message => [letter, etc] // error!

This also errors! Why? Typescript sees that messageLetter contains every letter, and simplifies it to letter, which is not assignable to letter.a!

We can add a constant to the enum that we never use, so that messageLetter now differs from letter:

  enum letter {
    a = 1, b,c,d,NULL
  }

  type messages = {
    a: [letter.a, string],
    b: [letter.b, string],
    c: [letter.c, string],
    d: [letter.d, string]
  }

  // [letter.a, string] | [letter.b, string], ...
  type message = messages[keyof messages]
  type messageLetter = messages[keyof messages][0]

  const f = <T extends messageLetter>(letter: T, etc: string): message => [letter, etc] // error!

  type f = <T extends letter>(letter: T) => [T, string]

But there's still an error! What's going on?

Type '[T, string]' is not assignable to type '[letter.a, string] | [letter.b, string] | [letter.c, string] | [letter.d, string]'.
  Type '[T, string]' is not assignable to type '[letter.a, string]'.
    Type 'T' is not assignable to type 'letter.a'.
      Type 'letter.a | letter.b | letter.c | letter.d' is not assignable to type 'letter.a'.
        Type 'letter.b' is not assignable to type 'letter.a'.

it looks like the pattern matcher is confused too ;)

Am I crazy, or is this basically unsolved with no progress? Bummer, being able to do this cleanly would be really helpful.

This issue has been marked as a 'Duplicate' and has seen no recent activity. It has been automatically closed for house-keeping purposes.

This issue has been marked as a 'Duplicate' and has seen no recent activity. It has been automatically closed for house-keeping purposes.

What is this a duplicate of?

Was this page helpful?
0 / 5 - 0 ratings

Related issues

fwanicka picture fwanicka  ·  3Comments

DanielRosenwasser picture DanielRosenwasser  ·  3Comments

weswigham picture weswigham  ·  3Comments

bgrieder picture bgrieder  ·  3Comments

jbondc picture jbondc  ·  3Comments