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
}
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?
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.