-
Notifications
You must be signed in to change notification settings - Fork 226
Description
The NNBD spec pull request (#293) contains the following text:
When static checking is done in a migrated library, types which are imported
from unmigrated libraries are seen as legacy types. However, for the purposes
of type inference in migrated libraries, types imported from unmigrated
libraries shall be treated as non-nullable. As a result, legacy types will
never appear as type annotations in migrated libraries, nor will they appear in
reified positions.
I'm trying to understand how to interpret this paragraph in light of the fact that type inference and static checking are interleaved.
For example:
// In a legacy library
List<int> f() { ... }
// In a migrated library
List<T> g<T>(List<T> l) { ... }
main() {
f()..add(null); // (A)
g(f()..add(null))..add(null); // (B)
g(f()..add(null))..add(0); // (C)
}
To analyze (A), the analyzer goes through the following steps:
(1) Figure out the type of f()
(List<int*>*
)
(2) Figure out the argument type expected by f()..add
(int*
)
(3) Check whether Null
is assignable to that type (it is, so there is no error at (A))
To analyze (B), the steps are as above, and then:
(4) Perform type inference to determine the implicit type argument passed to g
(5) Figure out the argument type expected by g(f()..add(null)).add
(6) Check whether Null
is assignable to that type
Based on the spec text quoted above, I believe (4) is supposed to infer a type argument of int
(as opposed to int*
). Meaning that (5) determines that g(f()..add(null)).add
requires an int
, so the assignability check at (6) will fail, and there will be an error at (B).
But how does this happen? The spec text says "for the purposes of type inference in migrated libraries, types imported from unmigrated libraries shall be treated as non-nullable", which seems to imply that back in step (1), we should have gotten a type of List<int>
. But then step (3) would have found an error at (A), which I don't think we want.
I'm wondering if the rule we really want is something like this:
When static checking is done in a migrated library, types which are imported
from unmigrated libraries are seen as legacy types. However, when type inference
is performed in migrated libraries, types are converted to non-nullable types
(or potentially non-nullable types, in the case of type parameters) at
the point where the inference occurs. As a result, legacy types will
never appear as type annotations in migrated libraries, nor will they appear in
reified positions.
I believe this would imply that all the calls to f()..add(null)
in the example above are ok, and all the g
s are type inferred as g<int>
; therefore (A) and (C) are ok, but there's an error at (B).