In a master router function, I am trying to detect the type of one parameter by a separate string parameter and then call the most appropriate secondary function for handling the first parameter. Example (playground link here):
interface Animal {[index: string] : number | string;}
interface Food<A extends Animal = Animal> {isFor: A;}
interface Dog extends Animal {barkVolume: number;}
interface Cat extends Animal {miceCaught: number;}
interface Gerbil extends Animal {cages: number;}
interface Snake extends Animal {length: number;}
interface Fish extends Animal {idealWaterTemp: number;}
interface Bird extends Animal {song: string;}
//Seeks to follow handling of e.g. GlobalEventHandlersEventMap in lib.dom.d.ts
interface BreedTypeMap {
"copperhead": Snake;
"garter": Snake;
"python": Snake;
"burmese": Cat;
"manx": Cat;
"persian": Cat;
"siamese": Cat;
"pug": Dog;
"poodle": Dog;
"canary": Bird;
"betta": Fish;
"guppy": Fish;
"mongolian": Gerbil;
}
//The steps involved in feeding each type of animal are quite different:
const feedSnake = function(food: Food<Snake>, breed: keyof BreedTypeMap) {/*...*/}
const feedDog = function(food: Food<Dog>, breed: keyof BreedTypeMap) {/*...*/}
const feedCat = function(food: Food<Cat>, breed: keyof BreedTypeMap) {/*...*/}
const feedGerbil = function(food: Food<Gerbil>, breed: keyof BreedTypeMap) {/*...*/}
const feedBird = function(food: Food<Bird>, breed: keyof BreedTypeMap) {/*...*/}
const feedFish = function(food: Food<Fish>, breed: keyof BreedTypeMap) {/*...*/}
//Naming a second generic type does not help, as in:
//const feedAnimal = function<B extends keyof BreedTypeMap, T extends BreedTypeMap[B]>(
// food: Food<T>,
const feedAnimal = function<B extends keyof BreedTypeMap>(
food: Food<BreedTypeMap[B]>,
breed: B,
category: BreedTypeMap[B], //absent in real use; just included for generics demonstration
) {
if(breed === 'copperhead' || breed === 'garter' || breed === 'python') {
//Here, breed is correctly narrowed to "copperhead" | "garter" | "python"
//Why can't TypeScript figure out food is of type Food<Snake>?
//Instead it gives error ts(2345):
//Argument of type 'Food<BreedTypeMap[B]>' is not assignable to parameter of type 'Food<Snake>'.
//Type 'BreedTypeMap[B]' is not assignable to type 'Snake'.
//Type 'Dog | Cat | Gerbil | Snake | Fish | Bird' is not assignable to type 'Snake'.
//Property 'length' is missing in type 'Dog' but required in type 'Snake'.
feedSnake(food, breed);
console.log(category); //type Snake | Dog | Cat | Gerbil | Bird | Fish; should be just Snake
} else if(breed === ('burmese') || breed === ('manx') || breed === ('persian') || breed === ('siamese')) {
feedCat(food, breed);
} else if(breed === ('pug') || breed === ('poodle')) {
feedDog(food, breed);
} else if(breed === ('canary')) {
feedBird(food, breed);
} else if(breed === ('betta') || breed === ('guppy')) {
feedFish(food, breed);
} else if(breed === ('mongolian')) {
feedGerbil(food);
}
}
How do I write the function signature to properly narrow the type of food
, ideally without casting or the use of any
?
I'd also ideally like to avoid having to manually write overload signatures, especially as that would mean separately writing type signatures for the rest of a pretty large class where this router function is found.