简体   繁体   English

Typescript 使用联合推断通用参数类型

[英]Typescript infer generic param types with union

I have been struggling with the codes below for a few hours.我一直在为下面的代码苦苦挣扎几个小时。 Don't understand why e4 is string not String ?不明白为什么e4string而不是String

type PropConstructor4<T = any> = { new(...args: any[]): (T & object) } | { (): T }
type e4 = StringConstructor extends PropConstructor4<infer R> ? R : false // why string not String ???

I have tested below and I think I could understand.我在下面进行了测试,我想我可以理解。

type a4 = StringConstructor extends { new(...args: any[]): (infer R & object) } ? R : false // String
type b4 = StringConstructor extends { (): ( String) } ? true : false // true
type c4 = StringConstructor extends { (): (infer R) } ? R : false // string

Also, I couldn't understand why e5 is String not string ?另外,我不明白为什么e5String不是string

type PropConstructor5<T = any> = { new(...args: any[]): (T ) } | { (): T }
type e5 = StringConstructor extends PropConstructor5<infer R> ? R : false //why String not string??

TL;DR the compiler uses heuristics to give different priorities to different inference sites, and infers the type from the inference site with the highest priority. TL;DR 编译器使用启发式方法为不同的推理站点赋予不同的优先级,并从具有最高优先级的推理站点推断类型。


Generally speaking, when TypeScript infers a specific type for a type parameter (the R in all your examples), it considers each inference site , which is where the type parameter occurs in the expression it's trying to match.一般来说,当 TypeScript 推断类型参数的特定类型(所有示例中的R )时,它会考虑每个推理站点,这是类型参数出现在它试图匹配的表达式中的位置。 For example, in例如,在

type P = StringConstructor extends 
    (() => infer R) | { new(...args: any[]): (infer R & object) } ? R : never
//         ^^^^^^^  <-- inference sites -->   ^^^^^^^

there are two inference sites for the R type parameter. R类型参数有两个推理站点。 The compiler's job is to try to match StringConstructor with the full expression by inspecting an inference site, coming up with a candidate specific type for that site, and then checking the full expression to determine if that candidate works when substituted in every site.编译器的工作是尝试通过检查推理站点,为该站点提供候选特定类型,然后检查完整表达式以确定该候选在每个站点中替换时是否有效,从而尝试将StringConstructor与完整表达式匹配。

Let's test it out with P above, by pretending that we're the compiler and seeing what happens if we change which inference site to inspect.让我们用上面的P来测试它,假设我们是编译器,看看如果我们改变要检查推理站点会发生什么。


If the compiler chooses the first inference site to inspect:如果编译器选择第一个推理站点进行检查:

type P = StringConstructor extends 
    (() => infer R) | { new(...args: any[]): (infer R & object) } ? R : never
//         ^^^^^^^  <-- inspect this

In this case, string is the candidate it would come up with, since String("hello") produces a string output.在这种情况下, string是它想出的候选,因为String("hello")产生一个string output。 It could then check that string works for the whole expression.然后它可以检查string是否适用于整个表达式。 StringConstructor does indeed extend (() => string) | { new(...args: any[]): (string & object) } StringConstructor确实扩展了(() => string) | { new(...args: any[]): (string & object) } (() => string) | { new(...args: any[]): (string & object) } because it extends the first member of the union (since A extends B | C is true if A extends B or A extends C ), and so R would be inferred as string if the compiler considers only the first inference site. (() => string) | { new(...args: any[]): (string & object) }因为它扩展了联合的第一个成员(因为A extends B | C如果A extends BA extends C ),因此R如果编译器仅考虑第一个推理站点,则将其推断为string


What about the second inference site?第二个推理站点呢?

type P = StringConstructor extends 
    (() => infer R) | { new(...args: any[]): (infer R & object) } ? R : never
//                           inspect this --> ~~~~~~~~

In this case, String is the candidate it would come up with, since new String("hello") produces a String output.在这种情况下, String是它会提出的候选,因为new String("hello")产生一个String output。 It would then check that String works for the whole expression.然后它会检查String是否适用于整个表达式。 StringConstructor does indeed extend (() => String) | { new(...args: any[]): (String & object) } StringConstructor确实扩展了(() => String) | { new(...args: any[]): (String & object) } (() => String) | { new(...args: any[]): (String & object) } because it extends both sides of the union. (() => String) | { new(...args: any[]): (String & object) }因为它扩展了联合的两边。 ( string extends String , so () => string extends () => String ), and so R would be inferred as String if the compiler considers only the second inference site. ( string extends String ,所以() => string extends () => String ),如果编译器只考虑第二个推理站点,那么R将被推断为String


It is also potentially possible that the compiler could consider both inference sites at the same time, and synthesize a union/supertype or intersection/subtype of the candidates depending on variance.编译器也有可能同时考虑两个推理位置,并根据方差合成候选的联合/超类型或交集/子类型。 In this case the parameters are both in a covariant position, so string | String在这种情况下,参数都在协变 position 中,所以string | String string | String or just String (since it is a supertype of string ) would be my guesses if this happens.如果发生这种情况,我会猜测string | String或只是String (因为它是string的超类型)。


So then for the above expression, one could plausibly imagine that string , String , string | String那么对于上面的表达式,人们可以合理地想象string , String , string | String string | String comes out. string | String出来。 What actually happens?实际发生了什么?

type P = StringConstructor extends
    (() => infer R) | { new(...args: any[]): (infer R & object) } ? R : never
// string

It's string .string That means the compiler is giving priority to the first inference site.这意味着编译器优先考虑第一个推理站点。 Compare to what happens in the following:比较以下发生的情况:

type O = StringConstructor extends
    (() => infer R) | { new(...args: any[]): (infer R) } ? R : never
// String

Now, the compiler is giving priority to the second inference site instead.现在,编译器优先考虑第二个推理站点。 Somehow, (infer R & object) has a lower priority than just infer R .不知何故, (infer R & object)的优先级低于仅infer R


So, how does the compiler assign priorities to different inference sites?那么,编译器如何为不同的推理站点分配优先级呢? I can't pretend to know the full details of this.我不能假装知道这件事的全部细节。

It used to be laid out in the TypeScript Specification document, but that is long out-of-date and now archived.曾经在 TypeScript 规范文档中列出,但该文档早已过时,现已存档。 See this section if you're curious.如果您好奇,请参阅本节 Nowadays there is no specification because the language is changing faster than can be rigorously documented.现在没有规范,因为语言的变化速度超过了严格记录的速度。

There are a few issues in GitHub that touch on this idea of inference site priority. GitHub 中有一些问题涉及推理站点优先级的概念。 See microsoft/TypeScript#14829 for a feature request to allow a site to have zero priority and be never used for inference.请参阅microsoft/TypeScript#14829以获取允许站点具有零优先级并且从不用于推理的功能请求。 See microsoft/TypeScript#39295 and microsoft/TypeScript#32389 for issues that stem from developers having a different intuition from what the compiler actually does.请参阅microsoft/TypeScript#39295microsoft/ TypeScript#32389,了解源自开发人员的直觉与编译器实际执行的操作不同的问题。

One common thread is that intersections like (T & {}) have lower priority than types without intersections T .一个常见的线程是,像(T & {})这样的交集的优先级低于没有交集T的类型。 So you can use T & {} to lower the priority of a site if it is getting in the way.因此,如果站点妨碍您,您可以使用T & {}降低站点的优先级。 And so you that explains why infer R & object is not the chosen site for P .所以你解释了为什么infer R & object不是P的选择站点。

Again, I don't know the full details of this, and it probably isn't very enlightening to learn.再说一次,我不知道这个的全部细节,学习它可能不是很有启发性。 If you go through the type checker code you might be able to piece together whether construct signature return types have higher priority than call signature return types, but I wouldn't recommend writing any programs that depend on such details unless you want to keep revisiting it... one cannot guarantee that such particular inference rules will persist across releases of the language.如果您通过类型检查器代码go 可能能够拼凑出构造签名返回类型是否比调用签名返回类型具有更高的优先级,但我不建议编写任何依赖于此类细节的程序,除非您想继续重新访问它...不能保证这种特定的推理规则会在语言的各个版本中持续存在。


Playground link to code Playground 代码链接

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM