I have this code:
type Constructable = {new(...args: any[]): any, prototype: any}; // Represents a class (that can be called with new)
type WithOnlyType<O, T> = {[P in keyof O as O[P] extends T? P: never]: O[P]}; // https://stackoverflow.com/questions/51419176/how-to-get-a-subset-of-keyof-t-whose-value-tk-are-callable-functions-in-typ
// Override a constructor with name C in a scope S
function extendConstructor<
S,
T extends WithOnlyType<S, Constructable>,
C extends keyof T>
(
className: C, constructor: (...args: ConstructorParameters<T[C]>) => any, scope: S) {
...}
However, ConstructorParameters<T[C]>
is erroring because Type 'T[C]' does not satisfy the constraint 'abstract new (...args: any) => any'.
Which is odd, because T contains only values that fit that requirement (are Constructable
) and C is keyof T
. However, farther down (at the bottom of) the error, I found:
Type 'S[string]' is not assignable to type 'abstract new (...args: any) => any'
This is true, S could have a lot more values than just classes, however, T is a lot more narrowed than S, and C than string. How to I stop tsc from expanding these types?
This is a design limitation of TypeScript. See microsoft/TypeScript#30728 for the description of a similar issue. The compiler is not clever enough to do the sort of higher-order analysis necessary to figure out that T[C]
will be assignable to Constructable
, when T
and C
are both unspecified generic type parameters. You wrote WithOnlyType
specifically to guarantee this sort of constraint, but WithOnlyType<S, Constructable>[keyof WithOnlyType<S, Constructable>]
is more or less opaque to the compiler.
If you want to work around this, you can use the Extract<T, U>
utility type. When you have a type T
that you know is assignable to type U
but the compiler doesn't know this, you can write Extract<T, U>
instead of T
. The compiler sees that Extract<T, U>
is assignable to U
, and you know that Extract<T, U>
will eventually resolve to just whatever T
is (because Extract<T, U>
evaluates to any union members of T
which are assignable to U
).
So if you change
ConstructorParameters<T[C]>
to
ConstructorParameters<Extract<T[C], Constructable>>
the error will go away.