-
Notifications
You must be signed in to change notification settings - Fork 13.1k
Description
TypeScript Version: 2.5.3
Code
enum Type {
Foo = "Foo",
Bar = "Bar",
}
interface ID<T extends Type> {
type: T;
value: string;
}
const eq1 = <T extends Type>(a: ID<T>, b: ID<T>): boolean =>
a.value === b.value
const eq2 = <T extends ID<Type>>(a: T, b: T): boolean =>
a.value === b.value
const fooID: ID<Type.Foo> = {
type: Type.Foo,
value: "identifier-1",
}
const barID: ID<Type.Bar> = {
type: Type.Bar,
value: "identifier-2",
}
eq1(fooID, barID) // Should produce an error, but does not
eq2(fooID, barID) // Produces an errorExpected behavior:
Either eq1 and eq2 should each produce a compile-time error, or neither eq1 nor eq2 should produce an error.
That is to say, if a solution exists, type inference should find it.
Actual behavior:
eq1 produces no error.
eq2 produces an error:
Argument of type 'ID<Type.Bar>' is not assignable to parameter of type 'ID<Type.Foo>'.
Type 'Type.Bar' is not assignable to type 'Type.Foo'.
There is a solution for types in both eq1 and eq2, but type inference solved only one of them.
Discussion:
I have two kinds of entities that have unique ID strings. I don't want one kind of ID to be substitutable for the other. To that end, I have a Type enum distinguishing the two kinds, and a generic ID type that expects a one or the other enum case as a type parameter.
I also need to be able to compare two of the same kind of ID. I want a compile-time error if I mistakenly try to compare two of the same kind of ID. To that end, I have two implementations of an equality function.
I first wrote eq1 and thought it was strict, but later discovered it was permissive. Here, T is inferred to be Type.Foo | Type.Bar. That makes some sense since the union type is a subtype of Type.
Then I wrote eq2, which is indeed strict, but I don't think it should behave differently from eq1. Here, T is inferred to be ID<Type.Foo>. But in light of eq1's type inference behavior, I expected T would be inferred to be ID<Type.Foo | Type.Bar>. That would also make sense, since ID<Type.Foo | Type.Bar> is a subtype of ID<Type>.