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

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: rebase 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..cce26259153fcfdaf5b134747677a30b7ba39996 100644
--- a/pkg/analyzer/lib/src/generated/type_system.dart
+++ b/pkg/analyzer/lib/src/generated/type_system.dart
@@ -11,10 +11,8 @@ 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
@@ -31,6 +29,113 @@ 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 typeProvider,
+ FunctionTypeImpl fnType,
+ List<DartType> correspondingParameterTypes,
+ List<DartType> argumentTypes) {
+ ExecutableElement element = fnType.element;
+ if (element.typeParameters.isEmpty) {
+ return fnType;
+ }
+
+ 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;
+ }
+ }
+
+ // Create a TypeSystem that will allow certain type parameters to be
+ // inferred. It will optimistically assume these type parameters can be
+ // subtypes (or supertypes) as necessary, and track the constraints that
+ // are implied by this.
+ var inferringTypeSystem = new _StrongInferenceTypeSystem(
+ typeProvider, 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.
+ inferringTypeSystem.isSubtypeOf(
+ argumentTypes[i], correspondingParameterTypes[i]);
+ }
+
+ var inferredTypes = new List<DartType>.from(fnTypeParams, growable: false);
+ for (int i = 0; i < fnTypeParams.length; i++) {
+ TypeParameterType typeParam = fnTypeParams[i];
+ _TypeParameterBound bound = inferringTypeSystem._bounds[typeParam];
+
+ // Now we've computed lower and upper bounds for each type parameter.
+ //
+ // To decide on which type to assign, we look at the return type and see
+ // if the type parameter occurs in covariant or contravariant positions.
+ //
+ // If the type is "passed in" at all, or if our lower bound was bottom,
+ // we choose the upper bound as being the most useful.
+ //
+ // Otherwise we choose the more precise lower bound.
+ _TypeParameterVariance variance =
+ new _TypeParameterVariance.from(typeParam, fnType.returnType);
+
+ inferredTypes[i] =
+ variance.passedIn || 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 +173,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 +183,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 +201,14 @@ 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) {
+ return false;
+ }
+
bool _isBottom(DartType t, {bool dynamicIsBottom: false}) {
return (t.isDynamic && dynamicIsBottom) || t.isBottom;
}
@@ -245,6 +358,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 +378,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 +387,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 +433,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 +565,161 @@ class TypeSystemImpl implements TypeSystem {
return leftType.isSubtypeOf(rightType);
}
}
+
+/// Tracks upper and lower type bounds for a set of type parameters.
+class _StrongInferenceTypeSystem extends StrongTypeSystemImpl {
+ final TypeProvider _typeProvider;
+ final Map<TypeParameterType, _TypeParameterBound> _bounds;
+
+ _StrongInferenceTypeSystem(
+ 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;
+ });
+
+ @override
+ bool _inferTypeParameterSubtypeOf(
+ DartType t1, DartType t2, Set<Element> visited) {
+ if (t1 is TypeParameterType) {
+ _TypeParameterBound bound = _bounds[t1];
+ if (bound != null) {
+ _GuardedSubtypeChecker<DartType> guardedSubtype = _guard(_isSubtypeOf);
+
+ DartType newUpper = t2;
+ if (guardedSubtype(bound.upper, newUpper, visited)) {
+ // upper bound 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;
+ }
+ // Optimistically assume we will be able to satisfy the constraint.
+ return true;
+ }
+ }
+ if (t2 is TypeParameterType) {
+ _TypeParameterBound bound = _bounds[t2];
+ if (bound != null) {
+ bound.lower = getLeastUpperBound(_typeProvider, bound.lower, t1);
+ // Optimistically assume we will be able to satisfy the constraint.
+ return true;
+ }
+ }
+ return false;
+ }
+}
+
+/// 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;
+}
+
+/// Records what positions a type parameter is used in.
+class _TypeParameterVariance {
+ /// The type parameter is a value passed out. It must satisfy T <: S,
+ /// where T is the type parameter and S is what it's assigned to.
+ ///
+ /// For example, this could be the return type, or a parameter to a parameter:
+ ///
+ /// TOut method<TOut>(void f(TOut t));
+ bool passedOut = false;
+
+ /// The type parameter is a value passed in. It must satisfy S <: T,
+ /// where T is the type parameter and S is what's being assigned to it.
+ ///
+ /// For example, this could be a parameter type, or the parameter of the
+ /// return value:
+ ///
+ /// typedef void Func<T>(T t);
+ /// Func<TIn> method<TIn>(TIn t);
+ bool passedIn = false;
+
+ _TypeParameterVariance.from(TypeParameterType typeParam, DartType type) {
+ _visitType(typeParam, type, false);
+ }
+
+ void _visitFunctionType(
+ TypeParameterType typeParam, FunctionType type, bool paramIn) {
+ for (ParameterElement p in type.parameters) {
+ // If a lambda L is passed in to a function F, the the parameters are
+ // "passed out" of F into L. Thus we invert the "passedIn" state.
+ _visitType(typeParam, p.type, !paramIn);
+ }
+ // If a lambda L is passed in to a function F, and we call L, the result of
+ // L is then "passed in" to F. So we keep the "passedIn" state.
+ _visitType(typeParam, type.returnType, paramIn);
+ }
+
+ void _visitInterfaceType(
+ TypeParameterType typeParam, InterfaceType type, bool paramIn) {
+ // Currently in "strong mode" generic type parameters are covariant.
+ //
+ // This means we treat them as "out" type parameters similar to the result
+ // of a function, and thus they follow the same rules.
+ //
+ // For example, we pass in Iterable<T> as a parameter. Then we iterate over
+ // it. The "T" is essentially an input. So it keeps the same state.
+ // Similarly, if we return an Iterable<T> it's equivalent to returning a T.
+ for (DartType typeArg in type.typeArguments) {
+ _visitType(typeParam, typeArg, paramIn);
+ }
+ }
+
+ void _visitType(TypeParameterType typeParam, DartType type, bool paramIn) {
+ if (type == typeParam) {
+ if (paramIn) {
+ passedIn = true;
+ } else {
+ passedOut = true;
+ }
+ } else if (type is FunctionType) {
+ _visitFunctionType(typeParam, type, paramIn);
+ } else if (type is InterfaceType) {
+ _visitInterfaceType(typeParam, type, paramIn);
+ }
+ }
+}
« no previous file with comments | « pkg/analyzer/lib/src/generated/testing/element_factory.dart ('k') | pkg/analyzer/test/generated/resolver_test.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698