Динамичное переопределение Symbol.toPrimitive

Описание

Написал простой класс, в нем динамичное переопределение Symbol.toPrimitive:

interface PrimitiveHintsMap {
    "number": number;
    "boolean": boolean;
    "string": string;
}

class Timespan {
    #duration = 0;
    [Symbol.toPrimitive]<K extends keyof PrimitiveHintsMap>(hint: K): PrimitiveHintsMap[K] {
        switch (hint) {
            case `number`: return this.#duration;
            case `boolean`: return Boolean(this.#duration);
            case `string`: return this.toString();
            default: throw new TypeError(`Invalid '${hint}' primitive hint`);
        }
    }
}

... так же как это сделано для встроенных слушателей DOM:

Функция жалуется при каждом return, что

Type 'number' is not assignable to type 'PrimitiveHintsMap[K]'.
Type 'number' is not assignable to type 'never'.ts(2322)

Вопрос

Где я допускаю ошибку?
Откуда вообще тут нашлась never?


Ответы (1 шт):

Автор решения: Qwertiy

Где я допускаю ошибку?

Нигде. В таких сложных местах ts не может понять тип, поэтому надо кастить явно.

Впрочем, меня смущают строки в switch в косых кавычках - я бы побоялся, что они мешают вывести нужные ограничения, но в твоём случае прямые кавычки не помогают.

Откуда вообще тут нашлась never?

Это пересечение всех значений PrimitiveHintsMap, т. е. number & boolean & string - это never.


При особом желании касты можно заменить на перегрузку функции, но по сути она просто убивает часть проверок типов, поэтому, вероятно, я бы делал через каст.

playground

interface PrimitiveHintsMap {
    "number": number;
    "boolean": boolean;
    "string": string;
}

class Timespan {
    #duration = 0;

    [Symbol.toPrimitive]<K extends keyof PrimitiveHintsMap>(hint: K): PrimitiveHintsMap[K];
    [Symbol.toPrimitive]<K extends keyof PrimitiveHintsMap>(hint: K): PrimitiveHintsMap[keyof PrimitiveHintsMap] {
        switch (hint) {
            case 'number': return this.#duration;
            case 'boolean': return Boolean(this.#duration);
            case 'string': return this.toString();
            default: throw new TypeError(`Invalid '${hint}' primitive hint`);
        }
    }
}
→ Ссылка