2

I'm trying to use mapped types with typescript, which is supposed to be possible, but running into some issues.

Let's say I have 3 classes:

class A {
    public foo1 = 1;
}
class B {
    public foo2 = 1;
}
class C {
    public foo3 = 1;
}

const base = [A, B, C] as const;

Basically, what I want is to pass base into some function, and get back an array with 3 matching instances.

type CTOR = new(...args: any[]) => any

function instantiate1<T extends ReadonlyArray<CTOR>>(arr: T): {[K in keyof T]: T[K]} {
    const items = arr.map(ctor => new ctor());
    return items as any; //Don't mind about breaking type safety inside the function
}

Now this returns:

const result1 = instantiate2(base) //type is [typeof A, typeof B, typeof C]

which makes sense, as I didn't really "map" the type yet, just made sure the syntax works.

However if I try to actually map the type:

function instantiate2<T extends ReadonlyArray<CTOR>>(arr: T): {[K in keyof T]: InstanceType<T[K]>} {
    const items = arr.map(ctor => new ctor());
    return items as any; //Don't mind about breaking type safety inside the function
}

I get an error that T[K] doesn't satisfy the constraint of instance type.

anyone as an idea for a workaround?

full playground link

1 Answer 1

2

This is a known bug in TypeScript, according to microsoft/Typescript#27995. It's a fairly old issue now and is on the "Backlog", so I wouldn't expect it to be fixed anytime soon.


Workarounds:

Explicitly constrain T[K] to the desired constructor type. I usually do this with the Extract<T, U> utility type like this:

declare function instantiate<T extends ReadonlyArray<Ctor>>(arr: T): {
    [K in keyof T]: InstanceType<Extract<T[K], Ctor>>
};

const result = instantiate(base);
// const result: readonly [A, B, C]

Or, you could replace the InstanceType<T> utility type with your own version that doesn't care if T is a constructor. The existing type definition is:

type InstanceType<T extends new (...args: any) => any> = 
   T extends new (...args: any) => infer R ? R : any;

So you can loosen it to:

type MyInstanceType<T> = // no constraint on T
    T extends new (...args: any) => infer R ? R : any;

and then your original version works as expected:

declare function instantiate<T extends ReadonlyArray<Ctor>>(arr: T): {
    [K in keyof T]: MyInstanceType<T[K]>
};

const result = instantiate(base);
// const result: readonly [A, B, C]

Playground link to code

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.