为什么TypeScrip说此变量在其自身的初始值设定项中被直接或间接引用? [英] Why does Typescript say this variable is "referenced directly or indirectly in its own initializer"?

查看:0
本文介绍了为什么TypeScrip说此变量在其自身的初始值设定项中被直接或间接引用?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

代码如下(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];
    }
}

本例中的代码并不是要做任何有用的事情;我删除了演示该问题所不需要的所有内容。TypeScrip在编译时在声明变量key的行上报告以下错误:

‘key’隐式具有类型‘any’,因为它没有类型批注,并且在其自身的初始值设定项中被直接或间接引用。

就我所知,在循环的每次迭代开始时,TypeScrip知道current被缩小为XY类型,而current.xcurrent.y都是number类型,因此应该直接确定表达式current.x + ',' + current.ystring类型,并推断keystring类型。在我看来,字符串连接显然应该string类型。但是,TypeScrip不能做到这一点。

我的问题是,为什么打字脚本不能推断key属于string


在调查该问题时,我发现对代码进行了几处更改,这些更改会导致错误消息消失,但我不能理解为什么这些更改对此代码中的键入脚本很重要:

  • key一个显式类型批注: string,这是我在实际代码中所做的,但它并不能帮助我理解为什么不能推断这一点。
  • 注释掉current = pointers[key]行。在这种情况下,key被正确推断为string,我不明白为什么对current的后续赋值会使其更难推断。
  • current参数的类型从XY | undefined更改为XY;我不明白这有什么关系,因为current确实通过控制流类型缩小在循环开始时具有类型XY。(如果不是这样,我预计类似";current的错误可能是undefined,而不是实际的错误消息。)
  • current.xcurrent.y替换为其他类型的number表达式。我不明白这有什么关系,因为current.xcurrent.y在表达式中确实有number类型。
  • (s: string) => XY类型的函数替换pointers,用函数调用替换索引访问。我不明白这有什么关系,因为Record<string, XY>的索引访问看起来应该等同于(s: string) => XY类型的函数调用,因为打字脚本确实假设索引将出现在记录中。

推荐答案

有关此类问题的规范答案,请参阅microsoft/TypeScript#43047

这是打字类型推理算法的设计限制。一般而言,编译器要在给定对x的初始化赋值的情况下推断变量x的类型,它需要知道被赋值的表达式的类型。如果该表达式包含对其类型尚未显式注释的其他变量的引用,则它还需要推断这些变量的类型。如果此依赖关系链在解析之前返回到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类型取决于...嘿,等一下,检测到循环!🤯

无论如何,这都不是理想的编译器行为。但它在类型脚本中不是真正的错误,因为key的初始值设定项引用current,并且第二次通过循环current有一个引用key的赋值。因此,key确实在其初始值设定项中间接引用自身……这是相当可靠的设计限制。


当然,在上面的许多要点中,理性的人可能在行为上与编译器有很大不同。例如,考虑

  • current.x + ','的类型取决于current.x的类型和','的类型
虽然一般来说,a + b形式的表达式的类型确实取决于a的类型和b的类型,但有一些特定的a(或b)类型意味着您可以对类型分析进行短路分析,而完全忽略b(或a)的类型。在上述情况下,由于current.x + ','是将string添加到current.x,因此无论current.x是什么,结果都肯定是string

不幸的是,编译器在这里不做这样的分析。也许有人可以在GitHub中提出这样的要求,但我不知道它会不会实现。总而言之,这种对短路表达式的额外检查可能会在编译器性能方面得到补偿。但如果它降低了编译器的平均性能,那么治愈的方法可能比疾病更糟糕。看到这样的功能请求会很有趣,我肯定会给它一个👍,但我不会对它被采用持非常乐观的态度。


总之,您在问题中谈到的更改会中断上述链的某一部分,并防止编译器跌入循环漏洞。显式地将key注释为string是显而易见的修复方法,它使编译器现在可以只检查key的类型,而不是推断。当它再次到达current = pointers[key]中的key时,它知道keystring,可以继续前进。

这篇关于为什么TypeScrip说此变量在其自身的初始值设定项中被直接或间接引用?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆