为什么TypeScrip说此变量在其自身的初始值设定项中被直接或间接引用? [英] Why does Typescript say this variable is "referenced directly or indirectly in its own initializer"?
问题描述
代码如下(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
的行上报告以下错误:
就我所知,在循环的每次迭代开始时,TypeScrip知道‘key’隐式具有类型‘any’,因为它没有类型批注,并且在其自身的初始值设定项中被直接或间接引用。
current
被缩小为XY
类型,而current.x
和current.y
都是number
类型,因此应该直接确定表达式current.x + ',' + current.y
是string
类型,并推断key
是string
类型。在我看来,字符串连接显然应该是string
类型。但是,TypeScrip不能做到这一点。
我的问题是,为什么打字脚本不能推断key
属于string
?
在调查该问题时,我发现对代码进行了几处更改,这些更改会导致错误消息消失,但我不能理解为什么这些更改对此代码中的键入脚本很重要:
- 给
key
一个显式类型批注: string
,这是我在实际代码中所做的,但它并不能帮助我理解为什么不能推断这一点。 - 注释掉
current = pointers[key]
行。在这种情况下,key
被正确推断为string
,我不明白为什么对current
的后续赋值会使其更难推断。 - 将
current
参数的类型从XY | undefined
更改为XY
;我不明白这有什么关系,因为current
确实通过控制流类型缩小在循环开始时具有类型XY
。(如果不是这样,我预计类似";current
的错误可能是undefined
,而不是实际的错误消息。) - 将
current.x
和current.y
替换为其他类型的number
表达式。我不明白这有什么关系,因为current.x
和current.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
时,它知道key
是string
,可以继续前进。
这篇关于为什么TypeScrip说此变量在其自身的初始值设定项中被直接或间接引用?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!