TransWikia.com

Typescript abstract property not getting function arg types

Stack Overflow Asked by mpcaddy on November 18, 2020

I am trying to create a generic abstract class to be able to pass a type in to force developers to implement the types that are passed in. But for some reason the params of the functions are being typed as any unless I further type the property in the class extending the abstract.

Below are stripped down classes of what I am trying to achieve.

In Generic1 the params are both types as any, but the return type is correct.
In Generic2 the params are the correct type as well as the return type, but have needed to add the typing of the control property again to get it to work.

Am I missing something? I’d prefer to use Generic1 but not sure if it is possible.

declare type Signiture<L> = {
    [E in keyof L]: [any, any?];
};

declare type ControlSignature<L extends Signiture<L>> = {
    [E in keyof L]: (params: L[E][0]) => Promise<L[E][1]>;
};

abstract class Abstract<
    C extends Signiture<C> = {[messageName: string]: [any, any?] | [void, any?]}
> {
    protected abstract control: ControlSignature<C>;
}

interface X { 
    test1: [{ hello: string }, { world: string }],
    test2: [{ nothing: number }, { something: number }],
}

class Generic1 extends Abstract<X> {
    control = {
        test1: async (params) => {
            return { world: "test" }
        },
        test2: async (params) => {
            return { something: 1 }
        }
    }
}

class Generic2 extends Abstract<X> {
    control: ControlSignature<X> = {
        test1: async (params) => {
            return { world: "test" }
        },
        test2: async (params) => {
            return { something: 1 }
        }
    }
}

One Answer

This is unfortunately a design limitation of TypeScript, at least as of TS 4.0.

Currently, initialized properties of a subclass that are not explicitly annotated are inferred to have the type of the initializing value, without paying attention to the superclass.

There's an open issue (quite old at this point), microsoft/TypeScript#10570, tracking the suggestion to inherit typing for initialized properties from their superclasses. You might want to go to the issue above and give it a ? to record your desire for this to happen, but I doubt it will have much of an effect.

It seems that people are generally unhappy with the current behavior. But previous attempts to address this, microsoft/TypeScript#6118 and microsoft/TypeScript#10610 failed because they either gave weird results in common use cases (e.g., using contextual typing), or because they broke real-world code that relies on the current behavior.

Since it would be a breaking change to require users who want the current narrowing behavior to explicitly annotate their subproperties, it's not clear how to move forward. Breaking changes are generally avoided unless they have absolutely clear benefits that outweigh the effort of fixing real code bases that depend on TypeScript. There are a number of TypeScript features which were arguably implemented "the wrong way", but they are not egregious enough to warrant destabilizing current users; if you have a spare time machine and don't want to try to prevent various historical disasters you could fix these instead.

In the current timeline, it might be hard to make case that it's worth breaking existing code that relies on properties getting the type of the initializer, when the workaround of annotating the property yourself is easy enough to do.

So for now I'd say just annotate the properties yourself and move on.

Correct answer by jcalz on November 18, 2020

Add your own answers!

Ask a Question

Get help from others!

© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP