【问题标题】:Bug in Typescript generics, or just me being stupid?Typescript 泛型中的错误,或者只是我很愚蠢?
【发布时间】:2021-03-03 03:29:07
【问题描述】:

我目前正在进行一项广泛的 TypeScript 练习(typescript-exercises.github.io,练习 14),我似乎被困在我看来像是一个 typescript 错误的问题上。但由于我对 TypeScript 没有那种经验,所以我将首先在这里询问它是否真的是一个错误,或者只是我很愚蠢。 在本练习中,您需要将类型添加到一堆函数中,除了任意数量的参数,如果参数数量不足,则返回子函数。这里的问题是 TypeScript 似乎没有得到正确的泛型。 如果我检查在我的 IDE 的最后一行中使用的func 的类型,它会读作func<*, number>,其中 * 是 complete 类型的 add 而不是数字。 是我做错了什么还是这真的是 TypeScript 搞错了?

declare function func<T1, T2>(subFunc: (arg1: T1, arg2: T2) => T1);
function add(a: number, b: number): number;
function add(a: number): (b: number) => number;
function add(): typeof add;
function add(a?: number, b?: number) {
  if (a === undefined)
    return add;
  if (b === undefined) {
    return function subAdd(b?: number) {
      if (b === undefined)
        return subAdd;
      return a + b;
    }
  }
  return a + b;
}

console.log( func(add) );

实际练习禁止更改测试(最后一行),因此很遗憾不能手动提供泛型。

附:这是对实际练习的简化。如果您想查看原点,这是在 test.ts 中的一项测试中用作 reducer 的 add 函数。

【问题讨论】:

  • 什么是add1?您的代码中没有该名称。如果您明确而不是说“完整类型”(无论这意味着什么),您的问题会更清楚。
  • @kaya3 是的,那是我将 add1 重命名为 add 之前的遗留问题。只是为了澄清而对其进行了编辑。

标签: typescript typescript-generics


【解决方案1】:

这并不完全是一个错误,而是由于 TypeScript 的设计限制。

您遇到的问题与microsoft/TypeScript#27027 中的相同;当编译器试图从重载的函数类型(具有多个调用签名的函数)推断时,它只检查最后一个调用签名。这不是您想要的,而是让编译器根据将/将如何调用它来选择重载签名;不幸的是,编译器无法做到这一点;因此存在设计限制。

所以在func(add)中,发生的是:编译器看到add需要匹配类型(arg1: T1, arg2: T2) =&gt; T1,所以它选择调用签名(a: number, b: number) =&gt; number,因为这是第一个一个匹配的。因此T1 被推断为numberT2 也被推断为number

发生的是:编译器看到add 需要匹配类型(arg1: T1, arg2: T2) =&gt; T1。因为add 是一个重载函数,它忽略除最后一个调用签名之外的所有内容,即() =&gt; typeof add。现在这个确实匹配(arg: T1, arg2: T2) =&gt; T1,因为functions of fewer parameters are assignable to functions of more parameters(TL;DR,函数忽略调用它们的额外参数通常是安全的,而且很多时候这是可取的)。因此,对于这两个参数都没有有用的推理站点,但是对于返回类型 T1,有一个很好的推理站点。所以编译器推断T1typeof addT2unknown,因为没有什么比它更好的推断了(类型参数隐含constrained 未知,当泛型类型推断失败时,编译器给出向上并扩大到约束)。

所以你看到func&lt;typeof add, unknown&gt;unknown,不是number,对吗?)你很伤心。


我不确定应该对您的代码做什么(如果有的话)来处理这个问题。我不清楚为什么func() 需要在add 上运行,一般来说,尝试在高阶位置使用重载函数往往会遇到您在此处看到的限制。重载的函数实际上是直接调用的,就是这样。

对于这个特定问题,您可以通过颠倒重载的顺序来处理它,例如

declare function add(): typeof add;
declare function add(a: number): (b: number) => number;
declare function add(a: number, b: number): number;

这将导致 T1T2 根据需要被推断为 number

console.log(func(add));
// function func<number, number>(subFunc: (arg1: number, arg2: number) => number): void

但是一旦你尝试一些稍微不同的func-like 需要访问不同的调用签名的东西,你就会再次遇到同样的问题。

Playground link to code

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-06-24
    • 2018-06-05
    • 2013-08-14
    相关资源
    最近更新 更多