Here’s a reduction of the problem:
type Animal = 'dog' | 'cat';
type AnimalSound<T extends Animal> = T extends 'dog'
? 'woof'
: T extends 'cat'
? 'meow'
: never;
const animalSoundMap: {[K in Animal]: AnimalSound<K>} = {
dog: 'woof',
cat: 'meow',
};
const lookupSound = <T extends Animal>(animal: T): AnimalSound<T> => {
const sound = animalSoundMap[animal];
return sound;
}
The return
line is an error; the error message suggests that the sound
variable is resolved to 'woof' | 'meow'
, even though it seems like TS should be able to type it as AnimalSound<T>
based on the type of animalSoundMap
. So why doesn’t the typechecker like it?
CodePudding user response:
In order to make TypeScript happy, I believe you should stick with @T.J. Crowder's solution or with this one:
type Animal = 'dog' | 'cat';
type AnimalSound<T extends Animal> = T extends 'dog'
? 'woof'
: T extends 'cat'
? 'meow'
: never;
const animalSoundMap: { [K in Animal]: AnimalSound<K> } = {
dog: 'woof',
cat: 'meow',
};
const lookupSound = <
AnimalName extends Animal,
AnimalMap extends { [Name in AnimalName]: AnimalSound<Name> }
>(animalMap: AnimalMap, animal: AnimalName):
AnimalMap[AnimalName] =>
animalMap[animal]
If you want to infer return type, you should also infer and make a part of function arguments animalMap
.
Playground
You don't even need to define explicit return type, TS is able to infer it from function body:
const lookupSound = <T extends Animal>(animal: T)=> {
const sound = animalSoundMap[animal];
return sound;
}
const result = lookupSound('cat') // "meow"
Conditional types does not work in a way you expect in a place of return type. It may work if you use conditional type in a function overloading:
function lookupSound<T extends Animal>(animal: T): AnimalSound<T>
function lookupSound<T extends Animal>(animal: T) {
const sound = animalSoundMap[animal];
return sound;
}
CodePudding user response:
I think the problem here is caused by AnimalSound<T>
being a conditional type, and Typescript resolves conditional types later than other types; specifically, when T extends ...
uses a type parameter, it is not resolved until T
is bound to a concrete type. So inside the function, where T
is just a formal type parameter, it can't reason about AnimalSound<T>
in the way you want.
To avoid this, I recommend making animalSoundMap
's type the one you use:
type AnimalSoundMap = {[K in Animal]: AnimalSound<K>}
const animalSoundMap: AnimalSoundMap = {
dog: 'woof',
cat: 'meow',
};
const lookupSound = <T extends Animal>(animal: T): AnimalSoundMap[T] => {
return animalSoundMap[animal];
}