dart-lang / language

Design of the Dart language
Other
2.65k stars 203 forks source link

Flow analysis feature request: on join, promote to LUB #1845

Open stereotype441 opened 3 years ago

stereotype441 commented 3 years ago

(This suggestion has come up a few times, most recently in https://github.com/dart-lang/sdk/issues/47105.)

Flow analysis currently maintains a list of promoted types for each variable called the variables promotion chain. When two control flow paths are joined, the promotion chains for each variable are intersected (meaning we retain any promotions made on both control paths, but exclude any promotions made on only one path or another). When types get excluded by this algorithm, we don't make any effort to replace them with a least upper bound.

So for example, this doesn't work:

f(Object x) {
  if (x is int) {
    // ...
  } else if (x is double) {
    // ...
  } else {
    return;
  }
  print(x + 1); // ERROR: x has type `Object`, which doesn't support operator `+`
}

In principle, flow analysis could be smarter and see that after the if statement, since x has either type int or double, it must have type num, in which case x + 1 would be allowed.

I think a lot of the reason this sort of thing surprises people is that there are other places where the language does use least upper bound, for example:

f(int? x, double y) {
  var z = x ?? y;
  print(z + 1); // OK: z has type `num`
}
lrhn commented 3 years ago

The usual argument against using "LUB" is that the type system might cause unexpected values to surface. The classical example was LUB computations which ended up with the private _EfficentLengthIterable<T> class instead of the least upper public bound of Iterable<T> that the user expected. If you make a list of that, you can't add other Iterable<T>s.

Also, promoting x to num here, even though num is not a "type of intertest", makes it non-obvious why

if (x is int) {
  ...
} else {
  x = 2.0;
}

will not promote to num as well (It doesn't because the x = 2.0; doesn't promote, because double is not a type-of-interest). It can make it harder to explain the limits of promotion.

I would totally accept promoting if one of the joined types is a supertype of the other, because then the result is a type-of-interest. The usability risks of exposing LUB types worries me quite a lot.

(I'd be interested in considering a "partial promotion" variant where a partially promoted variable allows member invocations at the promoted type, and assignment to the promoted type, but otherwise its own static type is not affected, so, say, creating a list containing it won't cause the list to use the partially promoted type as element type.)