检查递归类型是否以特定类型结尾

Check if a recursive type ends with a certain type

我在 Typescript 中有以下类型定义:

type MU = ['I' | 'U' | 'M', MU] | '.'

我正在尝试编写助手:

type EndsWith<T extends MU, M extends MU> = T extends M ? any : T extends [infer _, infer Q] ? EndsWith<Q, M> : never 

这将检查特定类型 T 是否以给定的 M 结尾,即 :

const a: EndsWith<['U', ['I', '.']], ['I', '.']>  // types as any
const b: EndsWith<['U', '.'], ['I', '.']>         // types as never

但是,EndsWith 不是我指定的别名类型。寻找解决方法:)

目前不支持循环条件类型。请参阅 microsoft/TypeScript#26980 以了解建议解除此约束的未决问题。目前,它们是被禁止的。


请注意,很可能 欺骗 编译器评估循环条件类型,但如果你这样做并且表现不佳,那是你的问题而不是 TypeScript 的问题。

这是一个这样的技巧。是 not supported:

type EndsWith<T extends MU, M extends MU> =
    T extends M ? any :
    {
        base: never,
        step: EndsWith<T extends any[] ? T[1] : never, M>
    }[T extends '.' ? "base" : "step"];

我们所做的是将明显的循环类型转变为递归的树状对象,我们立即使用延迟条件类型对其进行索引。这仍然是循环,但编译器没有注意到它。

它"works":

declare const a: EndsWith<['U', ['I', '.']], ['I', '.']>  // any
declare const b: EndsWith<['U', '.'], ['I', '.']>         // never

但很脆弱。如果你这样写:

declare const c: EndsWith<MU, ['I', MU]>  // oops

您要求编译器通过首先检查是否 MU extends ['I', MU](不是)来评估 EndsWith<MU, ['I', MU]>,然后再次递归到... EndsWith<MU, ['I', MU]>。呃哦。某个地方会有编译器问题;很可能你会得到一个“这种类型实例化太深的错误。如果你真的很聪明,你可以使用上面的 EndsWith 编写一些代码,这实际上会导致编译器挂起而不是吐出错误.

我觉得你只是为了学术目的而写 EndsWith(因为作为 cons-like 对实现的列表不是惯用的 JS 或 TS)所以它不受支持的事实可能不是问题。如果我错了,而你想在某处使用它作为生产代码,请不要。我不想成为针对编译器犯罪的从犯。

希望对您有所帮助;祝你好运! Playground link to code