| Index: editor/tools/plugins/com.google.dart.engine/src/com/google/dart/engine/internal/type/UnionTypeImpl.java
|
| diff --git a/editor/tools/plugins/com.google.dart.engine/src/com/google/dart/engine/internal/type/UnionTypeImpl.java b/editor/tools/plugins/com.google.dart.engine/src/com/google/dart/engine/internal/type/UnionTypeImpl.java
|
| index 27a3491bd6fe13a61f8c797e94b8838dde28f498..5970002acf70200514b0baf6099d986360eb75d4 100644
|
| --- a/editor/tools/plugins/com.google.dart.engine/src/com/google/dart/engine/internal/type/UnionTypeImpl.java
|
| +++ b/editor/tools/plugins/com.google.dart.engine/src/com/google/dart/engine/internal/type/UnionTypeImpl.java
|
| @@ -148,49 +148,77 @@ public class UnionTypeImpl extends TypeImpl implements UnionType {
|
| @Override
|
| protected boolean internalIsMoreSpecificThan(Type type, boolean withDynamic,
|
| Set<TypePair> visitedTypePairs) {
|
| - // TODO(collinsn): what version of subtyping do we want?
|
| + // What version of subtyping do we want? See discussion below in [internalIsSubtypeOf].
|
| //
|
| // The more unsound version: any.
|
| - /*
|
| for (Type t : types) {
|
| if (((TypeImpl) t).internalIsMoreSpecificThan(type, withDynamic, visitedTypePairs)) {
|
| return true;
|
| }
|
| }
|
| return false;
|
| - */
|
| // The less unsound version: all.
|
| + /*
|
| for (Type t : types) {
|
| if (!((TypeImpl) t).internalIsMoreSpecificThan(type, withDynamic, visitedTypePairs)) {
|
| return false;
|
| }
|
| }
|
| return true;
|
| + */
|
| }
|
|
|
| @Override
|
| protected boolean internalIsSubtypeOf(Type type, Set<TypePair> visitedTypePairs) {
|
| // Premature optimization opportunity: if [type] is also a union type, we could instead
|
| - // do a subset test on the underlying element tests.
|
| + // do a subset test on the underlying element sets.
|
|
|
| - // TODO(collinsn): what version of subtyping do we want?
|
| + // What version of union-type subtyping do we want? We choose the more unsound version,
|
| + // motivated by the following example. Suppose classes [A] and [B] are unrelated
|
| + // and that [C] extends [B]:
|
| + //
|
| + // A B
|
| + // |
|
| + // C
|
| + //
|
| + // If [ab : {A, B}] and [ac : {A, C}] then we'd intuitively expect either
|
| + // both or neither of the assignments
|
| + //
|
| + // B b1 = ab;
|
| + // B b2 = ac;
|
| //
|
| + // to be allowed; they're both allowed under the more unsound subtyping rule.
|
| + //
|
| + // However, under the less unsound subtyping rule, the assignment to [b1] is
|
| + // allowed, but the assignment to [b2] is not. The reason is that assignment
|
| + // compatible [T1 <=> T1] for interface types is defined by [T1 <: T2] or
|
| + // [T2 <: T1]. So, in the [b1 = ab] case, the test [B <: {A, B}] always passes,
|
| + // for both definitions of union-type subtyping. On the other hand, the test
|
| + // [B <: {A, C}] always fails, leaving only [{A, C} <: B], which only passes
|
| + // for the more unsound rule.
|
| + //
|
| + // An interesting consequence: we usually think of [A <: B] meaning that
|
| + // knowing an expression has type [A] is more informative than knowing it has type [B].
|
| + // Of course, this intuition is already wrong once we have [dynamic], but it becomes
|
| + // more wrong when we define union-type subtyping in the more unsound way. We now e.g.
|
| + // have [{A, B} <: B], where the LHS is surely less informative than the RHS!
|
| +
|
| // The more unsound version: any.
|
| - /*
|
| for (Type t : types) {
|
| if (((TypeImpl) t).internalIsSubtypeOf(type, visitedTypePairs)) {
|
| return true;
|
| }
|
| }
|
| return false;
|
| - */
|
| // The less unsound version: all.
|
| + /*
|
| for (Type t : types) {
|
| if (!((TypeImpl) t).internalIsSubtypeOf(type, visitedTypePairs)) {
|
| return false;
|
| }
|
| }
|
| return true;
|
| + */
|
| }
|
|
|
| /**
|
|
|