hillin
hillin

Reputation: 1779

Infer generic parameter from an array of types

interface Fruit<T extends string> { type: T; }
class Apple implements Fruit<"apple"> { type = "apple"; }
class Orange implements Fruit<"orange"> { type = "orange"; }

const fruitTypes = [ Apple, Orange ];

How can I extract a type that is "apple" | "orange" from fruitTypes?

My best attempt was:

type FruitTypes = InstanceType<typeof fruitTypes[number]> extends Fruit<infer U> ? U : never;

Which unfortunately yields never.

Upvotes: 0

Views: 55

Answers (2)

Lesiak
Lesiak

Reputation: 25966

Your attempt yields 'string', not never.

See TS Playground.

I believe the problem lies not in the definition of FruitTypes type, but in definition of your classes.

With your code, TS complains:

(property) Apple.type: string
Property 'type' in type 'Apple' is not assignable to the same property in base type 'Fruit<"apple">'.
  Type 'string' is not assignable to type '"apple"'.

If you change it to:

interface Fruit<T extends string> { type: T; }
class Apple implements Fruit<"apple"> { type: "apple" = "apple"; }
class Orange implements Fruit<"orange"> { type: "orange" = "orange"; }

this error goes away, and FruitTypes is correctly inferred to "apple" | "orange"

TS Playground - corrected code

Upvotes: 2

Bishwajit jha
Bishwajit jha

Reputation: 384

I think this should solve your problem. We make use of Distributive property of conditional types and infer the type from each of Fruit<T>

interface Fruit<T extends string> { type: T; }

let Apple : Fruit<"apple"> = { type: 'apple' }
let Orange: Fruit<"orange"> = { type: 'orange' }



const fruitTypes = [ Apple, Orange ];

type FruitTypes = 
  | typeof fruitTypes extends Array<infer K> 
    ? K extends any 
      ? K extends Fruit<infer T> 
        ? T 
        : never
      : never 
    : never


// type FruitTypes = "apple" | "orange"

Code Playground

Upvotes: 1

Related Questions