Ticket Created
over 2 years ago

WERETECH-12192

Better way to remove Null from types

I've been playing with the typechecker, and generally I like the results. One thing that seems problematic though is removing null from a type. This comes up a lot, especially with dictionaries.

I can explicitly type a dictionary, but when accessing it, all the types get Null added. eg

function bar(arg as String) as Void {}
function foo(arg as {:a as String, :b as Number}) as Void {
    bar(arg[:a]);
}

This fails to compile, because arg may not have an :a field at all (except that often, the programmer knows that it does). So I have to write "bar(arg[:a] as String)" (or do a runtime check for null, when I know it can't happen).

The problem now is that "as type" is too heavy handed. I could also say "bar(arg[:b] as String);" and the type checker would be happy - even though prior to adding the as clause, it knew that the expression was Number or Null (and at runtime, bar is going to get a Number, and will probably fail). So ultimately, typing my Dictionary got me no type safety whatsoever; I could just have left it untyped, and written "as String" or "as Number" everywhere.

Ideally, "x as Type" should check that x really could be Type, and reject the program otherwise. So "arg[:a] as String" is fine, but "arg[:b] as String" is an error. That might break existing code - although presumably such code would break at runtime anyway. And if you really wanted to compile such code you could always write "(arg[:b] as Object) as String" (String or Null *could* be an Object, so the first as clause is accepted; then Object *could* be a String, so that one is fine too).

If changing that behavior is too risky, an alternative would be a NonNull type, so "arg[:a] as NonNull" would deduce type String, and "arg[:b] as NonNull" would deduce type Number.