TypeScript中is_same工具类型处理字符串字面量联合类型的行为差异问询
is_same<"a" | "b", "a" | "b">返回boolean而手动展开逻辑返回true? 问题描述
我正在尝试用TypeScript实现类似C++ std::type_traits的静态类型分析工具,首先实现了is_same<>类型工具。这个工具在处理类类型和基本类型时都正常,但处理字符串字面量联合类型时出现了异常。
我的is_same定义如下:
type is_same<A, B> = A extends B ? B extends A ? true : false : false;
当我调用is_same<"a" | "b", "a" | "b">时,得到的类型是boolean;但如果手动展开这个逻辑,写成:
type _2 = "a" | "b" extends "a" | "b" ? "a" | "b" extends "a" | "b" ? true : false : false;
得到的结果却是true。
我使用的是TypeScript 4.7.4版本,想知道这两种结果存在差异的原因是什么?
回答
这个差异的核心原因是**TypeScript的分布式条件类型(Distributive Conditional Types)**机制,它会在特定场景下自动拆分联合类型进行计算,而手动展开时不会触发这个行为。
1. 分布式条件类型的触发规则
当条件类型的形式是 T extends U ? X : Y,且 T 是一个裸泛型参数(没有被包裹在数组、元组、对象等结构中),同时 T 是联合类型时,TypeScript会自动将条件类型分发到联合类型的每个成员上:分别对每个成员执行条件判断,最后把所有结果合并成一个新的联合类型。
2. 分析is_same<"a" | "b", "a" | "b">的展开过程
你的is_same定义里,两次条件判断都使用了裸泛型参数,所以会触发两次分发:
- 第一步:第一个条件
A extends B中,A是裸参数且为"a" | "b",因此拆分为两个分支:is_same<"a", "a" | "b">和is_same<"b", "a" | "b">,最终结果是这两个分支的联合。 - 第二步:以
is_same<"a", "a" | "b">为例,第一个条件"a" extends "a" | "b"结果为true,进入第二个条件B extends A。此时B是裸参数且为"a" | "b",再次触发分发,拆分为"a" extends "a"和"b" extends "a"—— 前者结果为true,后者为false,所以这个分支的结果是true | false(也就是boolean)。 - 同理,
is_same<"b", "a" | "b">的结果也是boolean。 - 最终合并两个分支,
boolean | boolean仍然是boolean,这就是你看到的_1的结果。
3. 手动展开为什么得到true?
手动展开的_2没有使用泛型参数,所以不会触发分布式条件类型。TypeScript直接对整个联合类型进行判断:"a" | "b" 显然extends自身,两次判断结果都是true,所以最终得到true。
4. 修复is_same的方法
要避免分布式条件类型的影响,只需要把泛型参数包裹在元组里(破坏“裸参数”的条件),这样就能直接比较整个类型:
type is_same<A, B> = [A] extends [B] ? [B] extends [A] ? true : false : false;
现在调用is_same<"a" | "b", "a" | "b">就会得到正确的true结果了。
内容的提问来源于stack exchange,提问作者Terukazu Inoue

