Цепочка вызовов из опциональных аргументов
Описание
Пытаюсь создать что-то похожее на function.bind, чтобы можно было использовать вот так:
const builder = new Builder<[number, string, boolean]>();
builder
.partial(42)
.partial("hello", true)
.build();
builder
.partial(42)
.partial()
.build("hello", true);
builder
.partial(42)
.partial("hello")
.build(true);
То есть Builder-у нужны аргументы [number, string, boolean] но их можно передать частично, главное, что в конце - во время build все было передано.
Вот и, собственно, создал:
class Builder<T extends any[]> {
partial<A extends any[], B extends any[]>(this: Builder<[...A, ...B]>, ...arg: A): Builder<B> {
throw new Error("Method not implemented");
}
build(...args: T): void {
throw new Error("Method not implemented");
}
}
Теперь хочу делать так чтобы в partial можно было передавать не массив аргументов, а конкретно 1 опциональный аргумент. То есть:
const builder = new Builder<[number, string, boolean]>();
builder
.partial(42) // Ok
.partial("hello", true) // Error. Partial has no pverload with 2 arguments
.build();
builder
.partial(42) // Ok
.partial() // Ok
.build("hello", true);
builder
.partial(42) // Ok
.partial("hello") // Ok
.build(true);
Опциональность очень конфликтует со spead-оператором (...), и он рассчитывает типы неправильно.
Вопрос
Подскажете как правильно это реализовать?
Дополнительно 1
Один из моих (наверно самый удачный) попыток:
class Builder<T extends any[]> {
partial<A extends [unknown], B extends any[]>(this: Builder<[...A, ...B]>, ...arg: A): Builder<B> {
throw new Error("Method not implemented");
}
build(...args: T): void {
throw new Error("Method not implemented");
}
}
const builder = new Builder<[number, string, boolean]>();
builder
.partial(42) // Норм
.partial("hello", true) // Ошибка, как и ожидалось
.build();
builder
.partial(42) // Норм
.partial() // Ошибка, которой не должно было быть
.build("hello", true);
Вот еще один:
class Builder<T extends any[]> {
partial<A extends any, B extends any[]>(this: Builder<[A, ...B]>, arg?: A): Builder<B> {
throw new Error("Method not implemented");
}
build(...args: T): void {
throw new Error("Method not implemented");
}
}
const builder = new Builder<[number, string, boolean]>();
builder
.partial(42) // Норм
.partial("hello", true) // Ошибка, как и ожидалось
.build();
builder
.partial(42) // Норм
.partial() // Норм
.build("hello", true); // Ошибка. string пропустил, ожидает только boolean
Ответы (1 шт):
Довольно упоротый вариант с поддержкой именованных элементов кортежа, а так же всех видов кортежей (с обязательными/необязательными элементами, а так же наличием/отсутствием реста в кортеже)
код:
type ArgRest<T extends unknown[]> = T extends [...infer X extends unknown[], unknown] ? ArgRest<X> : [T[0]];
type ArgAfterRest<T extends unknown[], R = []> = T extends [...unknown[], ...infer Y extends [unknown]]
? T extends [...infer X extends unknown[], ...Y]
? ArgAfterRest<X, Y>
: R
: R;
type PartArgs<T extends unknown[], V = 0> = T extends []
? []
: '0' extends keyof T
? T extends [...infer X extends [unknown?], ...unknown[]]
? X
: []
: T extends [...unknown[], unknown]
? V extends 0 ? ArgRest<T> : ArgAfterRest<T>
: [T[number]];
type ShiftAfterRest<T extends unknown[], R extends unknown[] = []> = T extends [...unknown[], unknown, unknown]
? T extends [...unknown[], ...infer Y extends [unknown]]
? T extends [...infer X extends unknown[], ...Y]
? ShiftAfterRest<X, [...Y, ...R]>
: R
: R
: R;
type Shift<T extends unknown[], V = 0> = T extends []
? []
: '0' extends keyof T
? T extends [unknown?, ...infer R extends unknown[]]
? R
: []
: T extends [...unknown[], unknown]
? V extends 0 ? T : ShiftAfterRest<T>
: T;
class Builder<T extends unknown[]> {
partial(): Builder<T>;
partial(...args: PartArgs<T>): Builder<Shift<T>>;
partial(...args: PartArgs<T, 1>): Builder<Shift<T, 1>>;
partial(a?: unknown): Builder<T | Shift<T> | Shift<T, 1>> {
throw new Error("Method not implemented");
}
build(...args: T): void {
throw new Error("Method not implemented");
}
}