This question is related to this issue with the TypeScript compiler:
https://github.com/Microsoft/TypeScript/issues/11498
The discussion ranges far and wide over how to fix the compiler but I didn't see any work arounds.
The short description of the problem is that:
var bar: Foo | null | undefined = null;
results in the type of 'bar' being narrowed to "never". If you have an if block that sets the variable, then it widens, but a callback does not widen the type.
Here's a playground link if you want to see the error.
I found a workaround, but I don't like it (but it does work).
Is there a better work around?
class Quz {
foreach(func: ((x: number) => boolean)){
for(var i = 0; i != 10;i++ ){
var isDone = func(i);
if(isDone) break;
}
}
}
interface Foo {
getValue(): string;
}
var bar: Foo | null | undefined;
/* Is there a better way to silence the stupid error??? */
if(Math.random() < 2) {
bar = null;
}
var quz = new Quz();
console.warn("Starting foreach")
quz.foreach(v => {
if(v === 6){
bar = {getValue() { return "z"}};
return true;
}
return false;
})
var baz = (bar ? bar.getValue() : "?")
console.warn(baz);
The control flow analysis TypeScript uses to narrow variables of union type, such as when you assign a value to it, doesn't properly account for what happens when these variables are changed in closures. This is the subject of microsoft/TypeScript#9998. It's not computationally feasible for the compiler to follow control flow into and out of functions, so it makes some approximations. In this case the relevant approximation is that calling functions will not have any effect on closed-over variables.
So bar
is indeed reassigned inside the callback, but the compiler doesn't see it. And thus it is never widened from its initial assignment to null
. Oops.
In cases like this, the "standard" workaround I've seen, as mentioned in this comment on ms/TS#9998, is to block the assignment narrowing by using a type assertion. So you change
var bar: Foo | null | undefined = null;
to
var bar = null as Foo | null | undefined;
This doesn't change the underlying type of the bar
variable, but it does change the apparent type, since null as Foo | null | undefined
is only seen by the compiler as type Foo | null | undefined
. Thus bar
is not narrowed by the assignment, and so it doesn't erroneously believe that bar
must be nullish inside (bar ? bar.getValue() : "?")
.
It's not a perfect solution, since it turns off a useful feature, but at least it lets you make progress.
Playground link to code
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With