Statically typed languages like Java use instanceof checks to determine the type of an object at runtime. After a successful check, a type cast needs to be done explicitly in most of those languages. In this post we present how N4JS introduced type guards to perform these type casts implicitly.
|
No error due to implicit cast in successful instanceof type guard |
The example above shows that strict type rules on the any instance a causes errors to show up when accessing the unknown property pX. However, after asserting that a is an instance of X, the property pX can be accessed without errors. A separate type cast is unnecessary, since type inference now also considers instanceof type guard information.
|
Hover information on variable access of a shows the inferred type |
The resulting type is the intersection type of the original type (which is here any) and of all type guards that must hold on a specific variable access (which is here only type X). Keeping the original types any or Object is not necessary and could be optimised later. In case the original type is different, it is necessary to include it in the resulting intersection type. The reason is that the type guard could check for an interface only. If so, property accesses to properties of the original types would cause errors.
|
Re-definition of a type guarded variable |
Two distinct differences between type guards and type declarations are (1) their data flow nature and (2) their read-only effects. Firstly, when redefining (in the sense of the data flow) a variable, the type guard information gets lost. Consequently, subsequent accesses to the variable will no longer benefit from the type guard, since the type guard was invalidated by the re-definition. Secondly, only the original type information is considered for a redefinition. That means that the type guard does not change the expected type and, hence, does not limit the set of types that can be assigned to a type guarded variable.
|
Further examples for instanceof type guards in N4JS |
Data flow analysis is essential for type guards and has been presented in a
previous post. Based upon this information, type information for each variable access is computed. Since also complicated data flows are handled correctly, such as in
for loops or
short circuit evaluation, type guard information is already available in composed condition expressions (see function
f3 and
f5 above). Aside from being able to nest
instanceof type guards (see function
f4 above), they also can be used as a filter at the beginning of a function (see function
f6 above) or inside a loop: Negating a type guard and then exiting the function or block leaves helpful valid type guard information on all the remaining control flow paths.
by Marcus Mews