typescript - 为什么 typescript 说这个变量是 "referenced directly or indirectly in its own initializer"?

标签 typescript type-inference

这是代码( Playground Link ):

interface XY {x: number, y: number}

function mcve(current: XY | undefined, pointers: Record<string, XY>): void {
    if(!current) { throw new Error(); }
    
    while(true) {
        let key = current.x + ',' + current.y;
        current = pointers[key];
    }
}
这个例子中的代码并不意味着做任何有用的事情;我删除了所有不需要证明问题的东西。 Typescript 在编译时报告以下错误,在变量 key 所在的行声明:

'key' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer.


据我所知,在循环的每次迭代开始时,Typescript 知道 current缩小到类型 XY ,以及 current.xcurrent.y都是 number 类型的,因此应该很容易确定表达式 current.x + ',' + current.y类型为 string ,并推断出 key类型为 string .在我看来,字符串连接显然应该是 string 类型的。 .但是,Typescript 不会这样做。
我的问题是,为什么 Typescript 不能推断出 key类型为 string ?

在调查此问题时,我发现了导致错误消息消失的代码的几处更改,但我无法理解为什么这些更改对以下代码中的 Typescript 很重要:
  • 捐赠 key显式类型注释 : string ,这是我在我的真实代码中所做的,但这并不能帮助我理解为什么不能推断出这一点。
  • 注释掉该行 current = pointers[key] .在这种情况下 key正确推断为 string ,我不明白为什么随后分配给 current应该使这更难推断。
  • 更改 current参数类型来自 XY | undefinedXY ;我不明白为什么这很重要,因为 current确实有类型 XY在循环开始时通过控制流类型缩小。 (如果没有,那么我预计会出现类似“current 可能是 undefined ”之类的错误,而不是实际的错误消息。)
  • 更换 current.xcurrent.y与一些其他类型的表达式 number .我不明白为什么这很重要,因为 current.xcurrent.y确实有类型 number在那个表情里。
  • 更换 pointers具有 (s: string) => XY 类型的函数并用函数调用替换索引访问。我不明白为什么这很重要,因为对 Record<string, XY> 的索引访问似乎它应该相当于 (s: string) => XY 类型的函数调用,鉴于 Typescript 确实假定索引将出现在记录中。
  • 最佳答案

    microsoft/TypeScript#43047对于此类问题的规范答案。
    这是 TypeScript 类型推断算法的设计限制。一般来说,编译器可以推断变量的类型xx 的初始化赋值,它需要知道被赋值的表达式的类型。如果该表达式包含对类型尚未显式注释的其他变量的引用,则它也需要推断这些变量的类型。如果这个依赖链回到 x在被解析之前,编译器只是放弃并声明 x在它自己的初始化程序中引用。
    在你的情况下,我想编译器的分析是这样的(我不是编译器专家,所以这只是为了说明,而不是规范):

  • key的类型取决于 current.x + ',' + current.y 的类型,这取决于 current.x + ',' 的类型
  • current.x + ','的类型取决于 current.x 的类型和','的类型
  • current.x的类型取决于 current 的类型.
  • current是联合类型变量,其表观类型可以是 narrowed via control flow analysis , 所以它的类型在 key分配取决于任何先前的此类缩小,例如分配 current = pointers[key]这可能发生在前一个循环的末尾。
  • pointers[key]的类型取决于 pointers 的类型和 key 的类型.
  • pointers的类型注释为 Record<string, XY>并且没有通过控制流分析缩小范围,所以我们可以停止看这里。
  • key的类型取决于...嘿等一下,检测到圆形! 🤯

  • 无论如何,这都不是理想的编译器行为。但这并不是 TypeScript 中的真正错误,因为 key的初始化程序引用 current第二次通过循环 current有一个引用 key 的赋值.所以key确实在其初始值设定项中间接引用了自己……这是非常可靠的“设计限制”领域。

    当然,在上面的许多要点中,一个理性的人可能在行为上与编译器不同。例如,考虑
  • current.x + ','的类型取决于 current.x 的类型和 ',' 的类型

  • 一般而言,形式为 a + b 的表达式的类型是正确的。取决于 a 的类型和b的类型, a 有一些特殊类型(或 b )这意味着您可以“短路”类型分析并完全忽略 b 的类型(或 a)。在上述情况下,由于 current.x + ','正在添加 stringcurrent.x ,结果肯定是string不管怎样current.x原来是。
    不幸的是,编译器在这里没有做这样的分析。也许有人可以在 GitHub 上提出这样的问题,但我不知道它会被实现。总的来说,这种对“可短路”表达式的额外检查可能会在编译器性能方面收回成本。但如果它降低了编译器的平均性能,那么治愈可能比疾病更糟糕。看到这样的功能请求会很有趣,我肯定会给它一个 👍,但我对它被采用不会很乐观。

    无论如何,您在问题中谈论的更改会破坏上述链的某些部分,并防止编译器掉入循环漏洞。显式注释 keystring是明显的修复,并使编译器现在只检查 key 的类型而不是推断它。当它再次到达keycurrent = pointers[key] ,它知道keystring并且可以继续前进。

    关于typescript - 为什么 typescript 说这个变量是 "referenced directly or indirectly in its own initializer"?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/67660342/

    相关文章:

    arrays - 为什么此列表推导式返回 Array{Any,1} 而不是 Array{Symbol,1}?

    javascript - Angular 8 : Assign Date Values to Type Moment

    angular - 从 Ionic 3.5 中的 Controller 内部 View 获取元素

    typescript - 使用@nomiclabs/hardhat-waffle 实现装置

    type-inference - D 中的函数和委托(delegate)文字

    typescript - 在 TypeScript 中推断接口(interface)类型参数

    Angular 2 错误 : Unhandled Promise rejection: Template parse errors: More than one component:

    javascript - Angular 2/4 接口(interface)声明问题

    Java泛型类参数类型推断

    typescript - 实例化类的通用 TypeScript 函数