I'm trying to have a method return a specific type depending on a string given as an argument. This is what I have:
interface Base {
type: 'a' | 'b';
}
interface MyA extends Base {
type: 'a';
nameA: string;
}
interface MyB extends Base {
type: 'b';
nameB: string;
}
interface Mappings {
a: MyA;
b: MyB;
}
const testMethod = <K extends keyof Mappings>(
type: K
): Mappings[K][] => {
const values: Mappings[K][] = [];
if(type === 'a') {
values.push({
type: 'a',
nameA: 'My name a'
});
}
else if(type === 'b') {
values.push({
type: 'b',
nameA: 'My name b'
});
}
return values;
}
So if I run testMethod('a')
it would have a return type of MyA[]
and when I use testMethod('b')
it would return MyB[]
. But no matter what I do I can't get it to work. The errors are always:
Argument of type 'MyA' is not assignable to parameter of type 'Mappings[K]'. Type 'MyA' is not assignable to type 'never'. The intersection 'MyA & MyB' was reduced to 'never' because property 'type' has conflicting types in some constituents.
I thought the array could be the problem but returning a variable of type Mappings[K]
has the same issue. Here is the playground link. I've done a similar thing in the past like this one to modify a function's argument according to the string given as argument, but here I'm completely lost.
Any guidance or typescript resources I can read would be appreciated!
Curiously, this worked for me as soon as I specified that Mappings extended Record<string, MyA | MyB>
. I removed the common ancestor Base as it didn't seem to help.
interface MyA {
type: 'a';
nameA: string;
}
interface MyB {
type: 'b';
nameB: string;
}
interface Mappings extends Record<string, MyA | MyB> {
a: MyA;
b: MyB;
}
// same as above
const arrayOfMyA = testMethod('a'); // typed as MyA[]
const arrayOfMyB = testMethod('b'); // typed as MyB[]
I don't have an intuitive explanation, other than that an explicit union is the only way to convince TypeScript that 'a' | 'b'
(keyof Mappings
) exhaustively results in MyA | MyB
...despite the fact that Mappings[keyof Mappings]
correctly results in MyA | MyB
both before and after the change! This might be a TypeScript bug, or at least an opportunity for an improvement.
type MappingResults = Mappings[keyof Mappings];
// typed as MyA | MyB, regardless of whether
// Mappings extends Record<string, MyA | MyB>!