Issue
Let's have a following code:
interface Foo {
a?: number
b?: number
}
function foo(options?: Foo) {
const {
a, // <-- error here
b = a
} = (options ?? {})
return [a, b]
}
Why this code fails with:
'a' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer.
Doesn't a
have type number | undefined
?
However, if I rewrite the foo
function to:
function foo(options?: Foo) {
const f = options ?? {}
const {
a,
b = a
} = f
return [a, b]
}
No error is emitted.
Why? Is it a bug?
Solution
This might be a bug in TypeScript, as described in microsoft/TypeScript#49989. It seems that there is a very recent (like, as of today, 2023-12-12) pull request at microsoft/TypeScript#56753 which aims to fix it. If it does I'll check back here and update the answer. Of course, that issue is specifically about destructuring arrays and not general objects, and sometimes TypeScript has different behavior that depends on this. So I'm not 100% sure it's the same issue.
In general, however, TypeScript's type inference algorithm is limited in its ability to avoid circularities. See microsoft/TypeScript#45213 for a similar situation that runs into this. If, when analyzing const { a, b = a } = options ?? {}
, the compiler decides to, for whatever reason, defer the analysis of options ?? {}
until after it knows the context in which it is assigned, then const { a, b = a }
will be seen as circular since the type of a
depends on the type of {a, b}
which depends on the type of b
which depends on the type of a
. Of course a human being would never get caught in such a situation, but the type checker doesn't have the luxury of seeing "the big picture".
While the particular problem you found might indeed be considered a bug in TypeScript and have a straightforward fix, there are many similar scenarios classified as a design limitation of the language, because fixing it would require a massive refactoring. As mentioned in this comment on microsoft/TypeScript#45213:
It's instructive to look at the call stack at the point that we log the circularity error and see how every caller in that path (of which the incoming call stack is could be practically any function in the checker) would have to be basically completely rewritten to handle the answer of "ask me again later".
We also need to be able to detect circularities instead of looping forever. [...] then any sort of work-deferring mechanism needs to be able to detect when it's looping forever so that it doesn't attempt to infer an infinite sequence [...].
The relevant codepaths in the checker here are not especially complicated (relatively speaking) and I'd encourage you to try a PR to understand the problem more deeply. Not to be glib, but if this were low-hanging fruit, we would have done it already 😉. Again, it's not impossible, but it's not at all a straightforward fix - we'd have to rewrite very large portions of the entire checker.
Answered By - jcalz
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.