Change-Id: I23c7e77ac64ce2cad288f42505ccf07257f00abc Reviewed-on: https://dart-review.googlesource.com/71861 Reviewed-by: Lasse R.H. Nielsen <lrn@google.com>
18 KiB
Dart 2.0 Static and Runtime Subtyping
Status: Implemented.
This is intended to define the core of the Dart 2.0 static and runtime subtyping relation.
Types
The syntactic set of types used in this draft are a slight simplification of full Dart types.
The meta-variables X, Y, and Z range over type variables.
The meta-variables T, S, U, and V range over types.
The meta-variable C ranges over classes.
The meta-variable B ranges over types used as bounds for type variables.
As a general rule, indices up to k are used for type parameters and type
arguments, n for required value parameters, and m for all value parameters.
The set of types under consideration are as follows:
- Type variables
X - Promoted type variables
X & TNote: static only ObjectdynamicvoidNullFunctionFuture<T>FutureOr<T>- Interface types
C,C<T0, ..., Tk> - Function types
U Function<X0 extends B0, ...., Xk extends Bk>(T0 x0, ...., Tn xn, [Tn+1 xn+1, ..., Tm xm])U Function<X0 extends B0, ...., Xk extends Bk>(T0 x0, ...., Tn xn, {Tn+1 xn+1, ..., Tm xm})
We leave the set of interface types unspecified, but assume a class hierarchy
which provides a mapping from interfaces types T to the set of direct
super-interfaces of T induced by the superclass declaration, implemented
interfaces, and mixins of the class of T. Among other well-formedness
constraints, the edges induced by this mapping must form a directed acyclic
graph rooted at Object.
The types Object, dynamic and void are all referred to as top types, and
are considered equivalent as types (including when they appear as sub-components
of other types). They exist as distinct names only to support distinct errors
and warnings (or absence thereof).
The type X & T represents the result of a type promotion operation on a
variable. In certain circumstances (defined elsewhere) a variable x of type
X that is tested against the type T (e.g. x is T) will have its type
replaced with the more specific type X & T, indicating that while it is known
to have type X, it is also known to have the more specific type T. Promoted
type variables only occur statically (never at runtime).
Given the current promotion semantics the following properties are also true:
- If
Xhas boundBthen for any typeX & T,T <: Bwill be true. - Promoted type variable types will only appear as top level types: that is, they can never appear as sub-components of other types, in bounds, or as part of other promoted type variables.
Notation
We use S[T0/Y0, ..., Tk/Yk] for the result of performing a simultaneous
capture-avoiding substitution of types T0, ..., Tk for the type variables
Y0, ..., Yk in the type S.
Type equality
We say that a type T0 is equal to another type T1 (written T0 === T1) if
the two types are structurally equal up to renaming of bound type variables,
and equating all top types.
TODO: make these rules explicit.
Algorithmic subtyping
By convention the following rules are intended to be applied in top down order, with exactly one rule syntactically applying. That is, rules are written in the form:
Syntactic criteria.
- Additional condition 1
- Additional or alternative condition 2
and it is the case that if a subtyping query matches the syntactic criteria for a rule (but not the syntactic criteria for any rule preceeding it), then the subtyping query holds iff the listed additional conditions hold.
This makes the rules algorithmic, because they correspond in an obvious manner to an algorithm with an acceptable time complexity, and it makes them syntax directed because the overall structure of the algorithm corresponds to specific syntactic shapes. We will use the word algorithmic to refer to this property.
The runtime subtyping rules can be derived by eliminating all clauses dealing with promoted type variables.
Rules
We say that a type T0 is a subtype of a type T1 (written T0 <: T1) when:
-
Reflexivity:
T0andT1are the same type.- Note that this check is necessary as the base case for primitive types, and type variables but not for composite types. In particular, algorithmically a structural equality check is admissible, but not required here. Pragmatically, non-constant time identity checks here are counter-productive
-
Right Top:
T1is a top type (i.e.Object,dynamic, orvoid). -
Left Bottom:
T0isNull -
Left FutureOr:
T0isFutureOr<S0>- and
Future<S0> <: T1 - and
S0 <: T1
- and
-
Type Variable Reflexivity 1:
T0is a type variableX0or a promoted type variablesX0 & S0andT1isX0.- Note that this rule is admissible, and can be safely elided if desired
-
Type Variable Reflexivity 2: Promoted
T0is a type variableX0or a promoted type variablesX0 & S0andT1isX0 & S1- and
T0 <: S1. - Note that this rule is admissible, and can be safely elided if desired
- and
-
Right Promoted Variable:
T1is a promoted type variableX1 & S1- and
T0 <: X1 - and
T0 <: S1
- and
-
Right FutureOr:
T1isFutureOr<S1>and- either
T0 <: Future<S1> - or
T0 <: S1 - or
T0isX0andX0has boundS0andS0 <: T1 - or
T0isX0 & S0andS0 <: T1
- either
-
Left Promoted Variable:
T0is a promoted type variableX0 & S0- and
S0 <: T1
- and
-
Left Type Variable Bound:
T0is a type variableX0with boundB0- and
B0 <: T1
- and
-
Function Type/Function:
T0is a function type andT1isFunction -
Interface Compositionality:
T0is an interface typeC0<S0, ..., Sk>andT1isC0<U0, ..., Uk>- and each
Si <: Ui
- and each
-
Super-Interface:
T0is an interface type with super-interfacesS0,...Sn- and
Si <: T1for somei
- and
-
Positional Function Types:
T0isU0 Function<X0 extends B00, ..., Xk extends B0k>(V0 x0, ..., Vn xn, [Vn+1 xn+1, ..., Vm xm])- and
T1isU1 Function<Y0 extends B10, ..., Yk extends B1k>(S0 y0, ..., Sp yp, [Sp+1 yp+1, ..., Sq yq]) - and
p >= n - and
m >= q - and
Si[Z0/Y0, ..., Zk/Yk] <: Vi[Z0/X0, ..., Zk/Xk]foriin0...q - and
U0[Z0/X0, ..., Zk/Xk] <: U1[Z0/Y0, ..., Zk/Yk] - and
B0i[Z0/X0, ..., Zk/Xk] === B1i[Z0/Y0, ..., Zk/Yk]foriin0...k - where the
Ziare fresh type variables with boundsB0i[Z0/X0, ..., Zk/Xk]
- and
-
Named Function Types:
T0isU0 Function<X0 extends B00, ..., Xk extends B0k>(V0 x0, ..., Vn xn, {Vn+1 xn+1, ..., Vm xm})- and
T1isU1 Function<Y0 extends B10, ..., Yk extends B1k>(S0 y0, ..., Sn yn, {Sn+1 yn+1, ..., Sq yq}) - and
{yn+1, ... , yq}subsetof{xn+1, ... , xm} - and
Si[Z0/Y0, ..., Zk/Yk] <: Vi[Z0/X0, ..., Zk/Xk]foriin0...n - and
Si[Z0/Y0, ..., Zk/Yk] <: Tj[Z0/X0, ..., Zk/Xk]foriinn+1...q,yj = xi - and
U0[Z0/X0, ..., Zk/Xk] <: U1[Z0/Y0, ..., Zk/Yk] - and
B0i[Z0/X0, ..., Zk/Xk] === B1i[Z0/Y0, ..., Zk/Yk]foriin0...k - where the
Ziare fresh type variables with boundsB0i[Z0/X0, ..., Zk/Xk]
- and
Note: the requirement that Zi are fresh is as usual strictly a requirement
that the choice of common variable names avoid capture. It is valid to choose
the Xi or the Yi for Zi so long as capture is avoided
Derivation of algorithmic rules
This section sketches out the derivation of the algorithmic rules from the
interpretation of FutureOr as a union type, and promoted type bounds as
intersection types, based on standard rules for such types that do not satisfy
the requirements for being algorithmic.
Non-algorithmic rules
The non-algorithmic rules that we derive from first principles of union and intersection types are as follows:
Left union introduction:
FutureOr<S> <: TifFuture<S> <: TandS <: T
Right union introduction:
S <: FutureOr<T>ifS <: Future<T>orS <: T
Left intersection introduction:
X & S <: TifX <: TorS <: T
Right intersection introduction:
S <: X & TifS <: XandS <: T
The only remaining non-algorithmic rule is the variable bounds rule:
Variable bounds:
X <: TifX extends BandB <: T
All other rules are algorithmic.
Note: We believe that bounds can be treated simply as uses of intersections, which could simplify this presentation.
Preliminaries
Lemma 1: If there is any derivation of FutureOr<S> <: T, then there is a
derivation ending in a use of left union introduction.
Proof. By induction on derivations. Consider a derivation of FutureOr<S> <: T.
If the last rule applied is:
-
Top type rules are trivial.
-
Null, Function and interface rules can't apply.
-
Left union introduction rule is immediate.
-
Right union introduction. Then
Tis of the formFutureOr<T0>, and either- we have a sub-derivation of
FutureOr<S> <: Future<T0>- by induction we therefore have a derivation ending in left union
introduction, so by inversion we have:
- a derivation of
Future<S> <: Future<T0>, and so by right union introduction we haveFuture<S> <: FutureOr<T0> - a derivation of
S <: Future<T0>, and so by right union introduction we haveS <: FutureOr<T0>
- a derivation of
- by left union introduction, we have
FutureOr<S> <: FutureOr<T0> - QED
- by induction we therefore have a derivation ending in left union
introduction, so by inversion we have:
- we have a sub-derivation of
FutureOr<S> <: T0- by induction we therefore have a derivation ending in left union
introduction, so by inversion we have:
- a derivation of
Future<S> <: T0, and so by right union introduction we haveFuture<S> <: FutureOr<T0> - a derivation of
S <: T0, and so by right union introduction we haveS <: FutureOr<T0>
- a derivation of
- by left union introduction, we have
FutureOr<S> <: FutureOr<T0> - QED
- by induction we therefore have a derivation ending in left union
introduction, so by inversion we have:
- we have a sub-derivation of
-
Right intersection introduction. Then
Tis of the formX & T0, and- we have sub-derivations
FutureOr<S> <: XandFutureOr<S> <: T0 - By induction, we can get derivations of the above ending in left union
introduction, so by inversion we have derivations of:
Future<S> <: X,S <: X,Future<S> <: T0,S <: T0- so we have derivations of
S <: X,S <: T0, so by right intersection introduction we haveS <: X & T0
- so we have derivations of
Future<S> <: X,Future<S> <: T0, so by right intersection introduction we haveFuture<S> <: X & T0
- so we have derivations of
- so by left union introduction, we have a derivation of
FutureOr<S> <: X & T0 - QED
- we have sub-derivations
Note: The reverse is not true. Counter-example:
Given arbitrary B <: A, suppose we wish to show that (X extends FutureOr<B>) <: FutureOr<A>. If we apply right union introduction first, we must show
either:
X <: Future<A>- only variable bounds rule applies, so we must show
FutureOr<B> <: Future<A>- Only left union introduction applies, so we must show both of:
Future<B> <: Future<A>(yes)B <: Future<A>(no)
X <: A- only variable bounds rule applies, so we must show that
FutureOr<B> <: A- Only left union introduction applies, so we must show both of:
Future<B> <: Future<A>(no)B <: Future<A>(yes)
On the other hand, the derivation via first applying the variable bounds rule is trivial.
Note though that we can also not simply always apply the variable bounds rule first. Counter-example:
Given X extends Object, it is trivial to derive X <: FutureOr<X> via the
right union introduction rule. But applying the variable bounds rule doesn't
work.
Lemma 2: If there is any derivation of S <: X & T, then there is
derivation ending in a use of right intersection introduction.
Proof. By induction on derivations. Consider a derivation D of S <: X & T.
If last rule applied in D is:
-
Bottom types are trivial.
-
Function and interface type rules can't apply.
-
Right intersection introduction then we're done.
-
Left intersection introduction. Then
Sis of the formY & S0, and either- we have a sub-derivation of
Y <: X & T- by induction we therefore have a derivation ending in right intersection
introduction, so by inversion we have:
- a derivation of
Y <: X, and so by left intersection introduction we haveY & S0 <: X - a derivation of
Y <: T, and so by left intersection introduction we haveY & S0 <: T
- a derivation of
- by right intersection introduction, we have
Y & S0 <: X & T - QED
- by induction we therefore have a derivation ending in right intersection
introduction, so by inversion we have:
- we have a sub-derivation of
S0 <: X & T- by induction we therefore have a derivation ending in right intersection
introduction, so by inversion we have:
- a derivation of
S0 <: X, and so by left intersection introduction we haveY & S0 <: X - a derivation of
S0 <: T, and so by left intersection introduction we haveY & S0 <: T
- a derivation of
- by induction we therefore have a derivation ending in right intersection
introduction, so by inversion we have:
- by right intersection introduction, we have
Y & S0 <: X & T - QED
- we have a sub-derivation of
-
Left union introduction. Then
Sis of the formFutureOr<S0>, and- we have sub-derivations
Future<S0> <: X & TandS0 <: X & T - By induction, we can get derivations of the above ending in right intersection
introduction, so by inversion we have derivations of:
Future<S0> <: X,S0 <: X,Future<S0> <: T,S0 <: T- so we have derivations of
S0 <: X,Future<S0> <: X, so by left union introduction we haveFutureOr<S0> <: X
- so we have derivations of
S0 <: T,Future<S0> <: T, so by left union introduction we haveFutureOr<S0> <: T
- so we have derivations of
- so by right intersection introduction, we have a derivation of
FutureOr<S0> <: X & T - QED
- we have sub-derivations
Conjecture 1: FutureOr<A> <: FutureOr<B> is derivable iff A <: B is
derivable.
Showing that A <: B => FutureOr<A> <: FutureOr<B> is easy, but it is not
immediately clear how to tackle the opposite direction.
Lemma 3: Transitivity of subtyping is admissible. Given derivations of A <: B
and B <: C, there is a derivation of A <: C.
Proof sketch: The proof should go through by induction on sizes of derivations, cases on pairs of rules used. For any pair of rules used, we can construct a new derivation of the desired result using only smaller derivations.
Observation 1: Given X with bound S, we have the property that for all
instances of X & T, T <: S will be true, and hence S <: M => T <: M.
Algorithmic rules
Consider T0 <: T1.
Union on the left
By lemma 1, if T0 is of the form FutureOr<S0> and there is any derivation of
T0 <: T1, then there is a derivation ending with a use of left union
introduction so we have the rule:
T0isFutureOr<S0>- and
Future<S0> <: T1 - and
S0 <: T1
- and
Identical type variables
If T0 and T1 are both the same unpromoted type variable, then subtyping
holds by reflexivity. If T0 is a promoted type variable X0 & S0, and T0
is X0 then it suffices to show that X0 <: X0 or S0 <: X0, and the former
holds immediately. This justifies the rule:
T0is a type variableX0or a promoted type variablesX0 & S0andT1isX0.
If T0 is X0 or X0 & S0 and T1 is X0 & S1, then by lemma 1 it suffices
to show that X0 & S0 <: X0 and X0 & S0 <: S1. The first holds immediately
by reflexivity on the type variable, so it is sufficient to check T0 <: S1.
T0is a type variableX0or a promoted type variablesX0 & S0andT1isX0 & S1- and
T0 <: S1.
- and
Note that neither of the previous rules are required to make the rules algorithmic: they are merely useful special cases of the next rule.
Intersection on the right
By lemma 2, if T1 is of the X1 & S1 and there is any derivation of T0 <: T1, then there is a derivation ending with a use of right intersection
introduction, hence the rule:
T1is a promoted type variableX1 & S1- and
T0 <: X1 - and
T0 <: S1
- and
Union on the right
Suppose T1 is FutureOr<S1>. The rules above have eliminated the possibility
that T0 is of the form FutureOr<S0. The only rules that could possibly
apply then are right union introduction, left intersection introduction, or the
variable bounds rules. Combining these yields the following preliminary
disjunctive rule:
T1isFutureOr<S1>and- either
T0 <: Future<S1> - or
T0 <: S1 - or
T0isX0andX0has boundS0andS0 <: T1 - or
T0isX0 & S0andX0 <: T1andS0 <: T1
- either
The last disjunctive clause can be further simplified to
- or
T0isX0 & S0andS0 <: T1
since the premise X0 <: FutureOr<S1> can only derived either using the
variable bounds rule or right union introduction. For the variable bounds rule,
the premise B0 <: T1 is redundant with S0 <: T1 by observation 1. For right
union introduction, X0 <: S1 is redundant with T0 <: S1, since if X0 <: S1
is derivable, then T0 <: S1 is derivable by left union introduction; and X0 <: Future<S1> is redundant with T0 <: Future<S1>, since if the former is
derivable, then the latter is also derivable by left intersection introduction.
So we have the final rule:
T1isFutureOr<S1>and- either
T0 <: Future<S1> - or
T0 <: S1 - or
T0isX0andX0has boundS0andS0 <: T1 - or
T0isX0 & S0andS0 <: T1
- either
Intersection on the left
Suppose T0 is X0 & S0. We've eliminated the possibility that T1 is
FutureOr<S1>, the possibility that T1 is X1 & S1, and the possibility that
T1 is any variant of X0. The only remaining rule that applies is left
intersection introduction, and so it suffices to check that X0 <: T1 and S0 <: T1. But given the remaining possible forms for T1, the only rule that can
apply to X0 <: T1 is the variable bounds rule, which by observation 1 is
redundant with the second premise, and so we have the rule:
T0 is a promoted type variable X0 & S0
- and
S0 <: T1
Type variable on the left
Suppose T0 is X0. We've eliminated the possibility that T1 is
FutureOr<S1>, the possibility that T1 is X1 & S1, and the possibility that
T1 is any variant of X0. The only rule that applies is the variable bounds
rule:
T0 is a type variable X0 with bound B0
- and
B0 <: T1
This eliminates all of the non-algorithmic rules: the remainder are strictly algorithmic.