[英]When does A not extends A in TypeScript
For reasons not relevant to the question (but which include fun and profit in type-level programming), one of my types eventually boils down to the following minimal example:由于与问题无关的原因(但包括类型级编程中的乐趣和利润),我的一种类型最终归结为以下最小示例:
type IsTrue<A extends true> = A
type Refl<M> = M extends M ? true : false
type Proof<M> = IsTrue<Refl<M>>
... which results in a compile error. ...这会导致编译错误。 Now, we can discuss how I ended up here, and it is related to a (probably) wrong way of encoding Type Equality.
现在,我们可以讨论一下我是如何走到这一步的,这与(可能)错误的 Type Equality 编码方式有关。 But the question remains: When doesn't
M extends M
resolves to true
for all M
?但是问题仍然存在:
M extends M
什么时候不对所有M
解析为true
? What's the counter-example?反例是什么? How would one go to fix this (possibly by constraining
M
)?一个 go 将如何解决这个问题(可能通过约束
M
)?
Conditional types , as originally implemented in microsoft/TypeScript#21316 are not always evaluated eagerly .最初在microsoft/TypeScript#21316中实现的条件类型并不总是急切地评估。 If they depend on an as-yet-unspecified generic type parameter, like the
M
inside the body of the Refl<M>
definition, the compiler will generally defer evaluation of the conditional type.如果它们依赖于尚未指定的泛型类型参数,例如
Refl<M>
M
主体内的 M,则编译器通常会推迟对条件类型的评估。 Note that the details about exactly when and where the compiler will evaluate a conditional type are not spelled out in any documentation, and they've been evolving over time with new releases of TypeScript, so I can't say anything with absolute certainty here.请注意,关于编译器评估条件类型的确切时间和地点的详细信息没有在任何文档中详细说明,并且随着时间的推移,随着 TypeScript 的新版本的发布,它们一直在发展,所以我在这里不能绝对肯定地说任何话。 But the general situation is as I've described it.
但总体情况正如我所描述的那样。
You were expecting the compiler to look at M extends M? true: false
您期望编译器查看
M extends M? true: false
M extends M? true: false
and eagerly reduce it to true
so that your definition would be equivalent to type Refl<M> = true
, either inside the definition of Refl<M>
or inside the definition of Proof<M>
. M extends M? true: false
并急切地将其简化为true
以便您的定义等同于type Refl<M> = true
,无论是在Refl<M>
的定义内还是在Proof<M>
的定义内。 Neither of these evaluations happen;这些评估都没有发生; they are deferred because in both cases
M
is an unspecified type parameter.它们被推迟是因为在这两种情况下
M
都是未指定的类型参数。 So the compiler cannot be sure that Refl<M>
will be any narrower than the union true | false
所以编译器不能确定
Refl<M>
会比联合true | false
更窄。 true | false
(also known as the boolean
type ), and thus is not known to satisfy the constraint for IsTrue<A extends true>
. true | false
(也称为boolean
类型),因此不知道满足IsTrue<A extends true>
的约束。
So it isn't that M extends M? true: false
所以不是
M extends M? true: false
M extends M? true: false
should ever actually evaluate to false
(as far as I know it can't), but that the compiler fails to evaluate it at all in order to simplify it to true
. M extends M? true: false
实际上应该评估为false
(据我所知它不能),但是编译器根本无法评估它以将其简化为true
。
I don't see any GitHub issues about this specific circumstance, but there are many such issues which boil down to the compiler's inability to analyze conditional types that depend on an unresolved generic type parameter.我没有看到任何关于此特定情况的 GitHub 问题,但有许多此类问题归结为编译器无法分析依赖于未解决的泛型类型参数的条件类型。 For a relatively recent example, see microsoft/TypeScript#46795 .
有关相对较新的示例,请参阅microsoft/TypeScript#46795 。
Note that the particular form M extends... ? ... : ...
请注意,特定形式
M extends... ? ... : ...
M extends... ? ... : ...
where M
is a plain generic type parameter is known as a distributive conditional type and so any unions in M
would be broken into individual members before being evaluated. M extends... ? ... : ...
其中M
是一个普通的泛型类型参数被称为分布式条件类型,因此M
中的任何联合在被评估之前都会被分解为单独的成员。 This doesn't affect whether Refl<M>
could ever be wider than true
, but it can affect the output type:这不会影响
Refl<M>
是否可以比true
更宽,但它会影响 output 类型:
type Refl<M> = M extends M ? true : false
type Hmm = Refl<never> // never
type Refl2<M> = [M] extends [M] ? true : false;
type Hmm2 = Refl2<never> // true
Refl<M>
is distributive over unions in M
, and never
is considered to be "the empty union" (see this comment in ms/TS#23182 ) and thus the output is also the empty union. Refl<M>
在M
中的联合上分配,并且never
被认为是“空联合”(参见ms/TS#23182 中的此评论),因此 output 也是空联合。 But Refl2<M>
is not distributive (since [M]
is not a plain generic type parameter) and so Refl2<never>
is true
.但是
Refl2<M>
不是分布式的(因为[M]
不是普通的泛型类型参数),所以Refl2<never>
是true
。 Both never
and true
are assignable to true
, though, so IsTrue<Refl<M>>
would work out no matter what.但是,
never
和true
都可以分配给true
,因此IsTrue<Refl<M>>
无论如何都可以解决。 But it's trickier than it might seem to demonstrate that.但这比它看起来要证明的要棘手。
It is conceivable that a feature could be introduced whereby conditional types of the form X extends X? Y: Z
可以想象,可以引入一个特征,即 X 形式的条件类型
X extends X? Y: Z
X extends X? Y: Z
could be eagerly reduced to Y
in cases where Y
does not depend on X
(your case) or where X
is not a plain generic type parameter (not your case). X extends X? Y: Z
在Y
不依赖于X
(您的情况)或X
不是普通泛型类型参数(不是您的情况)的情况下,Z 可以急切地简化为Y
But such a feature would have a negative effect on compiler performance, since it would need to check every conditional type for this situation even though the vast majority of conditional types are not like this;但是这样的特性会对编译器性能产生负面影响,因为即使绝大多数条件类型不是这样,它也需要检查这种情况下的每个条件类型; features need to pay for themselves, and this one probably wouldn't.
功能需要自己付费,而这个可能不会。 Even worse, there's probably a lot of real-world code that either intentionally or unintentionally depends on the compiler deferring conditional types like this, and such a feature would be a large breaking change.
更糟糕的是,可能有很多现实世界的代码有意或无意地依赖于编译器延迟这样的条件类型,这样的特性将是一个巨大的突破性变化。
Finally, if you're just interested in a workaround, my usual approach is as follows: If you're sure that T extends U
but the compiler isn't, then you can't use T
in a place that expects something assignable to U
.最后,如果您只是对解决方法感兴趣,我通常的方法如下:如果您确定
T extends U
但编译器没有,那么您不能在期望可以分配的地方使用T
U
。 But you can use Extract<T, U>
.但是您可以使用
Extract<T, U>
。 The Extract<T, U>
utility type is primarily meant to filter any unions in T
so that only those members assignable to U
are left. Extract<T, U>
实用程序类型主要用于过滤T
中的任何联合,以便只留下那些可分配给U
的成员。 But the compiler does see Extract<T, U>
is assignable to both T
and U
(possibly in ms/TS#29437 ), and if you're right that T extends U
, then Extract<T, U>
will eventually evaluate to just T
as desired.但是编译器确实看到
Extract<T, U>
可以同时分配给T
和U
(可能在ms/TS#29437中),如果T extends U
是正确的,那么Extract<T, U>
最终将评估为只是T
根据需要。 So this off-label use of Extract
does what we want:因此,
Extract
的这种标签外使用可以满足我们的要求:
type Proof<M> = IsTrue<Extract<Refl<M>, true>> // okay
This is almost like a type-level type assertion , so similar caveats apply.这几乎就像一个类型级别的类型断言,所以类似的警告也适用。 If you're wrong about
Refl<M>
being assignable to true
, then IsTrue<Extract<Refl<M>, true>>
would still compile, but you're no longer evaluating IsTrue<Refl<M>>
, but something more like IsTrue<never>
or IsTrue<true>
.如果您对
Refl<M>
可分配给true
有误,那么IsTrue<Extract<Refl<M>, true>>
仍然可以编译,但您不再评估IsTrue<Refl<M>>
,而是更多像IsTrue<never>
或IsTrue<true>
。 So be careful!所以要小心!
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.