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.