ReturnType on a callback in a nested property doesn't work

Viewed 47

The code below explains issue better than words. It seems to me that it should work, but for some reason it doesn't. What's going on here?

interface Types {
    'type1': boolean,
    'type2': string,
}

type TypesCallbacks = {
    [key in keyof Types]: (object: any) => Types[key];
};

class TypeProvider {

    private callbackMap: TypesCallbacks = {
        type1: (object) => false,
        type2: (object) => 'test',
    };

    public runTypeCallback<T extends keyof TypesCallbacks>(id: T): ReturnType<TypesCallbacks[T]> {
        const callback = this.callbackMap[id];

        return callback('zz');
             //^^^^^^^^
             //There is an error here
    }

    public getCallback<T extends keyof TypesCallbacks>(id: T): TypesCallbacks[T] {
        return this.callbackMap[id];
               //^^^^^^^^^^^^^^^^^^^
               //But this works! Which is proven below.
    }

}
const provider: TypeProvider = new TypeProvider();

const booleanValue: boolean = provider.runTypeCallback('type1');
const stringValue: string = provider.runTypeCallback('type2');


const booleanCallback: (object: any) => boolean = provider.getCallback('type1');
const stringCallback: (object: any) => string = provider.getCallback('type2');

Playground Link

1 Answers

The problem is, that inside the runTypeCallback body, callback's currently type isn't restricted to one T inference or the other - instead it is both.

If you hover on callback in your editor, you can see it infers this type:

(object: any) => string | boolean

Which fails type1 because it contains string, and fails type2 because of boolean.

You will have to assert one or the other, or the final type, which would be a supertype of the actual type, so it should work.

Just add:

as ReturnType<TypesCallbacks[T]> at the end of return, like so:

    public runTypeCallback<T extends keyof TypesCallbacks>(id: T): ReturnType<TypesCallbacks[T]> {
        const callback = this.callbackMap[id];

        return callback('zz') as ReturnType<TypesCallbacks[T]>;
    }

}

You can see it working in the playground.

Related