You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
T | U var with initial type T, set to a U inside lambda always deduced as T #10613
When strict null checks are on, TS2 RC deduces that curThing in the if at the end is of type null and inside the block is of type never. The error I get then is that never has no member named x. Fair enough.
In this case I can sidestep the issue by using for (var line of lines) { … } instead of the forEach method, but that may not always be the case. BTW, this is a reduced case from some parser code.
This case also deals with T | null but this applies to any T | U type value. I tested this separately with a string | number initialised to a number and then set to a string, etc.
The issue is the compiler has no idea when the function will be called. in this case lines.forEach will call the lambda immediately. but if this was setTimeout it would not. so doing this for the general case is not possible. all it knows is that the last definite assignment for curThing is null, and the if statement filters that out, leaving the type never.
Not sure if this could be applied to TS, but Swift supports the @noescape modifier on functions passed as parameters that indicates that the passed function will execute before the called function exits. E.g. (Swift code follows)
func example(@noescape lamb:()->()){lamb()}
The compiler then checks that noescape function parameters are not passed to non-noescape function parameters, etc. In Swift this is mainly for some optimisations (and perhaps static analysis) but in TS this could provide static checking and thus accurate determination if a function param will or may not execute within the call context of the owning function.
Downsides: all of the standard library methods would have to be annotated and for it to work for me as well I would have to annotate it as well, but the only other option is cross-module SA (which is also impossible for any native functions)
To follow up on mhegazy's first comment: Indeed, the compiler right now has no way of knowing what will happen, but it pretends it knows anyway by assuming the lambda will not be called directly. If it truly cannot know, it has to leave the type alone and have the programmer do any manual casts, etc.
After looking into it more, and catching up on changes in the last month or so, the reversal of the behavior in #8548 is intentional. see #10118 for more details.
My comment earlier still holds. the compiler does not check lambdas, or any calls for possible side effects. it can not make an assumption about the lambda being called immediately or not. Consider the same example with setTimeout in place for lines.forEach. Adding an annotation on the function parameter declaration seems like a right direction, but it does not provide the full solution either. consider:
To get this right, first every call site has to be investigated for possible side effects, the annotation on the function declaration should be consulted, and the control flow analysis should be done on the function body as if it was inlined at the call site, then conclude that curThing would actually be non-nullable.
All of this is theoretically doable; but doing this would drastically increase the compilation time and the complexity of the compiler.
The workarounds available now, is using the bang ! operator to assert that the value is not empty, e.g.:
if(curThing){console.info(curThing!.x);// trust me curThing is not null}
use a cast for null:
functiontest(lines: string[]){varcurThing: {x: number}=<any>null;// i am sure it will be set to a non-null value later on lines.forEach(line=>{curThing={x: 1};});if(curThing){console.info(curThing.x);}}
Activity
mhegazy commentedon Aug 30, 2016
The issue is the compiler has no idea when the function will be called. in this case
lines.forEachwill call the lambda immediately. but if this wassetTimeoutit would not. so doing this for the general case is not possible. all it knows is that the last definite assignment forcurThingisnull, and theifstatement filters that out, leaving the typenever.mhegazy commentedon Aug 30, 2016
i suppose this is a regression from #8548
zenmumbler commentedon Sep 8, 2016
Not sure if this could be applied to TS, but Swift supports the
@noescapemodifier on functions passed as parameters that indicates that the passed function will execute before the called function exits. E.g. (Swift code follows)The compiler then checks that
noescapefunction parameters are not passed to non-noescapefunction parameters, etc. In Swift this is mainly for some optimisations (and perhaps static analysis) but in TS this could provide static checking and thus accurate determination if a function param will or may not execute within the call context of the owning function.Downsides: all of the standard library methods would have to be annotated and for it to work for me as well I would have to annotate it as well, but the only other option is cross-module SA (which is also impossible for any native functions)
zenmumbler commentedon Sep 9, 2016
To follow up on mhegazy's first comment: Indeed, the compiler right now has no way of knowing what will happen, but it pretends it knows anyway by assuming the lambda will not be called directly. If it truly cannot know, it has to leave the type alone and have the programmer do any manual casts, etc.
mhegazy commentedon Sep 13, 2016
After looking into it more, and catching up on changes in the last month or so, the reversal of the behavior in #8548 is intentional. see #10118 for more details.
My comment earlier still holds. the compiler does not check lambdas, or any calls for possible side effects. it can not make an assumption about the lambda being called immediately or not. Consider the same example with
setTimeoutin place forlines.forEach. Adding an annotation on the function parameter declaration seems like a right direction, but it does not provide the full solution either. consider:To get this right, first every call site has to be investigated for possible side effects, the annotation on the function declaration should be consulted, and the control flow analysis should be done on the function body as if it was inlined at the call site, then conclude that
curThingwould actually be non-nullable.All of this is theoretically doable; but doing this would drastically increase the compilation time and the complexity of the compiler.
The workarounds available now, is using the bang
!operator to assert that the value is not empty, e.g.:use a cast for
null:zenmumbler commentedon Sep 13, 2016
Fair enough. I realised this would be a tricky one but figured I'd put it up. Thanks for giving it consideration.