Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(12)

Unified Diff: pkg/analyzer/lib/src/generated/type_system.dart

Issue 1442883003: generic method type parameter inference (Closed) Base URL: git@github.com:dart-lang/sdk.git@master
Patch Set: Created 5 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
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 ef6bbfb850c955135bc1b0fe7f92937e6a09fa7e..17c7a411213665592a2bfd10e90044e6f41a7874 100644
--- a/pkg/analyzer/lib/src/generated/type_system.dart
+++ b/pkg/analyzer/lib/src/generated/type_system.dart
@@ -6,15 +6,14 @@ library analyzer.src.generated.type_system;
import 'dart:collection';
+import 'ast.dart' show Expression;
import 'element.dart';
import 'engine.dart' show AnalysisContext;
import 'resolver.dart' show TypeProvider;
typedef bool _GuardedSubtypeChecker<T>(T t1, T t2, Set<Element> visited);
-
typedef bool _SubtypeChecker<T>(T t1, T t2);
-
/**
* Implementation of [TypeSystem] using the strong mode rules.
* https://github.com/dart-lang/dev_compiler/blob/master/STRONG_MODE.md
@@ -22,6 +21,11 @@ typedef bool _SubtypeChecker<T>(T t1, T t2);
class StrongTypeSystemImpl implements TypeSystem {
final _specTypeSystem = new TypeSystemImpl();
+ /// If we are currently in a context where we are inferring type parameters
+ /// from arguments to a generic function call, this will be set, otherwise it
+ /// will be null.
+ _TypeParameterBounds _typeParamBounds;
Brian Wilkerson 2015/11/19 00:10:17 Storing temporary state in an instance field seems
Jennifer Messerly 2015/11/19 00:25:26 Yeah, I can change it to pass it down if y'all pre
Jennifer Messerly 2015/11/19 01:36:39 I have an idea how to fix this. Working on it.
Jennifer Messerly 2015/11/19 01:43:26 PTAL, I fixed this by creating a subclass that tra
+
StrongTypeSystemImpl();
@override
@@ -31,6 +35,118 @@ class StrongTypeSystemImpl implements TypeSystem {
return _specTypeSystem.getLeastUpperBound(typeProvider, type1, type2);
}
+ /// Given a function type with generic type parameters, infer the type
+ /// parameters from the actual argument types, and return it. If we can't.
+ /// returns the original function type.
+ ///
+ /// Concretely, given a function type with parameter types P0, P1, ... Pn,
+ /// result type R, and generic type parameters T0, T1, ... Tm, use the
+ /// argument types A0, A1, ... An to solve for the type parameters.
+ ///
+ /// For each parameter Pi, we want to ensure that Ai <: Pi. We can do this by
+ /// running the subtype algorithm, and when we reach a type parameter Pj,
+ /// recording the lower or upper bound it must satisfy. At the end, all
+ /// constraints can be combined to determine the type.
+ ///
+ /// As a simplification, we do not actually store all constraints on each type
+ /// parameter Pj. Instead we track Uj and Lj where U is the upper bound and
+ /// L is the lower bound of that type parameter.
+ FunctionType inferCallFromArguments(
+ TypeProvider provider,
+ FunctionTypeImpl fnType,
+ List<DartType> correspondingParameterTypes,
+ List<DartType> argumentTypes) {
+ ExecutableElement element = fnType.element;
+ if (element.typeParameters.isEmpty) {
+ return fnType;
+ }
+
+ assert(_typeParamBounds == null);
+
+ int numParams = element.typeParameters.length;
+ List<DartType> fnTypeParams = fnType.typeArguments;
+ fnTypeParams = fnTypeParams.sublist(0, numParams);
+ for (int i = 0; i < numParams; i++) {
+ if (element.typeParameters[i].type != fnTypeParams[i]) {
+ // Only infer if all of the type parameters are unsubstituted.
+ return fnType;
+ }
+ }
+
+ var typeBounds = new _TypeParameterBounds(
+ provider, new List<TypeParameterType>.from(fnTypeParams));
+
+ for (int i = 0; i < argumentTypes.length; i++) {
+ // Try to pass each argument to each parameter, recording any type
+ // parameter bounds that were implied by this assignment.
+ _typeParamBounds = typeBounds;
+ isSubtypeOf(argumentTypes[i], correspondingParameterTypes[i]);
+ _typeParamBounds = null;
+ }
+
+ // Now we've computed lower and upper bounds for each type parameter.
+ //
+ // To decide on which type to assign, we use the following heuristic.
Jennifer Messerly 2015/11/19 00:25:26 Leaf pointed out that the code doesn't implement t
+ //
+ // If the type parameter T appears in a contravariant position, such as
+ // the function's return type, or a parameter to a parameter, then we choose
+ // the lower bound. This lets our "out" type be as precise as possible. For
+ // example, this will allow tools to offer the most useful completions on
+ // the result value:
+ //
+ // min(1.0, 2.0). // <-- we can pick `double` as T and show completions.
+ // list.fold(0, (x, y) => x. // <-- we can pick `int` as the type of `x`
+ // and show completions.
+ //
+ // Otherwise, if the type only appears in covariant positions, choose the
+ // upper bound.
+ var inferredTypes = new List<DartType>.from(fnTypeParams, growable: false);
+ for (int i = 0; i < fnTypeParams.length; i++) {
+ TypeParameterType typeParam = fnTypeParams[i];
+ _TypeParameterBound bound = typeBounds._bounds[typeParam];
+
+ // If the type parameter occurs passed out, we'll have an "interesting"
+ // lower bound (i.e. not bottom). Use it. Otherwise use the upper bound.
+ inferredTypes[i] = bound.lower.isBottom ? bound.upper : bound.lower;
+
+ // Assumption: if the current type parameter has an "extends" clause
+ // that refers to another type variable we are inferring, it will appear
+ // before us or in this list position. For example:
+ //
+ // <TFrom, TTo extends TFrom>
+ //
+ // We may infer TTo is TFrom. In that case, we already know what TFrom
+ // is inferred as, so we can substitute it now. This also handles more
+ // complex cases such as:
+ //
+ // <TFrom, TTo extends Iterable<TFrom>>
+ //
+ // Or if the type parameter's bound depends on itself such as:
+ //
+ // <T extends Clonable<T>>
+ inferredTypes[i] =
+ inferredTypes[i].substitute2(inferredTypes, fnTypeParams);
+
+ // See if this actually worked.
+ // If not, fall back to the known upper bound (if any) or `dynamic`.
+ if (inferredTypes[i].isBottom ||
+ !isSubtypeOf(inferredTypes[i],
+ bound.upper.substitute2(inferredTypes, fnTypeParams)) ||
+ !isSubtypeOf(bound.lower.substitute2(inferredTypes, fnTypeParams),
+ inferredTypes[i])) {
+
+ inferredTypes[i] = DynamicTypeImpl.instance;
+ if (typeParam.element.bound != null) {
+ inferredTypes[i] =
+ typeParam.element.bound.substitute2(inferredTypes, fnTypeParams);
+ }
+ }
+ }
+
+ // Return the substituted type.
+ return fnType.substitute2(inferredTypes, fnTypeParams);
+ }
+
// TODO(leafp): Document the rules in play here
@override
bool isAssignableTo(DartType fromType, DartType toType) {
@@ -68,6 +184,9 @@ class StrongTypeSystemImpl implements TypeSystem {
return _isSubtypeOf(leftType, rightType, null);
}
+ // Given a type t, if t is an interface type with a call method
+ // defined, return the function type for the call method, otherwise
+ // return null.
FunctionType _getCallMethodType(DartType t) {
if (t is InterfaceType) {
return t.lookUpInheritedMethod("call")?.type;
@@ -75,9 +194,6 @@ class StrongTypeSystemImpl implements TypeSystem {
return null;
}
- // Given a type t, if t is an interface type with a call method
- // defined, return the function type for the call method, otherwise
- // return null.
_GuardedSubtypeChecker<DartType> _guard(
_GuardedSubtypeChecker<DartType> check) {
return (DartType t1, DartType t2, Set<Element> visited) {
@@ -96,6 +212,46 @@ class StrongTypeSystemImpl implements TypeSystem {
};
}
+ /// If [t1] or [t2] is a type parameter we are inferring, update its bound.
+ /// Returns `true` if we could possibly find a compatible type,
+ /// otherwise `false`.
+ bool _inferTypeParameterSubtypeOf(
+ DartType t1, DartType t2, Set<Element> visited) {
+ if (_typeParamBounds == null) {
+ return false;
+ }
+
+ if (t1 is TypeParameterType) {
+ _TypeParameterBound bound = _typeParamBounds._bounds[t1];
+ if (bound != null) {
+ _GuardedSubtypeChecker<DartType> guardedSubtype = _guard(_isSubtypeOf);
+
+ DartType newUpper = t2;
+ if (guardedSubtype(bound.upper, newUpper, visited)) {
+ // upper bound is already covers this. Nothing to do.
+ } else if (guardedSubtype(newUpper, bound.upper, visited)) {
+ // update to the new, more precise upper bound.
+ bound.upper = newUpper;
+ } else {
+ // Failed to find an upper bound. Use bottom to signal no solution.
+ bound.upper = BottomTypeImpl.instance;
+ }
+ return guardedSubtype(bound.lower, bound.upper, visited);
+ }
+ }
+ if (t2 is TypeParameterType) {
+ _TypeParameterBound bound = _typeParamBounds._bounds[t2];
+ if (bound != null) {
+ _GuardedSubtypeChecker<DartType> guardedSubtype = _guard(_isSubtypeOf);
+
+ TypeProvider tp = _typeParamBounds._typeProvider;
+ bound.lower = getLeastUpperBound(tp, bound.lower, t1);
+ return guardedSubtype(bound.lower, bound.upper, visited);
+ }
+ }
+ return false;
+ }
+
bool _isBottom(DartType t, {bool dynamicIsBottom: false}) {
return (t.isDynamic && dynamicIsBottom) || t.isBottom;
}
@@ -245,6 +401,8 @@ class StrongTypeSystemImpl implements TypeSystem {
{bool dynamicIsBottom: false}) {
// Guard recursive calls
_GuardedSubtypeChecker<DartType> guardedSubtype = _guard(_isSubtypeOf);
+ _GuardedSubtypeChecker<DartType> guardedInferTypeParameter =
+ _guard(_inferTypeParameterSubtypeOf);
if (t1 == t2) {
return true;
@@ -263,7 +421,7 @@ class StrongTypeSystemImpl implements TypeSystem {
// Trivially false.
if (_isTop(t1, dynamicIsBottom: dynamicIsBottom) ||
_isBottom(t2, dynamicIsBottom: dynamicIsBottom)) {
- return false;
+ return guardedInferTypeParameter(t1, t2, visited);
}
// S <: T where S is a type variable
@@ -272,13 +430,15 @@ class StrongTypeSystemImpl implements TypeSystem {
// So only true if bound of S is S' and
// S' <: T
if (t1 is TypeParameterType) {
+ if (guardedInferTypeParameter(t1, t2, visited)) {
+ return true;
+ }
DartType bound = t1.element.bound;
- if (bound == null) return false;
- return guardedSubtype(bound, t2, visited);
+ return bound == null ? false : guardedSubtype(bound, t2, visited);
}
if (t2 is TypeParameterType) {
- return false;
+ return guardedInferTypeParameter(t1, t2, visited);
}
if (t1.isVoid || t2.isVoid) {
@@ -316,7 +476,6 @@ class StrongTypeSystemImpl implements TypeSystem {
}
}
-
/**
* The interface `TypeSystem` defines the behavior of an object representing
* the type system. This provides a common location to put methods that act on
@@ -449,3 +608,62 @@ class TypeSystemImpl implements TypeSystem {
return leftType.isSubtypeOf(rightType);
}
}
+
+/// An [upper] and [lower] bound for a type variable.
+class _TypeParameterBound {
+ /// The upper bound of the type parameter. In other words, T <: upperBound.
+ ///
+ /// In Dart this can be written as `<T extends UpperBoundType>`.
+ ///
+ /// In inference, this can happen as a result of parameters of function type.
+ /// For example, consider a signature like:
+ ///
+ /// T reduce<T>(List<T> values, T f(T x, T y));
+ ///
+ /// and a call to it like:
+ ///
+ /// reduce(values, (num x, num y) => ...);
+ ///
+ /// From the function expression's parameters, we conclude `T <: num`. We may
+ /// still be able to conclude a different [lower] based on `values` or
+ /// the type of the elided `=> ...` body. For example:
+ ///
+ /// reduce(['x'], (num x, num y) => 'hi');
+ ///
+ /// Here the [lower] will be `String` and the upper bound will be `num`,
+ /// which cannot be satisfied, so this is ill typed.
+ DartType upper = DynamicTypeImpl.instance;
+
+ /// The lower bound of the type parameter. In other words, lowerBound <: T.
+ ///
+ /// This kind of constraint cannot be expressed in Dart, but it applies when
+ /// we're doing inference. For example, consider a signature like:
+ ///
+ /// T pickAtRandom<T>(T x, T y);
+ ///
+ /// and a call to it like:
+ ///
+ /// pickAtRandom(1, 2.0)
+ ///
+ /// when we see the first parameter is an `int`, we know that `int <: T`.
+ /// When we see `double` this implies `double <: T`.
+ /// Combining these constraints results in a lower bound of `num`.
+ ///
+ /// In general, we choose the lower bound as our inferred type, so we can
+ /// offer the most constrained (strongest) result type.
+ DartType lower = BottomTypeImpl.instance;
+}
+
+/// Tracks upper and lower type bounds for a set of type parameters.
+class _TypeParameterBounds {
+ final TypeProvider _typeProvider;
+ final Map<TypeParameterType, _TypeParameterBound> _bounds;
+
+ _TypeParameterBounds(
+ this._typeProvider, Iterable<TypeParameterType> typeParams)
+ : _bounds = new Map.fromIterable(typeParams, value: (t) {
+ _TypeParameterBound bound = new _TypeParameterBound();
+ if (t.element.bound != null) bound.upper = t.element.bound;
+ return bound;
+ });
+}

Powered by Google App Engine
This is Rietveld 408576698