Skip to content

Need clarification on type inference in migrated libraries when importing legacy libraries #348

@stereotype441

Description

@stereotype441

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 gs are type inferred as g<int>; therefore (A) and (C) are ok, but there's an error at (B).

Metadata

Metadata

Assignees

No one assigned

    Labels

    nnbdNNBD related issues

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions