bifi
bifi

Reputation: 325

Typescript: A type is not assignable to instance of generic type - both looks the same

export type Animal<T extends Kind> = T extends "cat"
  ? CatData<T>
  : DogData<"dog">

export type Kind = "cat" | "dog"

type CatData<T extends "cat"> = {
  kind: T
  someData: number[]
}
type DogData<T extends "dog"> = {
  kind: T
  someData: string[]
}

const fun = <T extends Kind>(kind: T): Animal<T> => {
  switch (kind) {
    case "cat":
      return {
        kind,
        someData: [33],
      }
    default:
      return {
        kind,
        someData: ["data"],
      }
  }
}

For above code I got two errors regarding both cases in switch statement. Both errors are the same:

 test.ts    24   7 error    2322   Type '{ kind: T; someData: string[]; }' is not assignable to type 'Animal<T>'. (lsp)

I don't understand why those types are not assignable to Animal. I'm probably missing something but they look like they are instances of generic Animal type with T provided by function's argument.

What's wrong with above code?

Upvotes: 0

Views: 1278

Answers (1)

kdau
kdau

Reputation: 2099

In your fun, you want to narrow the type T, and thus Animal<T>, but you're actually just narrowing the type of the argument kind. Checking that kind equals, e.g., "cat" doesn't necessarily imply anything about other variables of type T, including the return value of the function. That is in part a limitation in TypeScript.

The idiom that you want here is a map of Kinds to Animal types using indexed access types. That will clean up the structuring of the types, but it doesn't address the limitation above. A wrapper inside the function, while not very elegant, can take care of that. Here's the whole thing:

interface KindMap {
  "cat": CatData
  "dog": DogData
}
export type Kind = keyof KindMap
export type Animal<T extends Kind> = KindMap[T]

type CatData = {
  kind: "cat"
  someData: number[]
}
type DogData = {
  kind: "dog"
  someData: string[]
}

const fun = <T extends Kind>(kind: T): Animal<T> => {
  return ((): KindMap[Kind] => {
    switch (kind) {
      case "cat":
        return {
          kind,
          someData: [33],
        }
      default:
        return {
          kind,
          someData: ["data"],
        }
    }
  }) () as Animal<T>;
}

console.log(fun("cat")) // {"kind":"cat","someData":[33]}
console.log(fun("dog")) // {"kind":"dog","someData":["data"]}
// console.log(fun("sphinx")) // Argument of type '"sphinx"' is not assignable to parameter of type 'keyof KindMap'.(2345)

(TypeScript Playground link)

Upvotes: 3

Related Questions