Typescript: Recursive conditional type infer unknown[] instead of string[]

Created on 5 Nov 2020  路  3Comments  路  Source: microsoft/TypeScript


TypeScript Version: 4.1.0-beta


Search Terms:

Code

type Join<T extends string[], D extends string> =
    T extends [] ? '' :
    T extends [string] ? `${T[0]}` :
    T extends [string, ...infer U] ? `${T[0]}${D}${Join<U, D>}` :
    string;

Expected behavior: string[] should be inferred

Actual behavior: unknown[] is inferred

Playground Link: https://www.typescriptlang.org/play?ts=4.1.0-beta#code/C4TwDgpgBAUg9gSwHYB4AqUIA9gSQEwGcpDgAnZAcygG0BdAGigBFMc8iTyqA+KAXgBQUEVAzZcBYvSgB+KAHIFUAFzDR49lNqkKSSnTlQABgBIA3mhoAGOgF9jq9SM2TONXVSYA6X8gBmEGRQAKqG8maWNvYWzHYW8MgoIUzMPA5Oolx6lADcQA

Related Issues:

Design Limitation

Most helpful comment

While it would be nice if TypeScript support this case, be aware that there is a workaround (for this and all related problems): you can replace U with U extends string[] ? U : never, which effectively acts as a "truth me, or explode later if I'm wrong":

type Join<T extends string [], D extends string> =
    T extends [] ? '' :
    T extends [string] ? `${T[0]}` :
    T extends [string, ...infer U] ? `${T[0]}${D}${Join<U extends string[] ? U : never, D>}` :
    string;

The conditional will never fail (provided that there's no bug in the TypeScript compiler and your reasoning that it is definitely string[] is remains actually correct).

You can also consider something like U extends string[] ? U : ["uh oh, something went wrong - check assumption on line 4"] so that the literal type _might_ show up and allow you to debug later. That's a handy debugging technique, anyway.

All 3 comments

While it would be nice if TypeScript support this case, be aware that there is a workaround (for this and all related problems): you can replace U with U extends string[] ? U : never, which effectively acts as a "truth me, or explode later if I'm wrong":

type Join<T extends string [], D extends string> =
    T extends [] ? '' :
    T extends [string] ? `${T[0]}` :
    T extends [string, ...infer U] ? `${T[0]}${D}${Join<U extends string[] ? U : never, D>}` :
    string;

The conditional will never fail (provided that there's no bug in the TypeScript compiler and your reasoning that it is definitely string[] is remains actually correct).

You can also consider something like U extends string[] ? U : ["uh oh, something went wrong - check assumption on line 4"] so that the literal type _might_ show up and allow you to debug later. That's a handy debugging technique, anyway.

As a workaround I used the following expression inside Join
U & string[]

I don't think there's a good path forward that could accomplish this. We'd have to have a lot of extra higher-order reasoning to determine that ...infer U would always produce something assignable to string[]

Was this page helpful?
0 / 5 - 0 ratings

Related issues

CyrusNajmabadi picture CyrusNajmabadi  路  3Comments

wmaurer picture wmaurer  路  3Comments

dlaberge picture dlaberge  路  3Comments

DanielRosenwasser picture DanielRosenwasser  路  3Comments

kyasbal-1994 picture kyasbal-1994  路  3Comments