为啥泛型 T 仅在具有约束时才被推断为文字类型?

Posted

技术标签:

【中文标题】为啥泛型 T 仅在具有约束时才被推断为文字类型?【英文标题】:Why generic T is inferred as a literal type only when it has a constraint?为什么泛型 T 仅在具有约束时才被推断为文字类型? 【发布时间】:2019-04-06 06:27:59 【问题描述】:

看下面的sn-p

declare function foo<T>(a: T): (b: T) => boolean;

foo(111)(222);       // T inferred as 'number'
foo('hello')('bye'); // T inferred as 'string'

declare function bar<T extends number | string>(a: T): (b: T) => boolean;

bar(111)(222);       // T inferred as '111'
bar('hello')('bye'); // T inferred as 'hello'

playground

如您所见,bar 函数将T 的类型推断为文字类型(示例中为'111''hello'),但在函数foo 中,它们被推断为numberstring,唯一的区别就是约束。

奇怪的是,如果使用盒装类型如下

declare function baz<T extends Number | String>(a: T): (b: T) => boolean;

然后T 被推断为numberstring,但其中一个是原始类型并且T 被推断为文字类型就足够了:

declare function brr<T extends Number | string>(a: T): (b: T) => boolean;

所以问题是: 为什么foo('hello')T 推断为stringbar('hello')T 推断为'hello'?为什么它只在T 受到约束时才会发生(至少在这个例子中)?

【问题讨论】:

当你说“为什么”时,你是在问背后的原因,还是在问 TypeScript 中的什么机制导致它发生? 顺便说一句,Number | string 推断数字文字可能是一个错误。似乎这种不一致的东西是“设计使然”的。 @PatrickRoberts 我在测试Number | String 时也有同样的想法,但是由于每个值都可以强制(在严格模式下)到包装的对象,然后是数字文字type 确实可以分配给“Number”。此外,这两种类型在函数内部将(大部分)无法区分。作为参考,这也适用于const n: Number = 42。尽管如此,这似乎很奇怪。 【参考方案1】:

有时,您希望为字符串文字 'hello' 推断出精确的文字类型 'hello'。有时,您希望为字符串文字 'hello' 推断出更广泛、非特定的 string 类型。

规则——什么时候应该推断出确切的类型,什么时候应该扩大类型——经历了几次迭代,当前的实现is presented here:

在调用表达式的类型实参推断期间,为类型参数 T 推断的类型扩大到其扩大的文字类型,如果:

对 T 的所有推断都是针对特定参数类型中 T 的***出现进行的,并且 T 没有约束或其约束不包括原始类型或文字类型,并且 T 在推理期间已修复,或者 T 未出现在返回类型的顶层。

【讨论】:

以上是关于为啥泛型 T 仅在具有约束时才被推断为文字类型?的主要内容,如果未能解决你的问题,请参考以下文章

为啥 TS 中的泛型接口不能正确推断类型?

为啥 TypeScript 中的方法链接会导致泛型类型推断失败?

为啥泛型类型约束不可继承/分层强制执行

Scala的泛型

值限制 - 该值已被推断为具有泛型类型

@Value lateinit var 仅在从测试调用时才被实例化