TypeScript Version: 3.4.1 ... 3.4.5, 3.5.0-dev.20190517
Search Terms: enum is not assignable to type
Code
class M<T>
{
}
function test<V>(m: M<V>, f: () => V)
{
}
enum Enum
{
A,
B,
}
class ClassWithConvert<T>
{
constructor(init: T)
{
}
convert<U>(converter: { to: (v: T) => U; from: (v: U) => T; })
{
}
}
const m2 = new M<ClassWithConvert<Enum>>();
test(m2, () => new ClassWithConvert(Enum.A));
Expected behavior:
no errors
Actual behavior:
Error:
Type 'ClassWithConvert<Enum.A>' is not assignable to type 'ClassWithConvert<Enum>'.
Type 'Enum' is not assignable to type 'Enum.A'.
Playground Link (Note: Option strictFunctionTypes must be checked!)
Related Issues:
These issues look similar: #31204 #28102
When I tried to compile with typescript@next (3.5.0-dev.20190517) I've got more detailed error description:
error TS2322: Type 'ClassWithConvert<Enum.A>' is not assignable to type 'ClassWithConvert<Enum>'.
Types of property 'convert' are incompatible.
Type '<U>(converter: { to: (v: Enum.A) => U; from: (v: U) => Enum.A; }) => void' is not assignable to type '<U>(converter: { to: (v: Enum) => U; from: (v: U) => Enum; }) => void'.
Types of parameters 'converter' and 'converter' are incompatible.
Type '{ to: (v: Enum) => any; from: (v: any) => Enum; }' is not assignable to type '{ to: (v: Enum.A) => any; from: (v: any) => Enum.A; }'.
Types of property 'from' are incompatible.
Type '(v: any) => Enum' is not assignable to type '(v: any) => Enum.A'.
Type 'Enum' is not assignable to type 'Enum.A'.
The error and its explanation are correct
@RyanCavanaugh are you sure this is working as intended? This behavior is a breaking change from 3.3 link.
In 3.4 T in ClassWithConvert gets inferred to the enum literal Enum.A while in 3.3 it is inferred to the enum Enum. I find this odd since there is no constraint on T and I thought that was the hint the compiler would look for in inferring literal types.
Here's the "obviously wrong" version that regressed at 3.4:
// @strictFunctionTypes: true
enum Enum { A, B }
class ClassWithConvert<T> {
constructor(val: T) { }
convert(converter: { to: (v: T) => T; }) { }
}
function fn<T>(arg: ClassWithConvert<T>, f: () => ClassWithConvert<T>) { }
fn(new ClassWithConvert(Enum.A), () => new ClassWithConvert(Enum.A));
Even simpler example:
// @strictFunctionTypes: true
enum Enum { A, B }
class Contra<T> {
f: (x: T) => void;
constructor(x: T) {}
}
let zz: Contra<Enum> = new Contra(Enum.A);
The constructor argument is contextually typed by type Enum and we therefore preserve the enum literal type Enum.A. But, since T is contravariant in Contra<T>, Contra<Enum.A> is not assignable to Contra<Enum>.
It worked in 3.3 because we didn't contextually type through generic return types and therefore promoted the Enum.A argument to type Enum.
Could you please tell me why Enum is regarded as equal to Enum.A, when Enum contains the only member A?
In such case there is no error in all these samples.
Enums defined with that syntax are basically aliases for the union of their members, i.e. Enum is short for Enum.A | Enum.B. So the single-member enum Enum { A } is equivalent to Enum.A
Thank you. Now I see.
So, there are actually two issues here. The first is the issue in the original post, which simplifies to:
// @strictFunctionTypes: true
enum Enum { A, B }
type Foo<T> = (x: T) => T;
declare function makeFoo<T>(x: T): Foo<T>;
declare function bar<U>(x: Foo<U>, y: Foo<U>): void;
bar(makeFoo(Enum.A), makeFoo(Enum.A)); // Error, but shouldn't be
The problem here is that we're including inferences made from previous arguments in the type from which we make return type inferences. So, in the first call to makeFoo above we make no inferences from the return type, but in the second call we infer from a contextual type Foo<Enum> which means we keep the literal type Enum.A. The fix here is to only include outer return type inferences when making inner return type inferences.
The second issue is that we need to make higher priority inferences from the return type in most circumstances. For example:
let z: Foo<Enum> = makeFoo(Enum.A); // Error, but shouldn't be
Here we infer Enum for T from the return type. That causes us to preserve the literal type Enum.A, but we then make a higher priority inference of Enum.A for T. And because Foo<T> is invariant in T this causes an error in the assignment.
In cases where we have an exact contextual type (i.e. the contextual type isn't itself a type for which we are making inferences), our return type inferences really should have the same priority as inferences made from arguments.
Most helpful comment
@RyanCavanaugh are you sure this is working as intended? This behavior is a breaking change from 3.3 link.
In 3.4
TinClassWithConvertgets inferred to the enum literalEnum.Awhile in 3.3 it is inferred to the enumEnum. I find this odd since there is no constraint onTand I thought that was the hint the compiler would look for in inferring literal types.