The error is quite descriptive.
Type '{ kind: "a"; }' is not assignable to type 'T'.
'{ kind: "a"; }' is assignable to the constraint of type 'T',
but 'T' could be instantiated with a different subtype of constraint 'AllKinds'.
This problem occurs even when you use a simpler type constraint.
function foo<T extends string>(): T {
return 'foo';
}
Here we'd get the following error.
Type 'string' is not assignable to type 'T'.
'string' is assignable to the constraint of type 'T',
but 'T' could be instantiated with a different subtype of constraint 'string'.
The problem is that we said that we'd return something of type T, but T is not the same type as string. Yes, the type T extends string but all that means is that T is a subtype of string. For example, the type 'bar' is a subtype of string. Hence, we can instantiate T with 'bar'. Hence, we'd expect the return value to be 'bar' but the return value is 'foo'.
The solution is to simply not use generics. If you want to return a string then just say that you're returning a string. Don't say that you're returning a value of some subtype T of string.
function foo(): string {
return 'foo';
}
Similarly, if you want to return a value of type AllKinds then just say that you're returning a value of type AllKinds. Don't say that you're returning a value of some subtype T of AllKinds.
type KindA = {kind:'a'};
type KindB = {kind:'b'};
type KindC = {kind:'c'};
type AllKinds = KindA | KindB | KindC;
function create(kind:AllKinds['kind']): AllKinds {
switch(kind) {
case "a": return {kind:'a'};
case "b": return {kind:'b'};
case "c": return {kind:'c'};
}
}
create("a");
Edit: You need dependent types to do what you want. TypeScript doesn't have dependent types. However, you can create a custom fold function that provides additional type safety.
type Kind = 'a' | 'b' | 'c';
type KindA = { kind: 'a' };
type KindB = { kind: 'b' };
type KindC = { kind: 'c' };
type AllKinds = KindA | KindB | KindC;
function foldKind<A, B, C>(a: A, b: B, c: C): (kind: Kind) => A | B | C {
return function (kind: Kind): A | B | C {
switch (kind) {
case 'a': return a;
case 'b': return b;
case 'c': return c;
}
}
}
const create: (kind: Kind) => AllKinds = foldKind<KindA, KindB, KindC>(
{ kind: 'a' },
{ kind: 'b' },
{ kind: 'c' }
);
Now, you can only provide a value of KindA for 'a', a value of KindB for 'b', and a value of KindC for 'c'. See the demo for yourself.