为什么 TypeScript 无法解析对象解构中的赋值?

TN.*_*TN. 8 typescript

让我们有以下代码:

interface Foo {
    a?: number
    b?: number
}

function foo(options?: Foo) {
    const {
        a, // <-- error here
        b = a
    } = (options ?? {})

    return [a, b]
}
Run Code Online (Sandbox Code Playgroud)

为什么此代码失败并显示:

'a' 隐式具有类型 'any',因为它没有类型注释,并且在其自己的初始值设定项中直接或间接引用。

没有a类型number | undefined

但是,如果我将foo函数重写为:

function foo(options?: Foo) {
    const f = options ?? {}
    
    const {
        a,
        b = a
    } = f

    return [a, b]
}
Run Code Online (Sandbox Code Playgroud)

不会发出任何错误。

为什么?这是一个错误吗?

TypeScript 游乐场

jca*_*alz 5

这是 TypeScript 中的一个错误,如microsoft/TypeScript#49989中所述。microsoft/TypeScript#56753的拉取请求修复了这个问题,因此从 TS5.4 开始,这个特定问题应该不再是问题:

Playground 链接,TS 版本 5.4.0-dev.20240111


然而,一般来说,TypeScript 的类型推断算法在避免循环方面的能力有限。有关遇到此问题的类似情况,请参阅microsoft/TypeScript#45213 。如果在分析 时const { a, b = a } = options ?? {},无论出于何种原因,编译器决定推迟分析 ,options ?? {}直到它知道分配它的上下文const { a, b = a }之后,则将被视为循环,因为 的类型a取决于 的类型,{a, b}而 的类型又取决于类型其中b取决于 的类型a。当然,人类永远不会陷入这种情况,但类型检查器没有看到“大局”的奢侈。

虽然您发现的特定问题确实被认为是 TypeScript 中的错误并且可以直接修复,但还有许多类似的场景被归类为该语言的设计限制,因为修复它需要大量重构。正如microsoft/TypeScript#45213 上的评论中提到的:

在记录循环错误时查看调用堆栈并查看该路径中的每个调用者(其中传入的调用堆栈实际上可以是检查器中的任何函数)基本上必须完全重写为处理“稍后再问我”的答案。

我们还需要能够检测循环而不是永远循环。[...] 那么任何类型的工作延迟机制都需要能够检测它何时永远循环,以便它不会尝试推断无限序列 [...]。

这里检查器中的相关代码路径并不是特别复杂(相对而言),我鼓励您尝试 PR 来更深入地理解问题。不是油嘴滑舌,但如果这是容易实现的目标,我们早就做到了。再说一遍,这并非不可能,但它根本不是一个简单的修复方法 - 我们必须重写整个检查器的很大一部分。