在下面的代码中,为什么在定义类型时返回不同的结果,而不是输入相同的类型作为泛型:

type StrictEqual<A1, A2> = [A1] extends [A2] ? ([A2] extends [A1] ? true : false) : false

const typesMatch = <A, B>(match: StrictEqual<A, B>) => match

type Pins<
  Node extends {
    Error: unknown
    Output: unknown
    ResultResolverController: unknown
  },
  AccumulatedTypes extends {
    AccumulatedErrorResolverControllers: unknown
  },
> = {
  (result: Node['Output']): Node['ResultResolverController']
  error: (error: Node['Error']) => AccumulatedTypes['AccumulatedErrorResolverControllers']
}

type Foo = Pins<
    {
      Output: 'OutputN2'
      Error: 'ErrorN2'
      ResultResolverController: 'ResResolverN2'
    },
    {
      AccumulatedErrorResolverControllers: 'ErrResolverN1' | 'ErrResolverN2'
    }
  >

type Bar = Pins<
    {
      Error: unknown
      Output: unknown
      ResultResolverController: unknown
      ErrorResolverController: unknown
    } & {
      Output: 'OutputN2'
      Error: 'ErrorN2'
      ResultResolverController: 'ResResolverN2'
      ErrorResolverController: 'ErrResolverN2'
    },
    {
      AccumulatedErrors: 'ErrorN1' | 'ErrorN2'
      AccumulatedOutputs: 'OutputN1' | 'OutputN2'
      AccumulatedResultResolverControllers: 'ResResolverN1' | 'ResResolverN2'
      AccumulatedErrorResolverControllers: 'ErrResolverN1' | 'ErrResolverN2'
    }
  >

typesMatch<Foo,Bar>(true) // works

typesMatch<
  Pins<
    {
      Output: 'OutputN2'
      Error: 'ErrorN2'
      ResultResolverController: 'ResResolverN2'
    },
    {
      AccumulatedErrorResolverControllers: 'ErrResolverN1' | 'ErrResolverN2'
    }
  >,  // should be the same as Foo
  Pins<
    {
      Error: unknown
      Output: unknown
      ResultResolverController: unknown
      ErrorResolverController: unknown
    } & {
      Output: 'OutputN2'
      Error: 'ErrorN2'
      ResultResolverController: 'ResResolverN2'
      ErrorResolverController: 'ErrResolverN2'
    },
    {
      AccumulatedErrors: 'ErrorN1' | 'ErrorN2'
      AccumulatedOutputs: 'OutputN1' | 'OutputN2'
      AccumulatedResultResolverControllers: 'ResResolverN1' | 'ResResolverN2'
      AccumulatedErrorResolverControllers: 'ErrResolverN1' | 'ErrResolverN2'
    }
  > // should be the same as Bar
>(true) // doesn't work!

code

推荐答案

Dr TypeScrip的类型判断器需要做出简化的假设才能发挥作用,有时这些假设是不正确的.Pins类型函数被错误地假定为具有可测量的variance(可能是invariant).

当编译器注意到它正在比较Pins<A, C>Pins<B, D>时,它将其简化为AB的比较以及CD的比较,而根本不计算Pins.另一方面,当它没有注意到时,它会对Pins<A, B>Pins<C, D>进行充分判断并比较结果.

这些是不同的比较类型的方法,可能会产生不同的结果,因此在正确性和性能之间存在权衡,您已经遇到了这种情况.

有关更多信息,请参阅microsoft/TypeScript#39549microsoft/TypeScript#48116microsoft/TypeScript#49852,可能还有许多其他内容.


这是你所看到的一个最小的例子.以下代码编译时没有任何问题:

type Foo<T extends { x: 0 }> = () => T['x'];

type Bar = Foo<{ x: 0 }> // type Bar = () => 0
declare let bar: Bar;

type Baz = Foo<{ x: 0, y: 1 }> // type Baz = () => 0
let baz: Baz = bar; // okay

这是有道理的,因为BarBaz都相当于() => 0.但以下看似相同的代码有一个错误:

type Foo<T extends { x: 0 }> = () => T['x'];

declare let bar: Foo<{ x: 0 }>;

let baz: Foo<{ x: 0, y: 1 }> = bar; // error?!
// Type 'Foo<{ x: 0; }>' is not assignable to type 'Foo<{ x: 0; y: 1; }>'.

如果Bar is可以分配给Baz,为什么Foo<{x: 0}>不能分配给Foo<{x: 0, y: 1}>?类型判断器不应该将所有这些类型识别为相互之间是compatible() => 0吗?


答案是它should是相同的,但打字文本类型判断器负担不起完全一致的代价.假设类型判断器需要确定类型X是否可分配给类型Y.correct要做的事情将是执行所谓的完整structural check,即Y的每个成员在 struct 上与X的每个成员进行完全的对比.在一般情况下,递归过程可能非常昂贵(如果类型复杂且深入),甚至无法确定(如果类型本身是递归的).因此,有时,编译器需要跳过完全判断.

它做到这一点的一种方式是使用所谓的variance markers.如果编译器注意到X的形式为F<A>,Y的形式为F<B>,则如果F<T>中的类型参数具有差异标记,则编译器可能能够跳过完全判断.例如,如果已知F<T>T中的covariant,则F<A>可分配给F<B>当且仅当A可分配给B.这意味着编译器只能判断AB,这可能更容易做.

如果所有类型函数都能被准确地标记为协变、逆变、双变或不变,那么我们就可以在不损失正确性的情况下提高类型判断的性能.

不幸的是,这是不可能的.许多类型函数足够复杂,以至于它们的变化不属于那些整齐的类别;对于任意的AB,如果不进行完整的 struct 判断,您可能不知道F<A>是否可以赋给F<B>.在这些情况下,F<T>还需要标记为unreliable(在差异判断失败的情况下,您需要回退到 struct 判断)或unmeasurable(您根本不能依赖差异判断).

因此,现在我们有了一个取舍.频谱范围包括:

  • 将所有类型函数标记为具有不可测量的方差.所有类型判断都将是完全的 struct 判断,我们将拥有从性能糟糕的编译器到以下各项的一致行为:

  • 用尽力而为的可测量差异标记所有类型函数.完全的 struct 判断很少被执行,而且我们的编译器很快就会产生错误的结果.

在中间的任何地方,我们都必须执行一些分析,以确定哪些类型的函数应该具有可测量的差异,并且该分析本身将在性能和正确性之间进行权衡.


做大量的挖掘来找出exactly份 spectral Typescript 在哪里可能并不太有启发性.在上面的示例中,Foo<T>似乎被错误地标记为T中的covariant,当您使用indexed access types时,这通常是合理的做法.但在这种情况下,它会带来问题.如果编译器将Foo<T>标记为不可靠或不可测量,那就太好了.但是,通过执行太多不必要的 struct 判断,或者花费太多时间来决定是否进行 struct 判断,这种情况能否在不 destruct 性能的情况下实现?我不知道.从上面链接的GitHub问题和其他问题来看,扰乱差异标记似乎很容易导致性能问题,因此他们倾向于在可能的情况下避免这样做.

Playground link to code

Typescript相关问答推荐

当类型不能undefined时,如何使编译器抛出错误?

如果存在一处房产,请确保存在其他房产

返回签名与其他类型正确的函数不同的函数

具有条件通用props 的react 类型脚本组件错误地推断类型

如何根据参数的值缩小函数内的签名范围?

在TypeScript中使用泛型的灵活返回值类型

如果请求是流,如何等待所有响应块(Axios)

TypeScrip:基于参数的条件返回类型

学习Angular :无法读取未定义的属性(正在读取推送)

当方法参数和返回类型不相互扩展时如何从类型脚本中的子类推断类型

React重定向参数

获取函数中具有动态泛型的函数的参数

如果字符串文字类型是泛型类型,则用反引号将该类型括起来会中断

如何解决类型T不能用于索引类型{ A:typeof A; B:typeof B;. }'

ANGLE独立组件布线错误:没有ActivatedRouting提供程序

TypeScrip错误:为循环中的对象属性赋值

try 使Angular依赖注入工作

在抽象类构造函数中获取子类型

如何从JWT Reaction中提取特定值

如何在Vue动态类的上下文中解释错误TS2345?