简体   繁体   中英

How to assert TypeScript Generics type

I'm trying to synthesize these overloads with mapped type and generics,

function x(_: BooleanConstructor): boolean
function x(_: StringConstructor): string
function x(_: NumberConstructor): number
function x<T>(_: Constructor<T>): T

But I am having lots of difficulties, especially this,

I am wondering why the following code (open in TypeScript playground) doesn't work.

export type Constructor<T> = new (...args: any[]) => T
export type MappedResult<T> =
    T extends Boolean ? boolean :
    T extends Number ? number :
    T extends String ? string :
    T

function make<T, Ctor = Constructor<T>, Result = MappedResult<T>>(ctor: Ctor): Result {
    if (ctor === String) { return '' } // would produce error
    throw new Error()
}

const str = make<String, StringConstructor, string>(String) // string!
const wrongInferenceStr = make(String) // would be {}

My understanding is that T is treated more like the new unknown type in TypeScript 3.0, so I'd have to assert its identity, is there any way around this?

Update

Using jcalz's answer, I've tried to use this , but with no luck.

In short, I think the compiler is buggy. Issue here

To get the inference right when calling make , it's best for its signature to require the simplest inference possible. That means: give the compiler the fewest places for it to make decisions, and make those decisions as straightforward as you can. For example, have only one type parameter which corresponds exactly to the type of the ctor parameter, and then use conditional types to calculate the related type of the output. Like this:

declare function make<C extends Constructor<any>>(ctor: C): 
  MappedResult<C extends Constructor<infer T> ? T : never>;

Now, you get

const str = make(String); // string

As for the error inside the implementation of make , the compiler is generally not clever enough to narrow the type of a generic type parameter like C to StringConstructor , and will complain. The easiest way to deal with that is usually to use a single overload for the caller signature and make the implementation signature more permissive (but less type safe). For example:

function make<C extends Constructor<any>>(ctor: C): MappedResult<C extends Constructor<infer T> ? T : never>;
function make(ctor: Constructor<any>): any {
    if (ctor === String) { return '' } // no error now
    throw new Error()
}

That works, but you have to be careful with the implementation since the return type is any . It's similar to an assertion. Not sure if there's a clever way to guarantee type safety here... but you probably don't need it.


Hope that helps. Good luck!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM