diff --git a/pkg/analyzer_plugin/analyzer_plugin.iml b/pkg/analyzer_plugin/analyzer_plugin.iml
index 13d4a511193b..04e8d05856b8 100644
--- a/pkg/analyzer_plugin/analyzer_plugin.iml
+++ b/pkg/analyzer_plugin/analyzer_plugin.iml
@@ -20,6 +20,5 @@
-
\ No newline at end of file
diff --git a/pkg/front_end/lib/src/fasta/type_inference/type_schema_environment.dart b/pkg/front_end/lib/src/fasta/type_inference/type_schema_environment.dart
index 70356ac5d34a..db636fb3bddc 100644
--- a/pkg/front_end/lib/src/fasta/type_inference/type_schema_environment.dart
+++ b/pkg/front_end/lib/src/fasta/type_inference/type_schema_environment.dart
@@ -16,11 +16,19 @@ import 'package:kernel/type_environment.dart';
class TypeConstraint {
/// The lower bound of the type being constrained. This bound must be a
/// subtype of the type being constrained.
- DartType lower = const UnknownType();
+ DartType lower;
/// The upper bound of the type being constrained. The type being constrained
/// must be a subtype of this bound.
- DartType upper = const UnknownType();
+ DartType upper;
+
+ TypeConstraint()
+ : lower = const UnknownType(),
+ upper = const UnknownType();
+
+ TypeConstraint._(this.lower, this.upper);
+
+ TypeConstraint clone() => new TypeConstraint._(lower, upper);
String toString() =>
'${typeSchemaToString(lower)} <: <: ${typeSchemaToString(upper)}';
@@ -149,6 +157,104 @@ class TypeSchemaEnvironment extends TypeEnvironment {
return const DynamicType();
}
+ /// Use the given [constraints] to substitute for type variables in
+ /// [genericType].
+ ///
+ /// [typeParametersToInfer] is the set of type parameters that should be
+ /// substituted for. [typesFromDownwardsInference] should be a list of the
+ /// same length, initially filled with `null`.
+ ///
+ /// If [downwardsInferPhase] is `true`, then we are in the first pass of
+ /// inference, pushing context types down. This means we are allowed to push
+ /// down `?` to precisely represent an unknown type. Also, any types that are
+ /// inferred during this stage will be stored in [typesFromDownwardsInference]
+ /// for later use.
+ ///
+ /// If [downwardsInferPhase] is `false`, then we are in the second pass of
+ /// inference, and must not conclude `?` for any type formal. In this pass,
+ /// values will be read from [typesFromDownwardsInference] to use as a
+ /// starting point for inference.
+ DartType inferTypeFromConstraints(
+ Map constraints,
+ DartType genericType,
+ List typeParametersToInfer,
+ List typesFromDownwardsInference,
+ {bool downwardsInferPhase: false}) {
+ // Initialize the inferred type array.
+ //
+ // In the downwards phase, they all start as `?` to offer reasonable
+ // degradation for f-bounded type parameters.
+ var inferredTypes = new List.filled(
+ typeParametersToInfer.length, const UnknownType());
+
+ for (int i = 0; i < typeParametersToInfer.length; i++) {
+ TypeParameter typeParam = typeParametersToInfer[i];
+
+ var typeParamBound = typeParam.bound;
+ DartType extendsConstraint;
+ if (!_isObjectOrDynamic(typeParamBound)) {
+ extendsConstraint = Substitution
+ .fromPairs(typeParametersToInfer, inferredTypes)
+ .substituteType(typeParamBound);
+ }
+
+ var constraint = constraints[typeParam];
+ if (downwardsInferPhase) {
+ typesFromDownwardsInference[i] = inferredTypes[i] =
+ _inferTypeParameterFromContext(constraint, extendsConstraint);
+ } else {
+ inferredTypes[i] = _inferTypeParameterFromAll(
+ typesFromDownwardsInference[i], constraint, extendsConstraint);
+ }
+ }
+
+ // If the downwards infer phase has failed, we'll catch this in the upwards
+ // phase later on.
+ if (downwardsInferPhase) {
+ return Substitution
+ .fromPairs(typeParametersToInfer, inferredTypes)
+ .substituteType(genericType);
+ }
+
+ // Check the inferred types against all of the constraints.
+ var knownTypes = {};
+ for (int i = 0; i < typeParametersToInfer.length; i++) {
+ TypeParameter typeParam = typeParametersToInfer[i];
+ var constraint = constraints[typeParam];
+ var typeParamBound = Substitution
+ .fromPairs(typeParametersToInfer, inferredTypes)
+ .substituteType(typeParam.bound);
+
+ var inferred = inferredTypes[i];
+ bool success = typeSatisfiesConstraint(inferred, constraint);
+ if (success && !_isObjectOrDynamic(typeParamBound)) {
+ // If everything else succeeded, check the `extends` constraint.
+ var extendsConstraint = typeParamBound;
+ success = isSubtypeOf(inferred, extendsConstraint);
+ }
+
+ if (!success) {
+ // TODO(paulberry): report error.
+
+ // Heuristic: even if we failed, keep the erroneous type.
+ // It should satisfy at least some of the constraints (e.g. the return
+ // context). If we fall back to instantiateToBounds, we'll typically get
+ // more errors (e.g. because `dynamic` is the most common bound).
+ }
+
+ if (isKnown(inferred)) {
+ knownTypes[typeParam] = inferred;
+ }
+ }
+
+ // Use instantiate to bounds to finish things off.
+ var result = instantiateToBounds(genericType, knownTypes: knownTypes);
+
+ // TODO(paulberry): report any errors from instantiateToBounds.
+
+ return result;
+ }
+
/// Given a [DartType] [type], if [type] is an uninstantiated
/// parameterized type then instantiate the parameters to their
/// bounds. See the issue for the algorithm description.
@@ -401,6 +507,44 @@ class TypeSchemaEnvironment extends TypeEnvironment {
requiredParameterCount: requiredParameterCount);
}
+ DartType _inferTypeParameterFromAll(DartType typeFromContextInference,
+ TypeConstraint constraint, DartType extendsConstraint) {
+ // See if we already fixed this type from downwards inference.
+ // If so, then we aren't allowed to change it based on argument types.
+ if (isKnown(typeFromContextInference)) {
+ return typeFromContextInference;
+ }
+
+ if (extendsConstraint != null) {
+ constraint = constraint.clone();
+ addUpperBound(constraint, extendsConstraint);
+ }
+
+ return solveTypeConstraint(constraint, grounded: true);
+ }
+
+ DartType _inferTypeParameterFromContext(
+ TypeConstraint constraint, DartType extendsConstraint) {
+ DartType t = solveTypeConstraint(constraint);
+ if (!isKnown(t)) {
+ return t;
+ }
+
+ // If we're about to make our final choice, apply the extends clause.
+ // This gives us a chance to refine the choice, in case it would violate
+ // the `extends` clause. For example:
+ //
+ // Object obj = math.min/**/(1, 2);
+ //
+ // If we consider the `T extends num` we conclude ``, which works.
+ if (extendsConstraint != null) {
+ constraint = constraint.clone();
+ addUpperBound(constraint, extendsConstraint);
+ return solveTypeConstraint(constraint);
+ }
+ return t;
+ }
+
DartType _interfaceLeastUpperBound(InterfaceType type1, InterfaceType type2) {
// This currently does not implement a very complete least upper bound
// algorithm, but handles a couple of the very common cases that are
diff --git a/pkg/front_end/test/fasta/type_inference/type_schema_environment_test.dart b/pkg/front_end/test/fasta/type_inference/type_schema_environment_test.dart
index fd980dacbd5b..5475095e3e7a 100644
--- a/pkg/front_end/test/fasta/type_inference/type_schema_environment_test.dart
+++ b/pkg/front_end/test/fasta/type_inference/type_schema_environment_test.dart
@@ -259,6 +259,75 @@ class TypeSchemaEnvironmentTest {
expect(env.getGreatestLowerBound(A, B), same(bottomType));
}
+ void test_inferTypeFromConstraints_applyBound() {
+ // class A {}
+ var T = new TypeParameter('T', numType);
+ var A = _addClass(_class('A', typeParameters: [T])).thisType;
+ var env = _makeEnv();
+ {
+ // With no constraints:
+ var constraints = {T: new TypeConstraint()};
+ // Downward inference should infer A>
+ var typesFromDownwardsInference = [null];
+ expect(
+ env.inferTypeFromConstraints(
+ constraints, A, [T], typesFromDownwardsInference,
+ downwardsInferPhase: true),
+ new InterfaceType(A.classNode, [unknownType]));
+ expect(typesFromDownwardsInference[0], unknownType);
+ // Upward inference should infer A
+ expect(
+ env.inferTypeFromConstraints(
+ constraints, A, [T], typesFromDownwardsInference),
+ new InterfaceType(A.classNode, [numType]));
+ }
+ {
+ // With an upper bound of Object:
+ var constraints = {T: _makeConstraint(upper: objectType)};
+ // Downward inference should infer A
+ var typesFromDownwardsInference = [null];
+ expect(
+ env.inferTypeFromConstraints(
+ constraints, A, [T], typesFromDownwardsInference,
+ downwardsInferPhase: true),
+ new InterfaceType(A.classNode, [numType]));
+ expect(typesFromDownwardsInference[0], numType);
+ // Upward inference should infer A
+ expect(
+ env.inferTypeFromConstraints(
+ constraints, A, [T], typesFromDownwardsInference),
+ new InterfaceType(A.classNode, [numType]));
+ // Upward inference should still infer A even if there are more
+ // constraints now, because num was finalized during downward inference.
+ constraints = {T: _makeConstraint(lower: intType, upper: intType)};
+ expect(
+ env.inferTypeFromConstraints(
+ constraints, A, [T], typesFromDownwardsInference),
+ new InterfaceType(A.classNode, [numType]));
+ }
+ }
+
+ void test_inferTypeFromConstraints_simple() {
+ var env = _makeEnv();
+ var T = listClass.typeParameters[0];
+ // With an upper bound of List>:
+ var constraints = {T: _makeConstraint(upper: _list(unknownType))};
+ // Downwards inference should infer List>
+ var typesFromDownwardsInference = [null];
+ expect(
+ env.inferTypeFromConstraints(
+ constraints, listClass.thisType, [T], typesFromDownwardsInference,
+ downwardsInferPhase: true),
+ _list(_list(unknownType)));
+ // And it should have recorded List> as the type inferred for T.
+ expect(typesFromDownwardsInference[0], _list(unknownType));
+ // Upwards inference should refine that to List>
+ expect(
+ env.inferTypeFromConstraints(
+ constraints, listClass.thisType, [T], typesFromDownwardsInference),
+ _list(_list(nullType)));
+ }
+
void test_instantiateToBounds_noTypesKnown() {
// class A {}
var A = _addClass(_class('A')).rawType;