The goal is to support precise type inference for the indexing operator a[i]
.
For instance, if a
turns out to have type Array<int>
, then a[0]
should have type int | undefined
; but if a[0]
turns out to have a more precise type such as [int, bool, nat]
, then a[0]
should have type int
.
(Note: the ... | undefined
return type for imprecise indexing type inference is in the process of being implemented in mahzoun99#1.)
The way to do this is as follows.
We should add some meta information in type variables, in addition to upper and lower bounds:
-
A list of "indexed in" info (A, R)
, which specifies what type the type variable is used to index into A
and that the type resulting from this operation is R
.
-
A list of "indexed by" info (T, R)
, which specifies what type the type variable is indexed into by some concrete T
and that the type resulting from this operation is R
.
For example, if a: ?A
and i: ?I
, then typing a[i]
will result in creating a new type variable ?R
and adding (?A, ?R)
to the "indexed in" info of type variable ?I
.
Then, the job of type inference is simply making sure that all ?I::(?A, ?R)
and ?A::(?I, ?R)
info remain consistent, by propagating concrete bounds that are added to the relevant type variables.
More specifically: when constraining a type variable with a new concrete lower bound B
in the rec
function of ConstraintSolver.scala
, we should check:
-
its "indexed in" info. For each such (?A, ?R)
, we should add (B, ?R)
to it, and while doing so we should make sure that this (B, ?R)
is consistent with the existing lower bounds in ?A
;
-
its "indexed by" info. For each such (T, ?R)
, we should make sure that B
is consistent with it.
Example: if add a lower bound 1 | 2
to type variable I0
which has "indexed in" info of (?A0, ?R0)
, then I add (1 | 2, ?R0)
as "indexed by" into in ?A0
. Then, if [S, T, U]
is a lower bound on?A0
, I should look at what the index 1 | 2
yields from that tuple, in this case T
and U
, and add these as lower bounds in R0
.
There is one subtlety to deal with, though: if the (?A, ?R)
info we want to register into some ?I
has a higher level than ?A
, we need to extrude its components to the correct level, using the extrude
method.