Цепочка вызовов из опциональных аргументов

Рейтинг: 0Ответов: 1Опубликовано: 26.07.2025

Описание

Пытаюсь создать что-то похожее на 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Принят

Довольно упоротый вариант с поддержкой именованных элементов кортежа, а так же всех видов кортежей (с обязательными/необязательными элементами, а так же наличием/отсутствием реста в кортеже)

https://clck.ru/3NKokt

код:

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");
    }
}