Chromium Code Reviews| Index: pkg/analyzer/lib/src/generated/type_system.dart |
| diff --git a/pkg/analyzer/lib/src/generated/type_system.dart b/pkg/analyzer/lib/src/generated/type_system.dart |
| index 3ffd95ae94856fd98a93b32729fac7c872e499bb..9dc5506848b16fca934138961e60599b59df579a 100644 |
| --- a/pkg/analyzer/lib/src/generated/type_system.dart |
| +++ b/pkg/analyzer/lib/src/generated/type_system.dart |
| @@ -95,6 +95,111 @@ class StrongTypeSystemImpl extends TypeSystem { |
| } |
| /** |
| + * This currently does not implement a very complete least upper bound |
| + * algorithm, but handles a couple of the very common cases that are |
| + * causing pain in real code. The current algorithm is: |
| + * 1. If either of the types is a supertype of the other, return it. |
| + * This is in fact the best result in this case. |
| + * 2. If the two types have the same class element, then take the |
| + * pointwise least upper bound of the type arguments. This is again |
| + * the best result, except that the recursive calls may not return |
| + * the true least uppper bounds. The result is guaranteed to be a |
| + * well-formed type under the assumption that the input types were |
| + * well-formed (and assuming that the recursive calls return |
| + * well-formed types). |
| + * 3. Otherwise return the spec-defined least upper bound. This will |
| + * be an upper bound, might (or might not) be least, and might |
| + * (or might not) be a well-formed type. |
| + * |
| + * TODO(leafp): Use matchTypes or something similar here to handle the |
| + * case where one of the types is a superclass (but not supertype) of |
| + * the other, e.g. LUB(Iterable<double>, List<int>) = Iterable<num> |
| + * TODO(leafp): Figure out the right final algorithm and implement it. |
| + */ |
| + @override |
| + DartType _interfaceLeastUpperBound( |
| + TypeProvider provider, InterfaceType type1, InterfaceType type2) { |
| + if (isSubtypeOf(type1, type2)) { |
| + return type2; |
| + } |
| + if (isSubtypeOf(type2, type1)) { |
| + return type1; |
| + } |
| + if (type1.element == type2.element) { |
| + List<DartType> tArgs1 = type1.typeArguments; |
| + List<DartType> tArgs2 = type2.typeArguments; |
| + |
| + assert(tArgs1.length == tArgs2.length); |
| + List<DartType> tArgs = new List(tArgs1.length); |
| + for (int i = 0; i < tArgs1.length; i++) { |
| + tArgs[i] = getLeastUpperBound(provider, tArgs1[i], tArgs2[i]); |
| + } |
| + InterfaceTypeImpl lub = new InterfaceTypeImpl(type1.element); |
|
Jennifer Messerly
2016/03/29 23:54:39
drive by comment, is this equivalent to:
type
Leaf
2016/03/29 23:57:42
Yeah, I think that's right, and is nicer.
|
| + lub.typeArguments = tArgs; |
| + return lub; |
| + } |
| + return InterfaceTypeImpl.computeLeastUpperBound(type1, type2) ?? |
| + provider.dynamicType; |
| + } |
| + |
| + /** |
| + * This currently just implements a simple least upper bound to |
| + * handle some common cases. It also avoids some termination issues |
| + * with the naive spec algorithm. The least upper bound of two types |
| + * (at least one of which is a type parameter) is computed here as: |
| + * 1. If either type is a supertype of the other, return it. |
| + * 2. If the first type is a type parameter, replace it with its bound, |
| + * with recursive occurrences of itself replaced with Object. |
| + * The second part of this should ensure termination. Informally, |
| + * each type variable instantiation in one of the arguments to the |
| + * least upper bound algorithm now strictly reduces the number |
| + * of bound variables in scope in that argument position. |
| + * 3. If the second type is a type parameter, do the symmetric operation |
| + * to #2. |
| + * |
| + * It's not immediately obvious why this is symmetric in the case that both |
| + * of the them are type parameters. For #1, symmetry holds since subtype |
| + * is antisymmetric. For #2, it's clearly not symmetric if upper bounds of |
| + * bottom are allowed. Ignoring this (for various reasons, not least |
| + * of which that there's no way to write it), there's an informal |
| + * argument (that might even be right) that you will always either |
| + * end up expanding both of them or else returning the same result no matter |
| + * which order you expand them in. A key observation is that |
| + * identical(expand(type1), type2) => subtype(type1, type2) |
| + * and hence the contra-positive. |
| + * |
| + * TODO(leafp): Think this through and figure out what's the right |
| + * definition. Be careful about termination. |
| + * |
| + * I suspect in general a reasonable algorithm is to expand the innermost |
| + * type variable first. Alternatively, you could probably choose to treat |
| + * it as just an instance of the interface type upper bound problem, with |
| + * the "inheritance" chain extended by the bounds placed on the variables. |
| + */ |
| + @override |
| + DartType _typeParameterLeastUpperBound( |
| + TypeProvider provider, DartType type1, DartType type2) { |
| + if (isSubtypeOf(type1, type2)) { |
| + return type2; |
| + } |
| + if (isSubtypeOf(type2, type1)) { |
| + return type1; |
| + } |
| + if (type1 is TypeParameterType) { |
| + type1 = type1 |
| + .resolveToBound(provider.objectType) |
| + .substitute2([provider.objectType], [type1]); |
| + return getLeastUpperBound(provider, type1, type2); |
| + } |
| + // We should only be called when at least one of the types is a |
| + // TypeParameterType |
| + type2 = type2 |
| + .resolveToBound(provider.objectType) |
| + .substitute2([provider.objectType], [type2]); |
| + return getLeastUpperBound(provider, type1, type2); |
| + } |
| + |
| + /** |
| * Given a generic function type `F<T0, T1, ... Tn>` and a context type C, |
| * infer an instantiation of F, such that `F<S0, S1, ..., Sn>` <: C. |
| * |
| @@ -663,26 +768,9 @@ abstract class TypeSystem { |
| return type1; |
| } |
| - // TODO(rnystrom): This isn't correct. See: https://dartbug.com/26054. |
| - // Leaf says: |
| - // It does at least give *an* upper bound, but it won't be the least, and in |
| - // many common cases won't be the useful one (e.g. T, S extends T). |
| - // |
| - // The spec definition is pretty unhelpful. |
| - // |
| - // I suspect that a reasonable algorithm is to expand the innermost type |
| - // variable first. Alternatively, you could probably choose to treat it as |
| - // just an instance of the interface type upper bound problem, with the |
| - // "inheritance" chain extended by the bounds placed on the variables. |
| - // |
| - // In the short term, in the case that both are type variables you could |
| - // check if either is a subtype of the other, and if so use the supertype, |
| - // and otherwise fall back to expanding both. This should catch many of the |
| - // interesting cases without getting too involved. |
| - // Let U be a type variable with upper bound B. The least upper bound of U |
| - // and a type T is the least upper bound of B and T. |
| - type1 = type1.resolveToBound(typeProvider.objectType); |
| - type2 = type2.resolveToBound(typeProvider.objectType); |
| + if (type1 is TypeParameterType || type2 is TypeParameterType) { |
| + return _typeParameterLeastUpperBound(typeProvider, type1, type2); |
| + } |
| // The least upper bound of a function type and an interface type T is the |
| // least upper bound of Function and T. |
| @@ -696,12 +784,7 @@ abstract class TypeSystem { |
| // At this point type1 and type2 should both either be interface types or |
| // function types. |
| if (type1 is InterfaceType && type2 is InterfaceType) { |
| - InterfaceType result = |
| - InterfaceTypeImpl.computeLeastUpperBound(type1, type2); |
| - if (result == null) { |
| - return typeProvider.dynamicType; |
| - } |
| - return result; |
| + return _interfaceLeastUpperBound(typeProvider, type1, type2); |
| } |
| if (type1 is FunctionType && type2 is FunctionType) { |
| @@ -714,6 +797,21 @@ abstract class TypeSystem { |
| } |
| /** |
| + * Given two [InterfaceType]s [type1] and [type2] return their least upper |
| + * bound in a type system specific manner. |
| + */ |
| + DartType _interfaceLeastUpperBound( |
| + TypeProvider provider, InterfaceType type1, InterfaceType type2); |
| + |
| + /** |
| + * Given two [DartType]s [type1] and [type2] at least one of which is a |
| + * [TypeParameterType], return their least upper bound in a type system |
| + * specific manner. |
| + */ |
| + DartType _typeParameterLeastUpperBound( |
| + TypeProvider provider, DartType type1, DartType type2); |
| + |
| + /** |
| * Given a [DartType] [type], instantiate it with its bounds. |
| * |
| * The behavior of this method depends on the type system, for example, in |
| @@ -958,6 +1056,22 @@ class TypeSystemImpl extends TypeSystem { |
| bool isSubtypeOf(DartType leftType, DartType rightType) { |
| return leftType.isSubtypeOf(rightType); |
| } |
| + |
| + @override |
| + DartType _interfaceLeastUpperBound( |
| + TypeProvider provider, InterfaceType type1, InterfaceType type2) { |
| + InterfaceType result = |
| + InterfaceTypeImpl.computeLeastUpperBound(type1, type2); |
| + return result ?? provider.dynamicType; |
| + } |
| + |
| + @override |
| + DartType _typeParameterLeastUpperBound( |
| + TypeProvider provider, DartType type1, DartType type2) { |
| + type1 = type1.resolveToBound(provider.objectType); |
| + type2 = type2.resolveToBound(provider.objectType); |
| + return getLeastUpperBound(provider, type1, type2); |
| + } |
| } |
| /// Tracks upper and lower type bounds for a set of type parameters. |