TypeScript: No error when an arrow function uses a variable from an unreachable branch
🔎 Search Terms
variable used before declaration function
🕗 Version & Regression Information
- This is the behavior in every version I tried since v3.3.3333
⏯ Playground Link
💻 Code
(() => {
if (Math.abs(1) === 1) {
requestAnimationFrame(() => console.log(foo));
return;
}
const foo = 2;
console.log(foo);
})();
🙁 Actual behavior
Fails at runtime with
Uncaught ReferenceError: Cannot access 'foo' before initialization
🙂 Expected behavior
The same error, but at compile time
Additional information about the issue
If you replace requestAnimationFrame() with an IIFE (``), it correct
About this issue
- Original URL
- State: closed
- Created 6 months ago
- Comments: 24 (2 by maintainers)
This is a duplicate of #11498. It’s sometimes valid and idiomatic to write this code, but we don’t know if it’s safe or not unless we know the semantics of the receiver of the callback. It’s not practical to always error on this, so the only thing we can do is never error.
Yes, I’m fully aware - I was specifically arguing against Ryan’s assertion that this code could ever be valid as written (it can’t).
If the branch is not called,
foois initialized.If the branch is called,
foois never initialized, and the CFA could reliably conclude that accessingfoois an error - IN THIS CASE.That’s seems true. However, because of #11498, for the general case, const declarations are hoisted. So #11489 is the crux here. By hoisting, the CFA can safely assume reference by (async) functions at the top scope and any subscopes, early returns or no. To allow for this case, it would have to know otherwise.
No, that’s irrelevant - even if the branch condition is only sometimes true, calling
requestAnimationFramealways corresponds to an early return.@RyanCavanaugh It is true in this case that, if
requestAnimationFrameis called, then theconstwill never be initialized (because the function returns early)—regardless of how many frames it takes to call the callback or the exact condition in theif.This isn’t a useful hypothetical. If it “can never be valid” then no one would write the
ifin the first place. IfMath.abs(1)were-1andrequestAnimationFramewaited until the next event loop tick to invoke its argument, then the code would be valid. The point is that TypeScript doesn’t know either of those facts and thus can’t conclude that it’s invalid.