Найдите и исправьте тип, падающий с «instantiation is excessively deep»
Join склеивает кортеж строк через разделитель. На коротких кортежах он работает, а на длинном падает с «Type instantiation is excessively deep and possibly infinite».
Объясните, почему рекурсия здесь дорогая, и перестройте её так, чтобы кортеж в несколько сотен элементов всё ещё разрешался. Сохраните ту же публичную сигнатуру и тот же результат.
type Join<T extends string[], Sep extends string> =
T extends [infer H extends string, ...infer R extends string[]]
? R extends []
? H
: `${H}${Sep}${Join<R, Sep>}`
: '';
type Short = Join<['a', 'b', 'c'], '-'>; // 'a-b-c'
type Long = Join<HundredsOfStrings, '-'>; // ошибка: excessively deep
Найдите и исправьте ошибку.
Рекурсивный вызов стоит внутри template literal, то есть не в хвостовой позиции: каждый уровень остаётся в стеке инстанцирования, ожидая свой результат, и бюджет глубины кончается. Протащите аккумулятор, чтобы ветка возвращала рекурсивный вызов напрямую, склеивая строку до рекурсии — такую хвостовую форму компилятор оптимизирует и раскручивает намного глубже.
- ✗Винить спред кортежа или ограничения
inferвместо позиции самого вызова - ✗Не распознавать, что рекурсивный вызов внутри template literal — не хвостовой
- ✗Ограничивать длину входа вместо перевода рекурсии в хвостовую позицию
- →Почему параметр-аккумулятор превращает это в хвостовой рекурсивный тип?
- →Как сохранить публичную сигнатуру из двух параметров, рекурсируя по трём?
Ошибка не означает, что тип бесконечен. Она означает, что компилятор исчерпал бюджет глубины инстанцирования. Ключ — где стоит рекурсивный вызов.
// НЕ хвостовой: вызов зашит внутрь template literal
type Join<T extends string[], Sep extends string> =
T extends [infer H extends string, ...infer R extends string[]]
? R extends [] ? H : `${H}${Sep}${Join<R, Sep>}`
: '';
Каждый уровень обязан дождаться результата вложенного Join, чтобы подставить его в строку, поэтому все кадры живут одновременно. Аккумулятор переносит склейку до вызова, и вызов становится последним действием ветки — компилятор распознаёт такую форму и разворачивает её итеративно.
type Join<T extends string[], Sep extends string, Acc extends string = ''> =
T extends [infer H extends string, ...infer R extends string[]]
? Join<R, Sep, Acc extends '' ? H : `${Acc}${Sep}${H}`>
: Acc;
type Short = Join<['a', 'b', 'c'], '-'>; // 'a-b-c' — сигнатура не изменилась
Публичная сигнатура сохранена: третий параметр имеет значение по умолчанию, поэтому вызывающий код о нём не знает.