'Infer subclass property type from base class generic in typescript

class A<T>
{
    some: { [K in keyof T]: (x: T[K]) => T[K] }
}

interface IB {
    b: number
}

class B<T extends IB> extends A<T>
{
    constructor()
    {
        super()

        /**
         * Type '{ b: (x: T["b"]) => number; }'
         * is not assignable to type '{ [K in keyof T]: (x: T[K]) => T[K]; }'.
         */
        this.some = {
            b: x => 2*x
        }
    }
}

interface IC {
    b: number
    c: boolean
}

class C<T extends IC> extends B<T>
{
    constructor()
    {
        super()
        /**
         * Type '{ b: (x: T["b"]) => number; c: (x: T["c"]) => boolean; }'
         * is not assignable to type '{ [K in keyof T]: (x: T[K]) => T[K]; }'
         */
        this.some = {
            b: x => 4*x,
            c: x => !x
        }
    }
}

Hello. I try to set generic constraint in base class "A" with the aim to automatically infer types of "some" properties in derived classes. Unfortunately I can't understand why I get TS errors like I have mentioned above. Everything seems ok from my point of view.

Thank you!



Solution 1:[1]

What should happen if I do this?

const b = new B<{ b: 3, z: string }>();

As you can see, I've passed in the type { b: 3, z: string }, which is acceptable because it extends { b: number }. So that means b.some.b should be of type (x: 3) => 3. And it also means b.some.z should be of type (x: string) => string. Are either of those true of the implementation of B? No; b.some.b is actually of type (x: 3) => number, and b.some.z is undefined. So it makes sense that the compiler is warning you.

First, let's take care of the z: string issue. Perhaps in A you want the properties of some to be optional, like this:

class A<T>
{
  some: {[K in keyof T]?: (x: T[K]) => T[K]}
}

This would allow your B and C constructor to initialize some without having to know about extra properties.

Now, about b: 3. If you want to allow for someone to extend number, then the only safe thing you can use is the identity function:

this.some = {};
this.some.b = x => x; // okay

But probably you don't want anyone to pass in anything more specific than number for the type of b. Unfortunately there's no great way to prevent it. So, fine, just document that users should only pass in types where b can be any number. In this case you need to just tell the compiler not to worry, by asserting that this is of type B<IB>:

this.some = {};
(this as B<IB>).some.b = x => 2 * x; // okay

Similar fixes can be done for your C class. Hope that helps; good luck!

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 jcalz