Flow-sensitive typing

Meta: short page, but pays for itself — the alternative is writing ! casts everywhere. Make sure the examples include both narrowing inside a branch and narrowing after a guard clause.

Null narrowing

After a guard

if (x == null) { return "no value" }
# x is now T! here
print(x.length)

Inside a branch

if (x != null) {
  # x is T! here
  print("got " + x)
}

Else branch

if (x == null) { ... } else {
  # x is T! here
}

Loop conditions

for (x != null) {
  # x is T! in the body
  x = x.next
}

Diverging constructs are narrowing-aware

Type narrowing via case

case (animal) {
  c: Cat => c.purr     # c is Cat!
  d: Dog => d.bark     # d is Dog!
}

Conditional result inference (related)

Compound conditions

if (x == null or y == null) { raise "missing" }
# both x and y are T! after the diverging guard
if (maybe != null and other != null) {
  maybe + other   # both T! here
}

Limitations

See also Errors: try, catch, raise (raise/try/catch divergence) and Control flow (guards, loops, case).

Meta: field-narrowing and the and-guard non-narrowing are the two most surprising gaps in practice. Both are now documented with the re-bind-to-a-local workaround.