Given the below
type Constructor<T> = new (...args: any[]) => T;
type MakeFirstUndefined<T extends unknown[]> = {
[I in keyof T]: I extends "0" ? undefined : T[I]
}
type FilterUndefined<T extends unknown[]> = T extends [] ? [] :
T extends [infer H, ...infer R] ?
H extends undefined ? FilterUndefined<R> : [H, ...FilterUndefined<R>] : T
When I apply it to a tuple, like below, I'm able to filter out the first element
type InputTuple = [number, string, object]
type FirstUndefinedTuple = MakeFirstUndefined<InputTuple> // Type [undefined, string, object]
const outputFirstUndefinedSuccess: FirstUndefinedTuple = [undefined, 'a-string', {}]
const outputFirstUndefinedCorrectlyErrors: FirstUndefinedTuple = [1, 'a-string', {}]
type FirstItemFilteredOut = FilterUndefined<FirstUndefinedTuple> // Type [string, object]
const ouputFilteredSuccess: FilterUndefined<FirstUndefinedTuple> = ['a-string', {}]
const ouputFilteredCorrectlyErrors: FilterUndefined<FirstUndefinedTuple> = ['a-string', {}, 'apple']
When I then apply the same concept to the ConstructorParameters
of a specific Constructor
. It works.
class MagicMonkey {
constructor(public name: string, public tricks: string[]) {
}
}
enum Color {
BLUE,
GREEN,
PURPLE,
RED,
PINK,
}
class FlashyFlamingo {
constructor(public name: string, public colors: Color[], wingSpan: number) {
}
}
function createMagicMonkey(...args: FilterUndefined<MakeFirstUndefined<ConstructorParameters<typeof MagicMonkey>>>) {
return new MagicMonkey('jack', ...args);
}
console.log(createMagicMonkey(['trick 1', 'trick 2']))
console.log(createMagicMonkey(['trick 1', 'trick 2'], 1000)) // Correct Error: Expected 1 arguments, but got 2
// Output:
// MagicMonkey: {
// "name": "jack",
// "tricks": [
// "trick 1",
// "trick 2"
// ]
// }
function createFlashyFlamingo(...args: FilterUndefined<MakeFirstUndefined<ConstructorParameters<typeof FlashyFlamingo>>>) {
return new FlashyFlamingo('', ...args);
}
console.log(createFlashyFlamingo([Color.PINK, Color.PURPLE], 1000))
console.log(createFlashyFlamingo([Color.PINK, Color.PURPLE])) // Correct Error: Expected 2 aguments, but got one
// Output:
// FlashyFlamingo: {
// "name": "",
// "colors": [
// 4,
// 2
// ]
// }
However if I attempt to generify it to any Constructor
. It does not work.
function createGeneric<T extends Constructor<any>>(ctor: T, ...args: FilterUndefined<MakeFirstUndefined<ConstructorParameters<T>>>) {
return new ctor('jack', ...args);
}
console.log(createGeneric(MagicMonkey, ['trick 1', 'trick 2']))
// Expected Ouput(same as createMagicMonkey):
// MagicMonkey: {
// "name": "jack",
// "tricks": [
// "trick 1",
// "trick 2"
// ]
// }
console.log(createGeneric(FlashyFlamingo, [Color.PINK, Color.PURPLE], 1000))
// Expected Ouput(same as createFlashyFlamingo):
// FlashyFlamingo: {
// "name": "",
// "colors": [
// 4,
// 2
// ]
// }
I get the error: A rest "parameter must be of an array type"
As a test I removed the FilterUndefined
part and even on just applying MakeFirstUndefined
it still says "parameter must be of an array type". It's definitely a typle after applying MakeFirstUndefined
and then MakeFirstUndefined
as the example demonstrates.
I don't see how it could be anything else other than a tuple, seeing that ConstructorParamters
returns a tuple. Mapped tuple types are a thing now, right?
EDIT: 2021/06/24 UTC 9:35 Update playground with usage examples and added FilterUndefined
to the createGeneric
function.
EDIT: 2021/06/24 UTC 16:08 Update playground to make it clear that the goal is to remove the first parameter type from any constructor.