f071f7ce64
Change-Id: I23c7e77ac64ce2cad288f42505ccf07257f00abc Reviewed-on: https://dart-review.googlesource.com/71861 Reviewed-by: Lasse R.H. Nielsen <lrn@google.com>
334 lines
11 KiB
Markdown
334 lines
11 KiB
Markdown
# Dart 2.X super mixin inference proposal
|
|
|
|
**Owner**: leafp@google.com
|
|
|
|
**Status**: This is now background material.
|
|
|
|
The current version of this document now resides
|
|
[here](https://github.com/dart-lang/language/blob/master/working/0006.%20Super-invocations%20in%20mixins/0007.%20Mixin%20declarations/mixin-inference.md).
|
|
|
|
This is intended to define a prototype approach to supporting inference of type
|
|
arguments in super-mixin applications. This is not an official part of Dart
|
|
2, but is an experimental feature hidden under flags. When super-mixins are
|
|
specified and landed, this feature or some variant of it may be included.
|
|
|
|
## Syntactic conventions
|
|
|
|
The meta-variables `X`, `Y`, and `Z` range over type variables.
|
|
|
|
The meta-variables `T`, and `U` range over types.
|
|
|
|
The meta-variables `M`, `I`, and `S` range over interface types (that is,
|
|
classes instantiated with zero or more type arguments).
|
|
|
|
The meta-variable `C` ranges over classes.
|
|
|
|
The meta-variable `B` ranges over types used as bounds for type variables.
|
|
|
|
Throughout this document, I assume that bound type variables have been suitably
|
|
renamed to avoid accidental capture.
|
|
|
|
## Mixin inference
|
|
|
|
In Dart 2 syntax, a class definition may also have an interpretation as mixin
|
|
under certain restrictions defined elsewhere.
|
|
|
|
### Mixins and superclass constraints
|
|
|
|
Given a class of the form:
|
|
|
|
```
|
|
class C<X0, ..., Xn> extends S with M0, ..., Mj implements I0, ..., Ik { ...}
|
|
```
|
|
|
|
we say that the superclass of `C<X0, ..., Xn>` is `S with M0, ..., Mj`.
|
|
|
|
When interpreted as a mixin, we say that the super class constraints for `C<X0,
|
|
..., Xn>` are `S`, `M0`, ..., `Mj`.
|
|
|
|
Given a mixin application class of the form:
|
|
|
|
```
|
|
class C<X0, ..., Xn> = S with M0, ..., Mj implements I0, ..., Ik;
|
|
```
|
|
|
|
for we say that the superclass of `C<X0, ..., Xn>` is `S with M0, ..., Mj-1`.
|
|
|
|
When interpreted as a mixin, we say that the super class constraints for `C<X0,
|
|
..., Xn>` are `S`, `M0`, ..., `Mj-1`.
|
|
|
|
#### Discussion
|
|
|
|
A class, interpreted as a mixin, is interpreted as a function from its
|
|
superclass to its own type. That is, the actual class onto which the mixin is
|
|
applied must be a subtype of the superclass constraint. When the superclass is
|
|
itself a mixin, we interpret each component of the mixin as a separate
|
|
constraint, each of which the actual class onto which the mixin is applied must
|
|
be a subtype of.
|
|
|
|
### Mixin type inference
|
|
|
|
Given a class of the form:
|
|
|
|
```
|
|
class C<T0, ..., Tn> extends S with M0, ..., Mj implements I0, ..., Ik { ...}
|
|
```
|
|
|
|
or of the form
|
|
|
|
```
|
|
class C<T0, ..., Tn> = S with M0, ..., Mj implements I0, ..., Ik;
|
|
```
|
|
|
|
we say that the superclass of `M0` is `S`, the superclass of `M1` is `S with
|
|
M0`, etc.
|
|
|
|
For a class with one or more mixins of either of the forms above we allow any
|
|
or all of the `M0`, ..., `Mj` to have their type arguments inferred. That is,
|
|
if any of the `M0`, ..., `Mj` are references to generic classes with no type
|
|
arguments provided, the missing type arguments will be attempted to be
|
|
reconstructed in accordance with this specification.
|
|
|
|
Type inference for a class is done from the innermost mixin application out.
|
|
That is, the type arguments for `M0` (if any) are inferred before type arguments
|
|
for `M1`, and so on. Each successive inference is done with respect to the
|
|
inferred version of its superclass: so if type arguments `T0, ..., Tn` are
|
|
inferred for `M0`, `M1` is inferred with respect to `M0<T0, ..., Tn`>, etc.
|
|
|
|
Type inference for the class hierarchy is done from top down. That is, in the
|
|
example classes above, all mixin inference for the definitions of `S`, the `Mi`,
|
|
and the `Ii` is done before mixin inference for `C` is done.
|
|
|
|
Let be `M` be a mixin applied to a superclass `S` where `M` is a reference to a
|
|
generic class with no type arguments provided, and `S` is some class (possibly
|
|
generic); and where `M` is defined with type parameters `X0, ..., Xj`. Let `S0,
|
|
..., Sn` be the superclass constraints for `M` as defined above. Note that the
|
|
`Xi` may appear free in the `Si`. Let `C0, ..., Cn` be the corresponding
|
|
classes for the `Si`: that is, each `Si` is of the form `Ci<T0, ..., Tk>` for
|
|
some `k >= 0`. Note that by assumption, the `Xi` are disjoint from any type
|
|
parameters in the enclosing scope, since we have assumed that type variables are
|
|
suitably renamed to avoid capture.
|
|
|
|
For each `Si`, find the unique `Ui` in the super-interface graph of `S` such
|
|
that the class of `Ui` is `Ci`. Note that if there is not such a `Ui`, then
|
|
there is no instantiation of `M` that will allow its superclass constraint to be
|
|
satisfied, and if there is such a `Ui` but it is not unique, then the superclass
|
|
hierarchy is ill-formed. In either case it is an error.
|
|
|
|
Let *SLN* be the smallest set of pairs of type variables and types `(Z0, T0),
|
|
..., (Zl, Tl)` with type variables drawn from `X0, ..., Xj` such that `{T0/Z0,
|
|
..., Tl/Zl}Si == Ui`. That is, replacing each free type variable in the `Si`
|
|
with its corresponding type in *SLN* makes `Si` and `Ui` the same. If no such
|
|
set exists, then it is an error. Note that for well-formed programs, the only
|
|
free type variables in the `Ti` must by definition be drawn from the type
|
|
parameters to the enclosing class of the mixin application. Hence it follows
|
|
both that the `Ti` are well-formed types in the scope of the mixin application,
|
|
and that the the `Xi` do not occur free in the `Ti` since we have assumed that
|
|
classes are suitably renamed to avoid capture.
|
|
|
|
Let `[X0 extends B0, ..., Xj extends Bj]` be a set of type variable bounds such
|
|
that if `(Xi, Ti)` is in *SLN* then `Bi` is `Ti` and otherwise `Bi` is the
|
|
declared bound for `Xi` in the definition of `M`.
|
|
|
|
Let `[X0 -> T0', ..., Xj -> Tj']` be the default bounds for this set of type
|
|
variable bounds as defined in the "instantiate to bounds" specification.
|
|
|
|
The inferred type arguments for `M` are then `<T0', ..., Ti'>`.
|
|
|
|
It is an error if the inferred type arguments are not a valid instantiation of
|
|
`M` (that is, if they do not satisfy the bounds of `M`).
|
|
|
|
#### Discussion
|
|
|
|
For each superclass constraint, there must be a matching interface in the
|
|
super-interface hierarchy of the actual superclass. So for each superclass
|
|
constraint of the form `I0<U0, ..., Uk>` there must be some `I0<U0', ..., Uk'>`
|
|
in the super-interface hierarchy of the actual superclass `S` (if not, there is
|
|
an error in the super class hierarchy, or in the mixin application). Note that
|
|
the `Ui` may have free occurrences of the type variables for which we are
|
|
solving, but the `Ui'` may not. A simple equality traversal comparing `Ui` and
|
|
`Ui'` will find all of the type variables which must be equated in order to make
|
|
the two interfaces equal. Once a type variable is solved via such a traversal,
|
|
subsequent occurrences must be constrained to an equal type, otherwise there is
|
|
no solution. Type variables which do not appear in any of the superclass
|
|
constraints are not constrained by the mixin application. Some or all of the
|
|
type variables may be unconstrained in this manner. We choose a solution for
|
|
these type variables using the instantiate to bounds algorithm. We construct a
|
|
synthetic set of bounds using the chosen constraints for the constrained
|
|
variables, and use instantiate to bounds to produce the remaining results.
|
|
Since instantiate to bounds may produce a super-bounded type, we must check that
|
|
the result satisfies the bounds (or else define a version of instantiate to
|
|
bounds which issues an error rather than approximates).
|
|
|
|
Note that we do not take into account information from later mixins when solving
|
|
the constraints: nor from implemented interfaces. The approach specified here
|
|
may therefore fail to find valid instantiations. We may consider relaxing this
|
|
in the future. Note however that fully using information from other positions
|
|
will result in equality constraint queries in which type variables being solved
|
|
for appear on both sides of the query, hence leading to a full unification
|
|
problem.
|
|
|
|
The approach specified here is a simplification of the subtype matching
|
|
algorithm used in expression level type inference. In the case that there is no
|
|
solution to the declarative specification above, subtype matching may still find
|
|
a solution which does not satisfy the property that no generic interface may
|
|
occur twice in the class hierarchy with different type arguments. A valid
|
|
implementation of the approach specified here should be to run the subtype
|
|
matching algorithm, and then to subsequently check that no generic interface has
|
|
been introduced at incompatible type.
|
|
|
|
## Tests and illustrative examples.
|
|
|
|
Some examples illustrating key points.
|
|
|
|
### Inference proceeds outward
|
|
|
|
```
|
|
class I<X> {}
|
|
|
|
class M0<T> extends I<T> {}
|
|
|
|
class M1<T> extends I<T> {}
|
|
|
|
// M1 is inferred as M1<int>
|
|
class A extends M0<int> with M1 {}
|
|
```
|
|
|
|
```
|
|
class I<X> {}
|
|
|
|
class M0<T> extends I<T> {}
|
|
|
|
class M1<T> extends I<T> {}
|
|
|
|
class M2<T> extends I<T> {}
|
|
|
|
// M1 is inferred as M1<int>
|
|
// M2 is inferred as M1<int>
|
|
class A extends M0<int> with M1, M2 {}
|
|
```
|
|
|
|
```
|
|
class I<X> {}
|
|
|
|
class M0<T> extends Object implements I<T> {}
|
|
|
|
class M1<T> extends I<T> {}
|
|
|
|
// M0 is inferred as M0<dynamic>
|
|
// Error since class hierarchy is inconsistent
|
|
class A extends Object with M0, M1<int> {}
|
|
```
|
|
|
|
```
|
|
class I<X> {}
|
|
|
|
class M0<T> extends Object implements I<T> {}
|
|
|
|
class M1<T> extends I<T> {}
|
|
|
|
// M0 is inferred as M0<dynamic> (unconstrained)
|
|
// M1 is inferred as M1<dynamic> (constrained by inferred argument to M0)
|
|
// Error since class hierarchy is inconsistent
|
|
class A extends Object with M0, M1 implements I<int> {}
|
|
```
|
|
|
|
### Multiple superclass constraints
|
|
```
|
|
class I<X> {}
|
|
|
|
class J<X> {}
|
|
|
|
class M0<X, Y> extends I<X> with J<Y> {}
|
|
|
|
class M1 implements I<int> {}
|
|
class M2 extends M1 implements J<double> {}
|
|
|
|
// M0 is inferred as M0<int, double>
|
|
class A extends M2 with M0 {}
|
|
```
|
|
|
|
### Instantiate to bounds
|
|
```
|
|
class I<X> {}
|
|
|
|
class M0<X, Y extends String> extends I<X> {}
|
|
|
|
class M1 implements I<int> {}
|
|
|
|
// M0 is inferred as M0<int, String>
|
|
class A extends M1 with M0 {}
|
|
```
|
|
|
|
```
|
|
class I<X> {}
|
|
|
|
class M0<X, Y extends X> extends I<X> {}
|
|
|
|
class M1 implements I<int> {}
|
|
|
|
// M0 is inferred as M0<int, int>
|
|
class A extends M1 with M0 {}
|
|
```
|
|
|
|
```
|
|
class I<X> {}
|
|
|
|
class M0<X, Y extends Comparable<Y>> extends I<X> {}
|
|
|
|
class M1 implements I<int> {}
|
|
|
|
// M0 is inferred as M0<int, Comparable<dynamic>>
|
|
// Error since super-bounded type not allowed
|
|
class A extends M1 with M0 {}
|
|
```
|
|
|
|
### Non-trivial constraints
|
|
|
|
```
|
|
class I<X> {}
|
|
|
|
class M0<T> extends I<List<T>> {}
|
|
|
|
class M1<T> extends I<List<T>> {}
|
|
|
|
class M2<T> extends M1<Map<T, T>> {}
|
|
|
|
// M0 is inferred as M0<Map<int, int>>
|
|
class A extends M2<int> with M0 {}
|
|
```
|
|
|
|
### Unification
|
|
These examples are not inferred given the strategy in this proposal, and suggest
|
|
some tricky cases to consider if we consider a broader approach.
|
|
|
|
|
|
```
|
|
class I<X, Y> {}
|
|
|
|
class M0<T> implements I<T, int> {}
|
|
|
|
class M1<T> implements I<String, T> {}
|
|
|
|
// M0 inferred as M0<String>
|
|
// M1 inferred as M1<int>
|
|
class A extends Object with M0, M1 {}
|
|
```
|
|
|
|
|
|
```
|
|
class I<X, Y> {}
|
|
|
|
class M0<T> implements I<T, List<T>> {}
|
|
|
|
class M1<T> implements I<List<T>, T> {}
|
|
|
|
// No solution, even with unification, since solution
|
|
// requires that I<List<U0>, U0> == I<U1, List<U1>>
|
|
// for some U0, U1, and hence that:
|
|
// U0 = List<U1>
|
|
// U1 = List<U0>
|
|
// which has no finite solution
|
|
class A extends Object with M0, M1 {}
|
|
```
|