For a detailed response to this query, refer to microsoft/TypeScript#9998.
The issue observed is not a bug but rather a TypeScript design limitation that is challenging to address easily. An illustration of functional code:
function fn() {
let flag = true
for (let i = 0; i < 10; i++) {
if (flag === false) { break }
flag = false;
}
return flag
};
In the provided example, the compiler recognizes flag
as type boolean
, a shorthand notation in TS for the union true | false
. The compiler narrows down the apparent type of flag
from true | false
to just true
after the initial assignment of true
. However, within the loop scope, the reassignment to false
resets the narrowing to
boolean</code inside the loop.</p>
<hr />
<p>Unfortunately, when refactoring the <code>flag = false
line into the body of
f()
, the compiler does not reset the narrowing post function call execution, resulting in an error due to incorrect assumption about
flag
's type.
The reason behind this behavior is the limitations in accurately and efficiently performing control flow analysis with closure mutations. Inline expansion for every function call would be impractical, leading to extended compile times, hindering performance.
As accuracy isn't achievable, the compiler relies on optimistic assumptions like assuming function calls have no impact on state. This approach saves compilation time but can lead to errors in scenarios where function calls do indeed alter state.
While there may be room for improvement in this area, at present, TypeScript struggles to track state mutations within function calls.
To prevent such erroneous errors when dealing with state-changing functions, refrain from narrowing flag
's type altogether by adjusting its initial assignment as follows:
let flag = true as boolean;
This widens the type of the true
value to true | false
, keeping flag
indeterminate throughout the scope according to the compiler.
Playground link to code