为什么TypeScript会将可辨识联合收窄为any而非never?
我在TypeScript中定义了一个带标签的可辨识联合(discriminated union)来表示多种输入类型。编写基于type字段(判别符)的穷尽switch辅助函数时,TypeScript错误地将默认分支的类型收窄为any,而非预期的never。
穷尽处理意味着已覆盖所有case,默认分支本应是不可达的断言,但此处TypeScript错误推断输入类型为any。以下是完整代码示例:
type inputType = { type: "select"; source: "notes"; folder: string; } | { type: "tag"; exclude?: string | undefined; } | { type: "slider"; min: number; max: number; } | { type: "note"; folder: string; } | { type: "folder"; } | { type: "dataview"; query: string; } | { type: "number" | "text" | "date" | "time" | "datetime" | "textarea" | "toggle" | "email" | "tel"; } | { type: "select"; source: "fixed"; options: { value: string; label: string; }[]; } | { type: "multiselect"; source: "notes"; folder: string; } | { type: "multiselect"; source: "fixed"; multi_select_options: string[]; allowUnknownValues: boolean; } | { type: "multiselect"; source: "dataview"; query: string; allowUnknownValues: boolean; } | { type: "document_block"; body: string; } function absurd<T>(_: never): T { throw new Error('Absurd!') } export function requiresListOfStrings(input: inputType): boolean { switch (input.type) { case "multiselect": case "tag": return true; case "select": case "dataview": case "note": case "folder": case "slider": case "document_block": case "number": case "text": case "date": case "time": case "datetime": case "textarea": case "toggle": case "email": case "tel": return false; default: return absurd(input.type); // 这里出现错误 } }
TypeScript报告的错误如下:
Argument of type 'any' is not assignable to parameter of type 'never'. Property 'type' does not exist on type 'never'.
我知道可以通过将type属性提取到变量中来规避,但想了解错误产生的原因。已在5.4和5.5版本测试,均出现该问题。
这个问题的核心在于TypeScript对嵌套在switch语句中的复杂可辨识联合类型的收窄逻辑存在局限性,具体原因如下:
重复标签的联合类型结构冲突
你的inputType联合中存在多个共享同一type标签的成员:比如两个type: "select"的对象、三个type: "multiselect"的对象。当直接在switch中使用input.type时,TypeScript在处理这些重复标签的联合成员时,无法准确追踪所有可能的分支是否被完全覆盖。switch内的类型收窄边界限制
在switch语句中,TypeScript是通过每个case的字面量匹配来逐步收窄input的类型。但对于存在重复type标签的联合类型,当处理完所有显式case后,TypeScript的类型检查器无法正确推断剩余的input.type应该是never,反而会因为内部的类型推断冲突,将其退化为any类型。直接访问
input.type的推断缺陷
在default分支中,理论上input的类型应该被收窄为never(因为所有可能的type都已被case覆盖),但由于联合类型中重复标签的存在,TypeScript无法完成这个收窄,反而错误地判定input是never类型——而never类型上不存在任何属性,同时又因为内部推断矛盾,把input.type的类型判定为any,最终触发了两条错误信息。
而将type提取到变量中(比如const type = input.type;再基于这个变量写switch)可以规避问题,因为此时TypeScript会先将type推断为所有可能的字面量联合,之后对这个变量进行分支匹配时,能正确收窄类型,最终推断default分支的type为never。
内容的提问来源于stack exchange,提问作者Danielo515

