Adam Baranyai
Adam Baranyai

Reputation: 3867

Infering generic types in typescript

I am trying to wrap my head around infering generics in Typescript, but I can't seem to get a hold on what am I doing wrong. My concrete example would be rather large to leave here, but I've made a small typescript playground link, which illustrates the problem I am facing completely.

Here's the link

Basically, I am trying to infer a generic parameter from a class hierarchy:

type payloadType = {
    [key:string]: string
}

type specificPayloadB = {
    a: string
}

type specificPayloadC = {
    a: string,
    b: string
}

class A<TPayload extends payloadType = any> {}
class B<TPayload extends specificPayloadB = specificPayloadB> extends A<TPayload> {}
class C extends B<specificPayloadC> {}

type InferKeys<TType> = TType extends A<infer Keys> ? Keys : never;
class extender<
   TType extends A,
   TKeys = InferKeys<TType>
> {}

function getClass<TClass extends A>(): extender<TClass> {
    return new extender<TClass>();
}

let testExtenderB = getClass<B>();
let testExtenderC = getClass<C>();

The above example, gives no compilation error, but when inspecting the returned variable types with typescript, testExtenderB is identified as a extender<B<specificPayloadB>, payloadType> which is correct, but based on the information provided, it could be more correctly identified as a extender<B<specificPayloadB>, specificPayloadB>.

The same is true in my opinion for the testExtenderC variable, which is identified as extender<C, payloadType> instead of it being identified as extender<C, specificPayloadC>.

Am I doing something wrong here? Is it possible to achieve the correct identification of the variables, without explicitly providing the types to typescript?

Upvotes: 1

Views: 62

Answers (1)

Titian Cernicova-Dragomir
Titian Cernicova-Dragomir

Reputation: 249466

Typescript has a structural type system. For object types this means the members of the type are important when determining type compatibility (see FAQ). This means that unused type parameters are a not very significant in the type system. Since you don't use TPayload in A the type parameter doesn't have any significance in the type system, so during inference it will end up with it's default value in some cases.

If you add a member, inference works as you expect it to:

class A<TPayload extends payloadType = any> { p!: TPayload }

Playground Link

Upvotes: 2

Related Questions