为何TypeScript对条件函数类型与仅含条件返回值的同类型处理不同?
我定义了两个预期等价的MatchT函数类型,但它们的表现截然不同:
第一个版本:
type MatchT<T extends {id: string}, M> = [M] extends [TyMap<infer X>] ? [T] extends [X] ? (t: T, m: M) => Ret<T, M> : (t: T, m: M) => never : (t: T, m: M) => never;
第二个版本:
type MatchT<T extends {id: string}, M> = (t: T, m: M) => [M] extends [TyMap<infer X>] ? [T] extends [X] ? Ret<T, M> : never : never;
两者的核心区别是:前者将整个函数类型包裹在条件判断逻辑中,后者仅将返回值类型放在条件判断里。
我的需求是让MatchT把<T>(t: T, map: TyMap<T>): Ret<T, TyMap<T>>风格的函数,重新标注为<T, X>(t: T, map: TyMap<X>): Ret<T, TyMap<X>>(仅当T是X的子类型时生效)。第一个版本符合预期,但第二个版本始终返回never——测试发现,只有第二个版本里的[M] extends [TyMap<infer X>]会被判定为false。
原因分析
问题出在TypeScript对条件类型的解析时机和类型参数推断上下文的处理逻辑上:
第一个版本:顶层条件判断优先解析类型
当MatchT<T, M>被实例化时,TypeScript会先执行顶层的条件判断[M] extends [TyMap<infer X>]。此时M的类型已经明确,infer X能正确从M中提取出对应的X类型,后续的[T] extends [X]判断也能正常生效,最终生成符合预期的函数类型。第二个版本:返回值条件类型延迟解析
第二个版本中,函数签名(t: T, m: M)是直接定义的,返回值才是条件类型。这种情况下,TypeScript会优先处理函数签名的兼容性匹配,而返回值的条件类型会被延迟解析。当涉及到infer X的推断时,此时M的类型推断上下文发生了变化——在返回值位置的条件类型中,M可能没有被正确关联到TyMap<X>的结构上,导致[M] extends [TyMap<infer X>]判定为false,最终返回never。
简单来说:把条件判断放在顶层,TypeScript会先确认M的类型结构再生成函数;而把条件判断放在返回值里,函数签名先确定,返回值的类型推断失去了正确的上下文,无法匹配TyMap<X>的结构。
内容的提问来源于stack exchange,提问作者Blue Nebula

