Sort declarations in type_system.dart and type_system_test.dart.

No changes other than moving around classes and methods.

Change-Id: I2bca30b7bd3bba85d0cbeb2ac2e2fe975aa3db62
Reviewed-on: https://dart-review.googlesource.com/53821
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
Commit-Queue: Paul Berry <paulberry@google.com>
diff --git a/pkg/analyzer/lib/src/generated/type_system.dart b/pkg/analyzer/lib/src/generated/type_system.dart
index 068294a..d178f25 100644
--- a/pkg/analyzer/lib/src/generated/type_system.dart
+++ b/pkg/analyzer/lib/src/generated/type_system.dart
@@ -64,6 +64,651 @@
       identical(t, UnknownInferredType.instance);
 }
 
+/// Tracks upper and lower type bounds for a set of type parameters.
+///
+/// This class is used by calling [isSubtypeOf]. When it encounters one of
+/// the type parameters it is inferring, it will record the constraint, and
+/// optimistically assume the constraint will be satisfied.
+///
+/// For example if we are inferring type parameter A, and we ask if
+/// `A <: num`, this will record that A must be a subytpe of `num`. It also
+/// handles cases when A appears as part of the structure of another type, for
+/// example `Iterable<A> <: Iterable<num>` would infer the same constraint
+/// (due to covariant generic types) as would `() -> A <: () -> num`. In
+/// contrast `(A) -> void <: (num) -> void`.
+///
+/// Once the lower/upper bounds are determined, [infer] should be called to
+/// finish the inference. It will instantiate a generic function type with the
+/// inferred types for each type parameter.
+///
+/// It can also optionally compute a partial solution, in case some of the type
+/// parameters could not be inferred (because the constraints cannot be
+/// satisfied), or bail on the inference when this happens.
+///
+/// As currently designed, an instance of this class should only be used to
+/// infer a single call and discarded immediately afterwards.
+class GenericInferrer {
+  final StrongTypeSystemImpl _typeSystem;
+  final TypeProvider typeProvider;
+  final Map<TypeParameterElement, List<_TypeConstraint>> constraints;
+
+  /// Buffer recording constraints recorded while performing a recursive call to
+  /// [_matchSubtypeOf] that might fail, so that any constraints recorded during
+  /// the failed match can be rewound.
+  final _undoBuffer = <_TypeConstraint>[];
+
+  GenericInferrer(this.typeProvider, this._typeSystem,
+      Iterable<TypeParameterElement> typeFormals)
+      : constraints = new HashMap(
+            equals: (x, y) => x.location == y.location,
+            hashCode: (x) => x.location.hashCode) {
+    for (var formal in typeFormals) {
+      constraints[formal] = [];
+    }
+  }
+
+  /// Apply an argument constraint, which asserts that the [argument] staticType
+  /// is a subtype of the [parameterType].
+  void constrainArgument(
+      DartType argumentType, DartType parameterType, String parameterName,
+      {DartType genericType}) {
+    var origin = new _TypeConstraintFromArgument(
+        argumentType, parameterType, parameterName,
+        genericType: genericType);
+    tryMatchSubtypeOf(argumentType, parameterType, origin, covariant: false);
+  }
+
+  /// Constrain a universal function type [fnType] used in a context
+  /// [contextType].
+  void constrainGenericFunctionInContext(
+      FunctionType fnType, DartType contextType) {
+    var origin = new _TypeConstraintFromFunctionContext(fnType, contextType);
+
+    // Since we're trying to infer the instantiation, we want to ignore type
+    // formals as we check the parameters and return type.
+    var inferFnType =
+        fnType.instantiate(TypeParameterTypeImpl.getTypes(fnType.typeFormals));
+    tryMatchSubtypeOf(inferFnType, contextType, origin, covariant: true);
+  }
+
+  /// Apply a return type constraint, which asserts that the [declaredType]
+  /// is a subtype of the [contextType].
+  void constrainReturnType(DartType declaredType, DartType contextType) {
+    var origin = new _TypeConstraintFromReturnType(declaredType, contextType);
+    tryMatchSubtypeOf(declaredType, contextType, origin, covariant: true);
+  }
+
+  /// Given the constraints that were given by calling [isSubtypeOf], find the
+  /// instantiation of the generic function that satisfies these constraints.
+  ///
+  /// If [downwardsInferPhase] is set, we are in the first pass of inference,
+  /// pushing context types down. At that point we are allowed to push down
+  /// `?` to precisely represent an unknown type. If [downwardsInferPhase] is
+  /// false, we are on our final inference pass, have all available information
+  /// including argument types, and must not conclude `?` for any type formal.
+  T infer<T extends ParameterizedType>(
+      T genericType, List<TypeParameterElement> typeFormals,
+      {bool considerExtendsClause: true,
+      ErrorReporter errorReporter,
+      AstNode errorNode,
+      bool downwardsInferPhase: false}) {
+    var fnTypeParams = TypeParameterTypeImpl.getTypes(typeFormals);
+
+    // 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<DartType>.filled(
+        fnTypeParams.length, UnknownInferredType.instance);
+    var _inferTypeParameter = downwardsInferPhase
+        ? _inferTypeParameterFromContext
+        : _inferTypeParameterFromAll;
+
+    for (int i = 0; i < fnTypeParams.length; i++) {
+      TypeParameterType typeParam = fnTypeParams[i];
+
+      var typeParamBound = typeParam.bound;
+      _TypeConstraint extendsClause;
+      if (considerExtendsClause && !typeParamBound.isDynamic) {
+        extendsClause = new _TypeConstraint.fromExtends(typeParam,
+            typeParam.bound.substitute2(inferredTypes, fnTypeParams));
+      }
+
+      inferredTypes[i] =
+          _inferTypeParameter(constraints[typeParam.element], extendsClause);
+    }
+
+    // If the downwards infer phase has failed, we'll catch this in the upwards
+    // phase later on.
+    if (downwardsInferPhase) {
+      return genericType.instantiate(inferredTypes) as T;
+    }
+
+    // Check the inferred types against all of the constraints.
+    var knownTypes = new HashMap<TypeParameterType, DartType>(
+        equals: (x, y) => x.element == y.element,
+        hashCode: (x) => x.element.hashCode);
+    for (int i = 0; i < fnTypeParams.length; i++) {
+      TypeParameterType typeParam = fnTypeParams[i];
+      var constraints = this.constraints[typeParam.element];
+      var typeParamBound =
+          typeParam.bound.substitute2(inferredTypes, fnTypeParams);
+
+      var inferred = inferredTypes[i];
+      bool success =
+          constraints.every((c) => c.isSatisifedBy(_typeSystem, inferred));
+      if (success && !typeParamBound.isDynamic) {
+        // If everything else succeeded, check the `extends` constraint.
+        var extendsConstraint =
+            new _TypeConstraint.fromExtends(typeParam, typeParamBound);
+        constraints.add(extendsConstraint);
+        success = extendsConstraint.isSatisifedBy(_typeSystem, inferred);
+      }
+
+      if (!success) {
+        errorReporter?.reportErrorForNode(
+            StrongModeCode.COULD_NOT_INFER,
+            errorNode,
+            [typeParam, _formatError(typeParam, inferred, constraints)]);
+
+        // 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 (UnknownInferredType.isKnown(inferred)) {
+        knownTypes[typeParam] = inferred;
+      }
+    }
+
+    // Use instantiate to bounds to finish things off.
+    var hasError = new List<bool>.filled(fnTypeParams.length, false);
+    var result = _typeSystem.instantiateToBounds(genericType,
+        hasError: hasError, knownTypes: knownTypes) as T;
+
+    // Report any errors from instantiateToBounds.
+    for (int i = 0; i < hasError.length; i++) {
+      if (hasError[i]) {
+        TypeParameterType typeParam = fnTypeParams[i];
+        var typeParamBound =
+            typeParam.bound.substitute2(inferredTypes, fnTypeParams);
+        // TODO(jmesserly): improve this error message.
+        errorReporter
+            ?.reportErrorForNode(StrongModeCode.COULD_NOT_INFER, errorNode, [
+          typeParam,
+          "\nRecursive bound cannot be instantiated: '$typeParamBound'."
+              "\nConsider passing explicit type argument(s) "
+              "to the generic.\n\n'"
+        ]);
+      }
+    }
+    return result;
+  }
+
+  /// Tries to make [i1] a subtype of [i2] and accumulate constraints as needed.
+  ///
+  /// The return value indicates whether the match was successful.  If it was
+  /// unsuccessful, any constraints that were accumulated during the match
+  /// attempt have been rewound (see [_rewindConstraints]).
+  bool tryMatchSubtypeOf(DartType t1, DartType t2, _TypeConstraintOrigin origin,
+      {bool covariant}) {
+    int previousRewindBufferLength = _undoBuffer.length;
+    bool success = _matchSubtypeOf(t1, t2, null, origin, covariant: covariant);
+    if (!success) {
+      _rewindConstraints(previousRewindBufferLength);
+    }
+    return success;
+  }
+
+  /// Choose the bound that was implied by the return type, if any.
+  ///
+  /// Which bound this is depends on what positions the type parameter
+  /// appears in. If the type only appears only in a contravariant position,
+  /// we will choose the lower bound instead.
+  ///
+  /// For example given:
+  ///
+  ///     Func1<T, bool> makeComparer<T>(T x) => (T y) => x() == y;
+  ///
+  ///     main() {
+  ///       Func1<num, bool> t = makeComparer/* infer <num> */(42);
+  ///       print(t(42.0)); /// false, no error.
+  ///     }
+  ///
+  /// The constraints we collect are:
+  ///
+  /// * `num <: T`
+  /// * `int <: T`
+  ///
+  /// ... and no upper bound. Therefore the lower bound is the best choice.
+  DartType _chooseTypeFromConstraints(Iterable<_TypeConstraint> constraints,
+      {bool toKnownType: false}) {
+    DartType lower = UnknownInferredType.instance;
+    DartType upper = UnknownInferredType.instance;
+    for (var constraint in constraints) {
+      // Given constraints:
+      //
+      //     L1 <: T <: U1
+      //     L2 <: T <: U2
+      //
+      // These can be combined to produce:
+      //
+      //     LUB(L1, L2) <: T <: GLB(U1, U2).
+      //
+      // This can then be done for all constraints in sequence.
+      //
+      // This resulting constraint may be unsatisfiable; in that case inference
+      // will fail.
+      upper = _getGreatestLowerBound(upper, constraint.upperBound);
+      lower = _typeSystem.getLeastUpperBound(lower, constraint.lowerBound);
+    }
+
+    // Prefer the known bound, if any.
+    // Otherwise take whatever bound has partial information, e.g. `Iterable<?>`
+    //
+    // For both of those, prefer the lower bound (arbitrary heuristic).
+    if (UnknownInferredType.isKnown(lower)) {
+      return lower;
+    }
+    if (UnknownInferredType.isKnown(upper)) {
+      return upper;
+    }
+    if (!identical(UnknownInferredType.instance, lower)) {
+      return toKnownType ? _typeSystem.lowerBoundForType(lower) : lower;
+    }
+    if (!identical(UnknownInferredType.instance, upper)) {
+      return toKnownType ? _typeSystem.upperBoundForType(upper) : upper;
+    }
+    return lower;
+  }
+
+  String _formatError(TypeParameterType typeParam, DartType inferred,
+      Iterable<_TypeConstraint> constraints) {
+    var intro = "Tried to infer '$inferred' for '$typeParam'"
+        " which doesn't work:";
+
+    var constraintsByOrigin = <_TypeConstraintOrigin, List<_TypeConstraint>>{};
+    for (var c in constraints) {
+      constraintsByOrigin.putIfAbsent(c.origin, () => []).add(c);
+    }
+
+    // Only report unique constraint origins.
+    Iterable<_TypeConstraint> isSatisified(bool expected) => constraintsByOrigin
+        .values
+        .where((l) =>
+            l.every((c) => c.isSatisifedBy(_typeSystem, inferred)) == expected)
+        .expand((i) => i);
+
+    String unsatisified = _formatConstraints(isSatisified(false));
+    String satisified = _formatConstraints(isSatisified(true));
+
+    assert(unsatisified.isNotEmpty);
+    if (satisified.isNotEmpty) {
+      satisified = "\nThe type '$inferred' was inferred from:\n$satisified";
+    }
+
+    return '\n\n$intro\n$unsatisified$satisified\n\n'
+        'Consider passing explicit type argument(s) to the generic.\n\n';
+  }
+
+  /// This is first calls strong mode's GLB, but if it fails to find anything
+  /// (i.e. returns the bottom type), we kick in a few additional rules:
+  ///
+  /// - `GLB(FutureOr<A>, B)` is defined as:
+  ///   - `GLB(FutureOr<A>, FutureOr<B>) == FutureOr<GLB(A, B)>`
+  ///   - `GLB(FutureOr<A>, Future<B>) == Future<GLB(A, B)>`
+  ///   - else `GLB(FutureOr<A>, B) == GLB(A, B)`
+  /// - `GLB(A, FutureOr<B>) ==  GLB(FutureOr<A>, B)` (defined above),
+  /// - else `GLB(A, B) == Null`
+  DartType _getGreatestLowerBound(DartType t1, DartType t2) {
+    var result = _typeSystem.getGreatestLowerBound(t1, t2);
+    if (result.isBottom) {
+      // See if we can do better by considering FutureOr rules.
+      if (t1 is InterfaceType && t1.isDartAsyncFutureOr) {
+        var t1TypeArg = t1.typeArguments[0];
+        if (t2 is InterfaceType) {
+          //  GLB(FutureOr<A>, FutureOr<B>) == FutureOr<GLB(A, B)>
+          if (t2.isDartAsyncFutureOr) {
+            var t2TypeArg = t2.typeArguments[0];
+            return typeProvider.futureOrType
+                .instantiate([_getGreatestLowerBound(t1TypeArg, t2TypeArg)]);
+          }
+          // GLB(FutureOr<A>, Future<B>) == Future<GLB(A, B)>
+          if (t2.isDartAsyncFuture) {
+            var t2TypeArg = t2.typeArguments[0];
+            return typeProvider.futureType
+                .instantiate([_getGreatestLowerBound(t1TypeArg, t2TypeArg)]);
+          }
+        }
+        // GLB(FutureOr<A>, B) == GLB(A, B)
+        return _getGreatestLowerBound(t1TypeArg, t2);
+      }
+      if (t2 is InterfaceType && t2.isDartAsyncFutureOr) {
+        // GLB(A, FutureOr<B>) ==  GLB(FutureOr<A>, B)
+        return _getGreatestLowerBound(t2, t1);
+      }
+      // TODO(jmesserly): fix this rule once we support non-nullable types.
+      return typeProvider.nullType;
+    }
+    return result;
+  }
+
+  DartType _inferTypeParameterFromAll(
+      List<_TypeConstraint> constraints, _TypeConstraint extendsClause) {
+    // See if we already fixed this type from downwards inference.
+    // If so, then we aren't allowed to change it based on argument types.
+    DartType t = _inferTypeParameterFromContext(
+        constraints.where((c) => c.isDownwards), extendsClause);
+    if (UnknownInferredType.isKnown(t)) {
+      // Remove constraints that aren't downward ones; we'll ignore these for
+      // error reporting, because inference already succeeded.
+      constraints.removeWhere((c) => !c.isDownwards);
+      return t;
+    }
+
+    if (extendsClause != null) {
+      constraints = constraints.toList()..add(extendsClause);
+    }
+
+    var choice = _chooseTypeFromConstraints(constraints, toKnownType: true);
+    return choice;
+  }
+
+  DartType _inferTypeParameterFromContext(
+      Iterable<_TypeConstraint> constraints, _TypeConstraint extendsClause) {
+    DartType t = _chooseTypeFromConstraints(constraints);
+    if (UnknownInferredType.isUnknown(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/*<infer Object, error>*/(1, 2);
+    //
+    // If we consider the `T extends num` we conclude `<num>`, which works.
+    if (extendsClause != null) {
+      constraints = constraints.toList()..add(extendsClause);
+      return _chooseTypeFromConstraints(constraints);
+    }
+    return t;
+  }
+
+  /// Tries to make [i1] a subtype of [i2] and accumulate constraints as needed.
+  ///
+  /// The return value indicates whether the match was successful.  If it was
+  /// unsuccessful, the caller is responsible for ignoring any constraints that
+  /// were accumulated (see [_rewindConstraints]).
+  bool _matchInterfaceSubtypeOf(InterfaceType i1, InterfaceType i2,
+      Set<Element> visited, _TypeConstraintOrigin origin,
+      {bool covariant}) {
+    if (identical(i1, i2)) {
+      return true;
+    }
+
+    if (i1.element == i2.element) {
+      List<DartType> tArgs1 = i1.typeArguments;
+      List<DartType> tArgs2 = i2.typeArguments;
+      assert(tArgs1.length == tArgs2.length);
+      for (int i = 0; i < tArgs1.length; i++) {
+        if (!_matchSubtypeOf(tArgs1[i], tArgs2[i], visited, origin,
+            covariant: covariant)) {
+          return false;
+        }
+      }
+      return true;
+    }
+    if (i1.isObject) {
+      return false;
+    }
+
+    // Guard against loops in the class hierarchy
+    //
+    // TODO(jmesserly): this function isn't guarding against anything (it's not
+    // passsing down `visitedSet`, so adding the element has no effect).
+    //
+    // If that's fixed, it breaks inference tests for types like
+    // `Iterable<Iterable<?>>` matched aganinst `List<List<int>>`.
+    //
+    // The fix is for type arguments (above) to not pass down `visited`, similar
+    // to how _isInterfaceSubtypeOf does not pass down `visited` for type
+    // arguments.
+    bool guardedInterfaceSubtype(InterfaceType t1) {
+      var visitedSet = visited ?? new HashSet<Element>();
+      if (visitedSet.add(t1.element)) {
+        bool matched = _matchInterfaceSubtypeOf(t1, i2, visited, origin,
+            covariant: covariant);
+        visitedSet.remove(t1.element);
+        return matched;
+      } else {
+        // In the case of a recursive type parameter, consider the subtype
+        // match to have failed.
+        return false;
+      }
+    }
+
+    // We don't need to search the entire class hierarchy, since a given
+    // subclass can't appear multiple times with different generic parameters.
+    // So shortcut to the first match found.
+    //
+    // We don't need undo logic here because if the classes don't match, nothing
+    // is added to the constraint set.
+    if (guardedInterfaceSubtype(i1.superclass)) return true;
+    for (final parent in i1.interfaces) {
+      if (guardedInterfaceSubtype(parent)) return true;
+    }
+    for (final parent in i1.mixins) {
+      if (guardedInterfaceSubtype(parent)) return true;
+    }
+    return false;
+  }
+
+  /// Assert that [t1] will be a subtype of [t2], and returns if the constraint
+  /// can be satisfied.
+  ///
+  /// [covariant] must be true if [t1] is a declared type of the generic
+  /// function and [t2] is the context type, or false if the reverse. For
+  /// example [covariant] is used when [t1] is the declared return type
+  /// and [t2] is the context type. Contravariant would be used if [t1] is the
+  /// argument type (i.e. passed in to the generic function) and [t2] is the
+  /// declared parameter type.
+  ///
+  /// [origin] indicates where the constraint came from, for example an argument
+  /// or return type.
+  bool _matchSubtypeOf(DartType t1, DartType t2, Set<Element> visited,
+      _TypeConstraintOrigin origin,
+      {bool covariant}) {
+    if (covariant && t1 is TypeParameterType) {
+      var constraints = this.constraints[t1.element];
+      if (constraints != null) {
+        if (!identical(t2, UnknownInferredType.instance)) {
+          var constraint = new _TypeConstraint(origin, t1, upper: t2);
+          constraints.add(constraint);
+          _undoBuffer.add(constraint);
+        }
+        return true;
+      }
+    }
+    if (!covariant && t2 is TypeParameterType) {
+      var constraints = this.constraints[t2.element];
+      if (constraints != null) {
+        if (!identical(t1, UnknownInferredType.instance)) {
+          var constraint = new _TypeConstraint(origin, t2, lower: t1);
+          constraints.add(constraint);
+          _undoBuffer.add(constraint);
+        }
+        return true;
+      }
+    }
+
+    if (identical(t1, t2)) {
+      return true;
+    }
+
+    // TODO(jmesserly): this logic is taken from subtype.
+    bool matchSubtype(DartType t1, DartType t2) {
+      return _matchSubtypeOf(t1, t2, null, origin, covariant: covariant);
+    }
+
+    // Handle FutureOr<T> union type.
+    if (t1 is InterfaceType && t1.isDartAsyncFutureOr) {
+      var t1TypeArg = t1.typeArguments[0];
+      if (t2 is InterfaceType && t2.isDartAsyncFutureOr) {
+        var t2TypeArg = t2.typeArguments[0];
+        // FutureOr<A> <: FutureOr<B> iff A <: B
+        return matchSubtype(t1TypeArg, t2TypeArg);
+      }
+
+      // given t1 is Future<A> | A, then:
+      // (Future<A> | A) <: t2 iff Future<A> <: t2 and A <: t2.
+      var t1Future = typeProvider.futureType.instantiate([t1TypeArg]);
+      return matchSubtype(t1Future, t2) && matchSubtype(t1TypeArg, t2);
+    }
+
+    if (t2 is InterfaceType && t2.isDartAsyncFutureOr) {
+      // given t2 is Future<A> | A, then:
+      // t1 <: (Future<A> | A) iff t1 <: Future<A> or t1 <: A
+      var t2TypeArg = t2.typeArguments[0];
+      var t2Future = typeProvider.futureType.instantiate([t2TypeArg]);
+
+      // First we try matching `t1 <: Future<A>`.  If that succeeds *and*
+      // records at least one constraint, then we proceed using that constraint.
+      var previousRewindBufferLength = _undoBuffer.length;
+      var success =
+          tryMatchSubtypeOf(t1, t2Future, origin, covariant: covariant);
+
+      if (_undoBuffer.length != previousRewindBufferLength) {
+        // Trying to match `t1 <: Future<A>` succeeded and recorded constraints,
+        // so those are the constraints we want.
+        return true;
+      } else {
+        // Either `t1 <: Future<A>` failed to match, or it matched trivially
+        // without recording any constraints (e.g. because t1 is `Null`).  We
+        // want constraints, because they let us do more precise inference, so
+        // go ahead and try matching `t1 <: A` to see if it records any
+        // constraints.
+        if (tryMatchSubtypeOf(t1, t2TypeArg, origin, covariant: covariant)) {
+          // Trying to match `t1 <: A` succeeded.  If it recorded constraints,
+          // those are the constraints we want.  If it didn't, then there's no
+          // way we're going to get any constraints.  So either way, we want to
+          // return `true` since the match suceeded and the constraints we want
+          // (if any) have been recorded.
+          return true;
+        } else {
+          // Trying to match `t1 <: A` failed.  So there's no way we are going
+          // to get any constraints.  Just return `success` to indicate whether
+          // the match succeeded.
+          return success;
+        }
+      }
+    }
+
+    // S <: T where S is a type variable
+    //  T is not dynamic or object (handled above)
+    //  True if T == S
+    //  Or true if bound of S is S' and S' <: T
+
+    if (t1 is TypeParameterType) {
+      // Guard against recursive type parameters
+      //
+      // TODO(jmesserly): this function isn't guarding against anything (it's
+      // not passsing down `visitedSet`, so adding the element has no effect).
+      bool guardedSubtype(DartType t1, DartType t2) {
+        var visitedSet = visited ?? new HashSet<Element>();
+        if (visitedSet.add(t1.element)) {
+          bool matched = matchSubtype(t1, t2);
+          visitedSet.remove(t1.element);
+          return matched;
+        } else {
+          // In the case of a recursive type parameter, consider the subtype
+          // match to have failed.
+          return false;
+        }
+      }
+
+      if (t2 is TypeParameterType && t1.definition == t2.definition) {
+        return guardedSubtype(t1.bound, t2.bound);
+      }
+      return guardedSubtype(t1.bound, t2);
+    }
+    if (t2 is TypeParameterType) {
+      return false;
+    }
+
+    if (_isBottom(t1) || _isTop(t2)) return true;
+
+    if (t1 is InterfaceType && t2 is InterfaceType) {
+      return _matchInterfaceSubtypeOf(t1, t2, visited, origin,
+          covariant: covariant);
+    }
+
+    if (t1 is FunctionType && t2 is FunctionType) {
+      return FunctionTypeImpl.relate(
+          t1,
+          t2,
+          (t1, t2) {
+            // TODO(jmesserly): should we flip covariance when we're relating
+            // type formal bounds? They're more like parameters.
+            return matchSubtype(t1, t2);
+          },
+          _typeSystem.instantiateToBounds,
+          parameterRelation: (p1, p2) {
+            return _matchSubtypeOf(p2.type, p1.type, null, origin,
+                covariant: !covariant);
+          });
+    }
+
+    if (t1 is FunctionType && t2 == typeProvider.functionType) {
+      return true;
+    }
+
+    return false;
+  }
+
+  /// Un-does constraints that were gathered by a failed match attempt, until
+  /// [_undoBuffer] has length [previousRewindBufferLength].
+  ///
+  /// The intended usage is that the caller should record the length of
+  /// [_undoBuffer] before attempting to make a match.  Then, if the match
+  /// fails, pass the recorded length to this method to erase any constraints
+  /// that were recorded during the failed match.
+  void _rewindConstraints(int previousRewindBufferLength) {
+    while (_undoBuffer.length > previousRewindBufferLength) {
+      var constraint = _undoBuffer.removeLast();
+      var element = constraint.typeParameter.element;
+      assert(identical(constraints[element].last, constraint));
+      constraints[element].removeLast();
+    }
+  }
+
+  static String _formatConstraints(Iterable<_TypeConstraint> constraints) {
+    List<List<String>> lineParts =
+        new Set<_TypeConstraintOrigin>.from(constraints.map((c) => c.origin))
+            .map((o) => o.formatError())
+            .toList();
+
+    int prefixMax = lineParts.map((p) => p[0].length).fold(0, math.max);
+
+    // Use a set to prevent identical message lines.
+    // (It's not uncommon for the same constraint to show up in a few places.)
+    var messageLines = new Set<String>.from(lineParts.map((parts) {
+      var prefix = parts[0];
+      var middle = parts[1];
+      var prefixPad = ' ' * (prefixMax - prefix.length);
+      var middlePad = ' ' * (prefixMax);
+      var end = "";
+      if (parts.length > 2) {
+        end = '\n  $middlePad ${parts[2]}';
+      }
+      return '  $prefix$prefixPad $middle$end';
+    }));
+
+    return messageLines.join('\n');
+  }
+}
+
 /**
  * Implementation of [TypeSystem] using the strong mode rules.
  * https://github.com/dart-lang/dev_compiler/blob/master/STRONG_MODE.md
@@ -100,20 +745,6 @@
   @override
   bool get isStrong => true;
 
-  bool anyParameterType(FunctionType ft, bool predicate(DartType t)) {
-    return ft.parameters.any((p) => predicate(p.type));
-  }
-
-  /// 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;
-    }
-    return null;
-  }
-
   /// Returns true iff the type [t] accepts function types, and requires an
   /// implicit coercion if interface types with a `call` method are passed in.
   ///
@@ -131,6 +762,20 @@
     return t is FunctionType || t.isDartCoreFunction;
   }
 
+  bool anyParameterType(FunctionType ft, bool predicate(DartType t)) {
+    return ft.parameters.any((p) => predicate(p.type));
+  }
+
+  /// 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;
+    }
+    return null;
+  }
+
   /// Computes the greatest lower bound of [type1] and [type2].
   DartType getGreatestLowerBound(DartType type1, DartType type2) {
     // The greatest lower bound relation is reflexive.
@@ -550,6 +1195,91 @@
         p1.isCovariant && isSubtypeOf(p1.type, p2.type);
   }
 
+  @override
+  bool isSubtypeOf(DartType t1, DartType t2) {
+    if (identical(t1, t2)) {
+      return true;
+    }
+
+    // The types are void, dynamic, bottom, interface types, function types,
+    // FutureOr<T> and type parameters.
+    //
+    // We proceed by eliminating these different classes from consideration.
+
+    // Trivially true.
+    //
+    // Note that `?` is treated as a top and a bottom type during inference,
+    // so it's also covered here.
+    if (_isTop(t2) || _isBottom(t1)) {
+      return true;
+    }
+
+    // Trivially false.
+    if (_isTop(t1) || _isBottom(t2)) {
+      return false;
+    }
+
+    // Handle FutureOr<T> union type.
+    if (t1 is InterfaceType && t1.isDartAsyncFutureOr) {
+      var t1TypeArg = t1.typeArguments[0];
+      if (t2 is InterfaceType && t2.isDartAsyncFutureOr) {
+        var t2TypeArg = t2.typeArguments[0];
+        // FutureOr<A> <: FutureOr<B> iff A <: B
+        return isSubtypeOf(t1TypeArg, t2TypeArg);
+      }
+
+      // given t1 is Future<A> | A, then:
+      // (Future<A> | A) <: t2 iff Future<A> <: t2 and A <: t2.
+      var t1Future = typeProvider.futureType.instantiate([t1TypeArg]);
+      return isSubtypeOf(t1Future, t2) && isSubtypeOf(t1TypeArg, t2);
+    }
+
+    if (t2 is InterfaceType && t2.isDartAsyncFutureOr) {
+      // given t2 is Future<A> | A, then:
+      // t1 <: (Future<A> | A) iff t1 <: Future<A> or t1 <: A
+      var t2TypeArg = t2.typeArguments[0];
+      var t2Future = typeProvider.futureType.instantiate([t2TypeArg]);
+      return isSubtypeOf(t1, t2Future) || isSubtypeOf(t1, t2TypeArg);
+    }
+
+    // S <: T where S is a type variable
+    //  T is not dynamic or object (handled above)
+    //  True if T == S
+    //  Or true if bound of S is S' and S' <: T
+    if (t1 is TypeParameterType) {
+      if (t2 is TypeParameterType &&
+          t1.definition == t2.definition &&
+          _typeParameterBoundsSubtype(t1.bound, t2.bound, true)) {
+        return true;
+      }
+      DartType bound = t1.element.bound;
+      return bound == null
+          ? false
+          : _typeParameterBoundsSubtype(bound, t2, false);
+    }
+    if (t2 is TypeParameterType) {
+      return false;
+    }
+
+    // We've eliminated void, dynamic, bottom, type parameters, and FutureOr.
+    // The only cases are the combinations of interface type and function type.
+
+    // A function type can only subtype an interface type if
+    // the interface type is Function
+    if (t1 is FunctionType && t2 is InterfaceType) {
+      return t2.isDartCoreFunction;
+    }
+
+    if (t1 is InterfaceType && t2 is FunctionType) return false;
+
+    // Two interface types
+    if (t1 is InterfaceType && t2 is InterfaceType) {
+      return _isInterfaceSubtypeOf(t1, t2, null);
+    }
+
+    return _isFunctionSubtypeOf(t1, t2);
+  }
+
   /// Given a [type] T that may have an unknown type `?`, returns a type
   /// R such that R <: T for any type substituted for `?`.
   ///
@@ -857,91 +1587,6 @@
     return false;
   }
 
-  @override
-  bool isSubtypeOf(DartType t1, DartType t2) {
-    if (identical(t1, t2)) {
-      return true;
-    }
-
-    // The types are void, dynamic, bottom, interface types, function types,
-    // FutureOr<T> and type parameters.
-    //
-    // We proceed by eliminating these different classes from consideration.
-
-    // Trivially true.
-    //
-    // Note that `?` is treated as a top and a bottom type during inference,
-    // so it's also covered here.
-    if (_isTop(t2) || _isBottom(t1)) {
-      return true;
-    }
-
-    // Trivially false.
-    if (_isTop(t1) || _isBottom(t2)) {
-      return false;
-    }
-
-    // Handle FutureOr<T> union type.
-    if (t1 is InterfaceType && t1.isDartAsyncFutureOr) {
-      var t1TypeArg = t1.typeArguments[0];
-      if (t2 is InterfaceType && t2.isDartAsyncFutureOr) {
-        var t2TypeArg = t2.typeArguments[0];
-        // FutureOr<A> <: FutureOr<B> iff A <: B
-        return isSubtypeOf(t1TypeArg, t2TypeArg);
-      }
-
-      // given t1 is Future<A> | A, then:
-      // (Future<A> | A) <: t2 iff Future<A> <: t2 and A <: t2.
-      var t1Future = typeProvider.futureType.instantiate([t1TypeArg]);
-      return isSubtypeOf(t1Future, t2) && isSubtypeOf(t1TypeArg, t2);
-    }
-
-    if (t2 is InterfaceType && t2.isDartAsyncFutureOr) {
-      // given t2 is Future<A> | A, then:
-      // t1 <: (Future<A> | A) iff t1 <: Future<A> or t1 <: A
-      var t2TypeArg = t2.typeArguments[0];
-      var t2Future = typeProvider.futureType.instantiate([t2TypeArg]);
-      return isSubtypeOf(t1, t2Future) || isSubtypeOf(t1, t2TypeArg);
-    }
-
-    // S <: T where S is a type variable
-    //  T is not dynamic or object (handled above)
-    //  True if T == S
-    //  Or true if bound of S is S' and S' <: T
-    if (t1 is TypeParameterType) {
-      if (t2 is TypeParameterType &&
-          t1.definition == t2.definition &&
-          _typeParameterBoundsSubtype(t1.bound, t2.bound, true)) {
-        return true;
-      }
-      DartType bound = t1.element.bound;
-      return bound == null
-          ? false
-          : _typeParameterBoundsSubtype(bound, t2, false);
-    }
-    if (t2 is TypeParameterType) {
-      return false;
-    }
-
-    // We've eliminated void, dynamic, bottom, type parameters, and FutureOr.
-    // The only cases are the combinations of interface type and function type.
-
-    // A function type can only subtype an interface type if
-    // the interface type is Function
-    if (t1 is FunctionType && t2 is InterfaceType) {
-      return t2.isDartCoreFunction;
-    }
-
-    if (t1 is InterfaceType && t2 is FunctionType) return false;
-
-    // Two interface types
-    if (t1 is InterfaceType && t2 is InterfaceType) {
-      return _isInterfaceSubtypeOf(t1, t2, null);
-    }
-
-    return _isFunctionSubtypeOf(t1, t2);
-  }
-
   DartType _substituteForUnknownType(DartType type, {bool lowerBound: false}) {
     if (identical(type, UnknownInferredType.instance)) {
       if (lowerBound) {
@@ -1660,651 +2305,6 @@
   T accept<T>(ElementVisitor visitor) => null;
 }
 
-/// Tracks upper and lower type bounds for a set of type parameters.
-///
-/// This class is used by calling [isSubtypeOf]. When it encounters one of
-/// the type parameters it is inferring, it will record the constraint, and
-/// optimistically assume the constraint will be satisfied.
-///
-/// For example if we are inferring type parameter A, and we ask if
-/// `A <: num`, this will record that A must be a subytpe of `num`. It also
-/// handles cases when A appears as part of the structure of another type, for
-/// example `Iterable<A> <: Iterable<num>` would infer the same constraint
-/// (due to covariant generic types) as would `() -> A <: () -> num`. In
-/// contrast `(A) -> void <: (num) -> void`.
-///
-/// Once the lower/upper bounds are determined, [infer] should be called to
-/// finish the inference. It will instantiate a generic function type with the
-/// inferred types for each type parameter.
-///
-/// It can also optionally compute a partial solution, in case some of the type
-/// parameters could not be inferred (because the constraints cannot be
-/// satisfied), or bail on the inference when this happens.
-///
-/// As currently designed, an instance of this class should only be used to
-/// infer a single call and discarded immediately afterwards.
-class GenericInferrer {
-  final StrongTypeSystemImpl _typeSystem;
-  final TypeProvider typeProvider;
-  final Map<TypeParameterElement, List<_TypeConstraint>> constraints;
-
-  /// Buffer recording constraints recorded while performing a recursive call to
-  /// [_matchSubtypeOf] that might fail, so that any constraints recorded during
-  /// the failed match can be rewound.
-  final _undoBuffer = <_TypeConstraint>[];
-
-  GenericInferrer(this.typeProvider, this._typeSystem,
-      Iterable<TypeParameterElement> typeFormals)
-      : constraints = new HashMap(
-            equals: (x, y) => x.location == y.location,
-            hashCode: (x) => x.location.hashCode) {
-    for (var formal in typeFormals) {
-      constraints[formal] = [];
-    }
-  }
-
-  /// Apply an argument constraint, which asserts that the [argument] staticType
-  /// is a subtype of the [parameterType].
-  void constrainArgument(
-      DartType argumentType, DartType parameterType, String parameterName,
-      {DartType genericType}) {
-    var origin = new _TypeConstraintFromArgument(
-        argumentType, parameterType, parameterName,
-        genericType: genericType);
-    tryMatchSubtypeOf(argumentType, parameterType, origin, covariant: false);
-  }
-
-  /// Constrain a universal function type [fnType] used in a context
-  /// [contextType].
-  void constrainGenericFunctionInContext(
-      FunctionType fnType, DartType contextType) {
-    var origin = new _TypeConstraintFromFunctionContext(fnType, contextType);
-
-    // Since we're trying to infer the instantiation, we want to ignore type
-    // formals as we check the parameters and return type.
-    var inferFnType =
-        fnType.instantiate(TypeParameterTypeImpl.getTypes(fnType.typeFormals));
-    tryMatchSubtypeOf(inferFnType, contextType, origin, covariant: true);
-  }
-
-  /// Apply a return type constraint, which asserts that the [declaredType]
-  /// is a subtype of the [contextType].
-  void constrainReturnType(DartType declaredType, DartType contextType) {
-    var origin = new _TypeConstraintFromReturnType(declaredType, contextType);
-    tryMatchSubtypeOf(declaredType, contextType, origin, covariant: true);
-  }
-
-  /// Given the constraints that were given by calling [isSubtypeOf], find the
-  /// instantiation of the generic function that satisfies these constraints.
-  ///
-  /// If [downwardsInferPhase] is set, we are in the first pass of inference,
-  /// pushing context types down. At that point we are allowed to push down
-  /// `?` to precisely represent an unknown type. If [downwardsInferPhase] is
-  /// false, we are on our final inference pass, have all available information
-  /// including argument types, and must not conclude `?` for any type formal.
-  T infer<T extends ParameterizedType>(
-      T genericType, List<TypeParameterElement> typeFormals,
-      {bool considerExtendsClause: true,
-      ErrorReporter errorReporter,
-      AstNode errorNode,
-      bool downwardsInferPhase: false}) {
-    var fnTypeParams = TypeParameterTypeImpl.getTypes(typeFormals);
-
-    // 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<DartType>.filled(
-        fnTypeParams.length, UnknownInferredType.instance);
-    var _inferTypeParameter = downwardsInferPhase
-        ? _inferTypeParameterFromContext
-        : _inferTypeParameterFromAll;
-
-    for (int i = 0; i < fnTypeParams.length; i++) {
-      TypeParameterType typeParam = fnTypeParams[i];
-
-      var typeParamBound = typeParam.bound;
-      _TypeConstraint extendsClause;
-      if (considerExtendsClause && !typeParamBound.isDynamic) {
-        extendsClause = new _TypeConstraint.fromExtends(typeParam,
-            typeParam.bound.substitute2(inferredTypes, fnTypeParams));
-      }
-
-      inferredTypes[i] =
-          _inferTypeParameter(constraints[typeParam.element], extendsClause);
-    }
-
-    // If the downwards infer phase has failed, we'll catch this in the upwards
-    // phase later on.
-    if (downwardsInferPhase) {
-      return genericType.instantiate(inferredTypes) as T;
-    }
-
-    // Check the inferred types against all of the constraints.
-    var knownTypes = new HashMap<TypeParameterType, DartType>(
-        equals: (x, y) => x.element == y.element,
-        hashCode: (x) => x.element.hashCode);
-    for (int i = 0; i < fnTypeParams.length; i++) {
-      TypeParameterType typeParam = fnTypeParams[i];
-      var constraints = this.constraints[typeParam.element];
-      var typeParamBound =
-          typeParam.bound.substitute2(inferredTypes, fnTypeParams);
-
-      var inferred = inferredTypes[i];
-      bool success =
-          constraints.every((c) => c.isSatisifedBy(_typeSystem, inferred));
-      if (success && !typeParamBound.isDynamic) {
-        // If everything else succeeded, check the `extends` constraint.
-        var extendsConstraint =
-            new _TypeConstraint.fromExtends(typeParam, typeParamBound);
-        constraints.add(extendsConstraint);
-        success = extendsConstraint.isSatisifedBy(_typeSystem, inferred);
-      }
-
-      if (!success) {
-        errorReporter?.reportErrorForNode(
-            StrongModeCode.COULD_NOT_INFER,
-            errorNode,
-            [typeParam, _formatError(typeParam, inferred, constraints)]);
-
-        // 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 (UnknownInferredType.isKnown(inferred)) {
-        knownTypes[typeParam] = inferred;
-      }
-    }
-
-    // Use instantiate to bounds to finish things off.
-    var hasError = new List<bool>.filled(fnTypeParams.length, false);
-    var result = _typeSystem.instantiateToBounds(genericType,
-        hasError: hasError, knownTypes: knownTypes) as T;
-
-    // Report any errors from instantiateToBounds.
-    for (int i = 0; i < hasError.length; i++) {
-      if (hasError[i]) {
-        TypeParameterType typeParam = fnTypeParams[i];
-        var typeParamBound =
-            typeParam.bound.substitute2(inferredTypes, fnTypeParams);
-        // TODO(jmesserly): improve this error message.
-        errorReporter
-            ?.reportErrorForNode(StrongModeCode.COULD_NOT_INFER, errorNode, [
-          typeParam,
-          "\nRecursive bound cannot be instantiated: '$typeParamBound'."
-              "\nConsider passing explicit type argument(s) "
-              "to the generic.\n\n'"
-        ]);
-      }
-    }
-    return result;
-  }
-
-  /// Choose the bound that was implied by the return type, if any.
-  ///
-  /// Which bound this is depends on what positions the type parameter
-  /// appears in. If the type only appears only in a contravariant position,
-  /// we will choose the lower bound instead.
-  ///
-  /// For example given:
-  ///
-  ///     Func1<T, bool> makeComparer<T>(T x) => (T y) => x() == y;
-  ///
-  ///     main() {
-  ///       Func1<num, bool> t = makeComparer/* infer <num> */(42);
-  ///       print(t(42.0)); /// false, no error.
-  ///     }
-  ///
-  /// The constraints we collect are:
-  ///
-  /// * `num <: T`
-  /// * `int <: T`
-  ///
-  /// ... and no upper bound. Therefore the lower bound is the best choice.
-  DartType _chooseTypeFromConstraints(Iterable<_TypeConstraint> constraints,
-      {bool toKnownType: false}) {
-    DartType lower = UnknownInferredType.instance;
-    DartType upper = UnknownInferredType.instance;
-    for (var constraint in constraints) {
-      // Given constraints:
-      //
-      //     L1 <: T <: U1
-      //     L2 <: T <: U2
-      //
-      // These can be combined to produce:
-      //
-      //     LUB(L1, L2) <: T <: GLB(U1, U2).
-      //
-      // This can then be done for all constraints in sequence.
-      //
-      // This resulting constraint may be unsatisfiable; in that case inference
-      // will fail.
-      upper = _getGreatestLowerBound(upper, constraint.upperBound);
-      lower = _typeSystem.getLeastUpperBound(lower, constraint.lowerBound);
-    }
-
-    // Prefer the known bound, if any.
-    // Otherwise take whatever bound has partial information, e.g. `Iterable<?>`
-    //
-    // For both of those, prefer the lower bound (arbitrary heuristic).
-    if (UnknownInferredType.isKnown(lower)) {
-      return lower;
-    }
-    if (UnknownInferredType.isKnown(upper)) {
-      return upper;
-    }
-    if (!identical(UnknownInferredType.instance, lower)) {
-      return toKnownType ? _typeSystem.lowerBoundForType(lower) : lower;
-    }
-    if (!identical(UnknownInferredType.instance, upper)) {
-      return toKnownType ? _typeSystem.upperBoundForType(upper) : upper;
-    }
-    return lower;
-  }
-
-  String _formatError(TypeParameterType typeParam, DartType inferred,
-      Iterable<_TypeConstraint> constraints) {
-    var intro = "Tried to infer '$inferred' for '$typeParam'"
-        " which doesn't work:";
-
-    var constraintsByOrigin = <_TypeConstraintOrigin, List<_TypeConstraint>>{};
-    for (var c in constraints) {
-      constraintsByOrigin.putIfAbsent(c.origin, () => []).add(c);
-    }
-
-    // Only report unique constraint origins.
-    Iterable<_TypeConstraint> isSatisified(bool expected) => constraintsByOrigin
-        .values
-        .where((l) =>
-            l.every((c) => c.isSatisifedBy(_typeSystem, inferred)) == expected)
-        .expand((i) => i);
-
-    String unsatisified = _formatConstraints(isSatisified(false));
-    String satisified = _formatConstraints(isSatisified(true));
-
-    assert(unsatisified.isNotEmpty);
-    if (satisified.isNotEmpty) {
-      satisified = "\nThe type '$inferred' was inferred from:\n$satisified";
-    }
-
-    return '\n\n$intro\n$unsatisified$satisified\n\n'
-        'Consider passing explicit type argument(s) to the generic.\n\n';
-  }
-
-  /// This is first calls strong mode's GLB, but if it fails to find anything
-  /// (i.e. returns the bottom type), we kick in a few additional rules:
-  ///
-  /// - `GLB(FutureOr<A>, B)` is defined as:
-  ///   - `GLB(FutureOr<A>, FutureOr<B>) == FutureOr<GLB(A, B)>`
-  ///   - `GLB(FutureOr<A>, Future<B>) == Future<GLB(A, B)>`
-  ///   - else `GLB(FutureOr<A>, B) == GLB(A, B)`
-  /// - `GLB(A, FutureOr<B>) ==  GLB(FutureOr<A>, B)` (defined above),
-  /// - else `GLB(A, B) == Null`
-  DartType _getGreatestLowerBound(DartType t1, DartType t2) {
-    var result = _typeSystem.getGreatestLowerBound(t1, t2);
-    if (result.isBottom) {
-      // See if we can do better by considering FutureOr rules.
-      if (t1 is InterfaceType && t1.isDartAsyncFutureOr) {
-        var t1TypeArg = t1.typeArguments[0];
-        if (t2 is InterfaceType) {
-          //  GLB(FutureOr<A>, FutureOr<B>) == FutureOr<GLB(A, B)>
-          if (t2.isDartAsyncFutureOr) {
-            var t2TypeArg = t2.typeArguments[0];
-            return typeProvider.futureOrType
-                .instantiate([_getGreatestLowerBound(t1TypeArg, t2TypeArg)]);
-          }
-          // GLB(FutureOr<A>, Future<B>) == Future<GLB(A, B)>
-          if (t2.isDartAsyncFuture) {
-            var t2TypeArg = t2.typeArguments[0];
-            return typeProvider.futureType
-                .instantiate([_getGreatestLowerBound(t1TypeArg, t2TypeArg)]);
-          }
-        }
-        // GLB(FutureOr<A>, B) == GLB(A, B)
-        return _getGreatestLowerBound(t1TypeArg, t2);
-      }
-      if (t2 is InterfaceType && t2.isDartAsyncFutureOr) {
-        // GLB(A, FutureOr<B>) ==  GLB(FutureOr<A>, B)
-        return _getGreatestLowerBound(t2, t1);
-      }
-      // TODO(jmesserly): fix this rule once we support non-nullable types.
-      return typeProvider.nullType;
-    }
-    return result;
-  }
-
-  DartType _inferTypeParameterFromAll(
-      List<_TypeConstraint> constraints, _TypeConstraint extendsClause) {
-    // See if we already fixed this type from downwards inference.
-    // If so, then we aren't allowed to change it based on argument types.
-    DartType t = _inferTypeParameterFromContext(
-        constraints.where((c) => c.isDownwards), extendsClause);
-    if (UnknownInferredType.isKnown(t)) {
-      // Remove constraints that aren't downward ones; we'll ignore these for
-      // error reporting, because inference already succeeded.
-      constraints.removeWhere((c) => !c.isDownwards);
-      return t;
-    }
-
-    if (extendsClause != null) {
-      constraints = constraints.toList()..add(extendsClause);
-    }
-
-    var choice = _chooseTypeFromConstraints(constraints, toKnownType: true);
-    return choice;
-  }
-
-  DartType _inferTypeParameterFromContext(
-      Iterable<_TypeConstraint> constraints, _TypeConstraint extendsClause) {
-    DartType t = _chooseTypeFromConstraints(constraints);
-    if (UnknownInferredType.isUnknown(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/*<infer Object, error>*/(1, 2);
-    //
-    // If we consider the `T extends num` we conclude `<num>`, which works.
-    if (extendsClause != null) {
-      constraints = constraints.toList()..add(extendsClause);
-      return _chooseTypeFromConstraints(constraints);
-    }
-    return t;
-  }
-
-  /// Tries to make [i1] a subtype of [i2] and accumulate constraints as needed.
-  ///
-  /// The return value indicates whether the match was successful.  If it was
-  /// unsuccessful, the caller is responsible for ignoring any constraints that
-  /// were accumulated (see [_rewindConstraints]).
-  bool _matchInterfaceSubtypeOf(InterfaceType i1, InterfaceType i2,
-      Set<Element> visited, _TypeConstraintOrigin origin,
-      {bool covariant}) {
-    if (identical(i1, i2)) {
-      return true;
-    }
-
-    if (i1.element == i2.element) {
-      List<DartType> tArgs1 = i1.typeArguments;
-      List<DartType> tArgs2 = i2.typeArguments;
-      assert(tArgs1.length == tArgs2.length);
-      for (int i = 0; i < tArgs1.length; i++) {
-        if (!_matchSubtypeOf(tArgs1[i], tArgs2[i], visited, origin,
-            covariant: covariant)) {
-          return false;
-        }
-      }
-      return true;
-    }
-    if (i1.isObject) {
-      return false;
-    }
-
-    // Guard against loops in the class hierarchy
-    //
-    // TODO(jmesserly): this function isn't guarding against anything (it's not
-    // passsing down `visitedSet`, so adding the element has no effect).
-    //
-    // If that's fixed, it breaks inference tests for types like
-    // `Iterable<Iterable<?>>` matched aganinst `List<List<int>>`.
-    //
-    // The fix is for type arguments (above) to not pass down `visited`, similar
-    // to how _isInterfaceSubtypeOf does not pass down `visited` for type
-    // arguments.
-    bool guardedInterfaceSubtype(InterfaceType t1) {
-      var visitedSet = visited ?? new HashSet<Element>();
-      if (visitedSet.add(t1.element)) {
-        bool matched = _matchInterfaceSubtypeOf(t1, i2, visited, origin,
-            covariant: covariant);
-        visitedSet.remove(t1.element);
-        return matched;
-      } else {
-        // In the case of a recursive type parameter, consider the subtype
-        // match to have failed.
-        return false;
-      }
-    }
-
-    // We don't need to search the entire class hierarchy, since a given
-    // subclass can't appear multiple times with different generic parameters.
-    // So shortcut to the first match found.
-    //
-    // We don't need undo logic here because if the classes don't match, nothing
-    // is added to the constraint set.
-    if (guardedInterfaceSubtype(i1.superclass)) return true;
-    for (final parent in i1.interfaces) {
-      if (guardedInterfaceSubtype(parent)) return true;
-    }
-    for (final parent in i1.mixins) {
-      if (guardedInterfaceSubtype(parent)) return true;
-    }
-    return false;
-  }
-
-  /// Tries to make [i1] a subtype of [i2] and accumulate constraints as needed.
-  ///
-  /// The return value indicates whether the match was successful.  If it was
-  /// unsuccessful, any constraints that were accumulated during the match
-  /// attempt have been rewound (see [_rewindConstraints]).
-  bool tryMatchSubtypeOf(DartType t1, DartType t2, _TypeConstraintOrigin origin,
-      {bool covariant}) {
-    int previousRewindBufferLength = _undoBuffer.length;
-    bool success = _matchSubtypeOf(t1, t2, null, origin, covariant: covariant);
-    if (!success) {
-      _rewindConstraints(previousRewindBufferLength);
-    }
-    return success;
-  }
-
-  /// Assert that [t1] will be a subtype of [t2], and returns if the constraint
-  /// can be satisfied.
-  ///
-  /// [covariant] must be true if [t1] is a declared type of the generic
-  /// function and [t2] is the context type, or false if the reverse. For
-  /// example [covariant] is used when [t1] is the declared return type
-  /// and [t2] is the context type. Contravariant would be used if [t1] is the
-  /// argument type (i.e. passed in to the generic function) and [t2] is the
-  /// declared parameter type.
-  ///
-  /// [origin] indicates where the constraint came from, for example an argument
-  /// or return type.
-  bool _matchSubtypeOf(DartType t1, DartType t2, Set<Element> visited,
-      _TypeConstraintOrigin origin,
-      {bool covariant}) {
-    if (covariant && t1 is TypeParameterType) {
-      var constraints = this.constraints[t1.element];
-      if (constraints != null) {
-        if (!identical(t2, UnknownInferredType.instance)) {
-          var constraint = new _TypeConstraint(origin, t1, upper: t2);
-          constraints.add(constraint);
-          _undoBuffer.add(constraint);
-        }
-        return true;
-      }
-    }
-    if (!covariant && t2 is TypeParameterType) {
-      var constraints = this.constraints[t2.element];
-      if (constraints != null) {
-        if (!identical(t1, UnknownInferredType.instance)) {
-          var constraint = new _TypeConstraint(origin, t2, lower: t1);
-          constraints.add(constraint);
-          _undoBuffer.add(constraint);
-        }
-        return true;
-      }
-    }
-
-    if (identical(t1, t2)) {
-      return true;
-    }
-
-    // TODO(jmesserly): this logic is taken from subtype.
-    bool matchSubtype(DartType t1, DartType t2) {
-      return _matchSubtypeOf(t1, t2, null, origin, covariant: covariant);
-    }
-
-    // Handle FutureOr<T> union type.
-    if (t1 is InterfaceType && t1.isDartAsyncFutureOr) {
-      var t1TypeArg = t1.typeArguments[0];
-      if (t2 is InterfaceType && t2.isDartAsyncFutureOr) {
-        var t2TypeArg = t2.typeArguments[0];
-        // FutureOr<A> <: FutureOr<B> iff A <: B
-        return matchSubtype(t1TypeArg, t2TypeArg);
-      }
-
-      // given t1 is Future<A> | A, then:
-      // (Future<A> | A) <: t2 iff Future<A> <: t2 and A <: t2.
-      var t1Future = typeProvider.futureType.instantiate([t1TypeArg]);
-      return matchSubtype(t1Future, t2) && matchSubtype(t1TypeArg, t2);
-    }
-
-    if (t2 is InterfaceType && t2.isDartAsyncFutureOr) {
-      // given t2 is Future<A> | A, then:
-      // t1 <: (Future<A> | A) iff t1 <: Future<A> or t1 <: A
-      var t2TypeArg = t2.typeArguments[0];
-      var t2Future = typeProvider.futureType.instantiate([t2TypeArg]);
-
-      // First we try matching `t1 <: Future<A>`.  If that succeeds *and*
-      // records at least one constraint, then we proceed using that constraint.
-      var previousRewindBufferLength = _undoBuffer.length;
-      var success =
-          tryMatchSubtypeOf(t1, t2Future, origin, covariant: covariant);
-
-      if (_undoBuffer.length != previousRewindBufferLength) {
-        // Trying to match `t1 <: Future<A>` succeeded and recorded constraints,
-        // so those are the constraints we want.
-        return true;
-      } else {
-        // Either `t1 <: Future<A>` failed to match, or it matched trivially
-        // without recording any constraints (e.g. because t1 is `Null`).  We
-        // want constraints, because they let us do more precise inference, so
-        // go ahead and try matching `t1 <: A` to see if it records any
-        // constraints.
-        if (tryMatchSubtypeOf(t1, t2TypeArg, origin, covariant: covariant)) {
-          // Trying to match `t1 <: A` succeeded.  If it recorded constraints,
-          // those are the constraints we want.  If it didn't, then there's no
-          // way we're going to get any constraints.  So either way, we want to
-          // return `true` since the match suceeded and the constraints we want
-          // (if any) have been recorded.
-          return true;
-        } else {
-          // Trying to match `t1 <: A` failed.  So there's no way we are going
-          // to get any constraints.  Just return `success` to indicate whether
-          // the match succeeded.
-          return success;
-        }
-      }
-    }
-
-    // S <: T where S is a type variable
-    //  T is not dynamic or object (handled above)
-    //  True if T == S
-    //  Or true if bound of S is S' and S' <: T
-
-    if (t1 is TypeParameterType) {
-      // Guard against recursive type parameters
-      //
-      // TODO(jmesserly): this function isn't guarding against anything (it's
-      // not passsing down `visitedSet`, so adding the element has no effect).
-      bool guardedSubtype(DartType t1, DartType t2) {
-        var visitedSet = visited ?? new HashSet<Element>();
-        if (visitedSet.add(t1.element)) {
-          bool matched = matchSubtype(t1, t2);
-          visitedSet.remove(t1.element);
-          return matched;
-        } else {
-          // In the case of a recursive type parameter, consider the subtype
-          // match to have failed.
-          return false;
-        }
-      }
-
-      if (t2 is TypeParameterType && t1.definition == t2.definition) {
-        return guardedSubtype(t1.bound, t2.bound);
-      }
-      return guardedSubtype(t1.bound, t2);
-    }
-    if (t2 is TypeParameterType) {
-      return false;
-    }
-
-    if (_isBottom(t1) || _isTop(t2)) return true;
-
-    if (t1 is InterfaceType && t2 is InterfaceType) {
-      return _matchInterfaceSubtypeOf(t1, t2, visited, origin,
-          covariant: covariant);
-    }
-
-    if (t1 is FunctionType && t2 is FunctionType) {
-      return FunctionTypeImpl.relate(
-          t1,
-          t2,
-          (t1, t2) {
-            // TODO(jmesserly): should we flip covariance when we're relating
-            // type formal bounds? They're more like parameters.
-            return matchSubtype(t1, t2);
-          },
-          _typeSystem.instantiateToBounds,
-          parameterRelation: (p1, p2) {
-            return _matchSubtypeOf(p2.type, p1.type, null, origin,
-                covariant: !covariant);
-          });
-    }
-
-    if (t1 is FunctionType && t2 == typeProvider.functionType) {
-      return true;
-    }
-
-    return false;
-  }
-
-  /// Un-does constraints that were gathered by a failed match attempt, until
-  /// [_undoBuffer] has length [previousRewindBufferLength].
-  ///
-  /// The intended usage is that the caller should record the length of
-  /// [_undoBuffer] before attempting to make a match.  Then, if the match
-  /// fails, pass the recorded length to this method to erase any constraints
-  /// that were recorded during the failed match.
-  void _rewindConstraints(int previousRewindBufferLength) {
-    while (_undoBuffer.length > previousRewindBufferLength) {
-      var constraint = _undoBuffer.removeLast();
-      var element = constraint.typeParameter.element;
-      assert(identical(constraints[element].last, constraint));
-      constraints[element].removeLast();
-    }
-  }
-
-  static String _formatConstraints(Iterable<_TypeConstraint> constraints) {
-    List<List<String>> lineParts =
-        new Set<_TypeConstraintOrigin>.from(constraints.map((c) => c.origin))
-            .map((o) => o.formatError())
-            .toList();
-
-    int prefixMax = lineParts.map((p) => p[0].length).fold(0, math.max);
-
-    // Use a set to prevent identical message lines.
-    // (It's not uncommon for the same constraint to show up in a few places.)
-    var messageLines = new Set<String>.from(lineParts.map((parts) {
-      var prefix = parts[0];
-      var middle = parts[1];
-      var prefixPad = ' ' * (prefixMax - prefix.length);
-      var middlePad = ' ' * (prefixMax);
-      var end = "";
-      if (parts.length > 2) {
-        end = '\n  $middlePad ${parts[2]}';
-      }
-      return '  $prefix$prefixPad $middle$end';
-    }));
-
-    return messageLines.join('\n');
-  }
-}
-
 /// A constraint on a type parameter that we're inferring.
 class _TypeConstraint extends _TypeRange {
   /// The type parameter that is constrained by [lowerBound] or [upperBound].
diff --git a/pkg/analyzer/test/generated/type_system_test.dart b/pkg/analyzer/test/generated/type_system_test.dart
index fbc3eba..c371ec8 100644
--- a/pkg/analyzer/test/generated/type_system_test.dart
+++ b/pkg/analyzer/test/generated/type_system_test.dart
@@ -51,13 +51,13 @@
   InterfaceType get doubleType => typeProvider.doubleType;
   DartType get dynamicType => typeProvider.dynamicType;
   InterfaceType get functionType => typeProvider.functionType;
+  InterfaceType get futureOrType => typeProvider.futureOrType;
   InterfaceType get intType => typeProvider.intType;
   InterfaceType get iterableType => typeProvider.iterableType;
   InterfaceType get listType => typeProvider.listType;
   InterfaceType get numType => typeProvider.numType;
   InterfaceType get objectType => typeProvider.objectType;
   InterfaceType get stringType => typeProvider.stringType;
-  InterfaceType get futureOrType => typeProvider.futureOrType;
   StrongTypeSystemImpl get strongTypeSystem =>
       typeSystem as StrongTypeSystemImpl;
 
@@ -1539,68 +1539,6 @@
     _checkGreatestLowerBound(bottomType, typeParam, bottomType);
   }
 
-  void test_classAndSuperclass() {
-    // class A
-    // class B extends A
-    // class C extends B
-    ClassElementImpl classA = ElementFactory.classElement2("A");
-    ClassElementImpl classB = ElementFactory.classElement("B", classA.type);
-    ClassElementImpl classC = ElementFactory.classElement("C", classB.type);
-    _checkGreatestLowerBound(classA.type, classC.type, classC.type);
-  }
-
-  void test_classAndSuperinterface() {
-    // class A
-    // class B implements A
-    // class C implements B
-    ClassElementImpl classA = ElementFactory.classElement2("A");
-    ClassElementImpl classB = ElementFactory.classElement2("B");
-    ClassElementImpl classC = ElementFactory.classElement2("C");
-    classB.interfaces = <InterfaceType>[classA.type];
-    classC.interfaces = <InterfaceType>[classB.type];
-    _checkGreatestLowerBound(classA.type, classC.type, classC.type);
-  }
-
-  void test_dynamic_bottom() {
-    _checkGreatestLowerBound(dynamicType, bottomType, bottomType);
-  }
-
-  void test_dynamic_function() {
-    _checkGreatestLowerBound(
-        dynamicType, simpleFunctionType, simpleFunctionType);
-  }
-
-  void test_dynamic_interface() {
-    DartType interfaceType = ElementFactory.classElement2('A', []).type;
-    _checkGreatestLowerBound(dynamicType, interfaceType, interfaceType);
-  }
-
-  void test_dynamic_typeParam() {
-    DartType typeParam = ElementFactory.typeParameterElement('T').type;
-    _checkGreatestLowerBound(dynamicType, typeParam, typeParam);
-  }
-
-  void test_dynamic_void() {
-    // Note: _checkGreatestLowerBound tests `GLB(x, y)` as well as `GLB(y, x)`
-    _checkGreatestLowerBound(dynamicType, voidType, voidType);
-  }
-
-  void test_bounds_of_top_types_sanity() {
-    final futureOrDynamicType = futureOrType.instantiate([dynamicType]);
-    final futureOrFutureOrDynamicType =
-        futureOrType.instantiate([futureOrDynamicType]);
-
-    // Sanity check specific cases of top for GLB/LUB.
-    _checkLeastUpperBound(objectType, dynamicType, dynamicType);
-    _checkGreatestLowerBound(objectType, dynamicType, objectType);
-    _checkLeastUpperBound(objectType, voidType, objectType);
-    _checkLeastUpperBound(futureOrDynamicType, dynamicType, dynamicType);
-    _checkGreatestLowerBound(
-        futureOrDynamicType, objectType, futureOrDynamicType);
-    _checkGreatestLowerBound(futureOrDynamicType, futureOrFutureOrDynamicType,
-        futureOrFutureOrDynamicType);
-  }
-
   void test_bounds_of_top_types_complete() {
     // Test every combination of a subset of Tops programatically.
     final futureOrDynamicType = futureOrType.instantiate([dynamicType]);
@@ -1647,6 +1585,68 @@
     }
   }
 
+  void test_bounds_of_top_types_sanity() {
+    final futureOrDynamicType = futureOrType.instantiate([dynamicType]);
+    final futureOrFutureOrDynamicType =
+        futureOrType.instantiate([futureOrDynamicType]);
+
+    // Sanity check specific cases of top for GLB/LUB.
+    _checkLeastUpperBound(objectType, dynamicType, dynamicType);
+    _checkGreatestLowerBound(objectType, dynamicType, objectType);
+    _checkLeastUpperBound(objectType, voidType, objectType);
+    _checkLeastUpperBound(futureOrDynamicType, dynamicType, dynamicType);
+    _checkGreatestLowerBound(
+        futureOrDynamicType, objectType, futureOrDynamicType);
+    _checkGreatestLowerBound(futureOrDynamicType, futureOrFutureOrDynamicType,
+        futureOrFutureOrDynamicType);
+  }
+
+  void test_classAndSuperclass() {
+    // class A
+    // class B extends A
+    // class C extends B
+    ClassElementImpl classA = ElementFactory.classElement2("A");
+    ClassElementImpl classB = ElementFactory.classElement("B", classA.type);
+    ClassElementImpl classC = ElementFactory.classElement("C", classB.type);
+    _checkGreatestLowerBound(classA.type, classC.type, classC.type);
+  }
+
+  void test_classAndSuperinterface() {
+    // class A
+    // class B implements A
+    // class C implements B
+    ClassElementImpl classA = ElementFactory.classElement2("A");
+    ClassElementImpl classB = ElementFactory.classElement2("B");
+    ClassElementImpl classC = ElementFactory.classElement2("C");
+    classB.interfaces = <InterfaceType>[classA.type];
+    classC.interfaces = <InterfaceType>[classB.type];
+    _checkGreatestLowerBound(classA.type, classC.type, classC.type);
+  }
+
+  void test_dynamic_bottom() {
+    _checkGreatestLowerBound(dynamicType, bottomType, bottomType);
+  }
+
+  void test_dynamic_function() {
+    _checkGreatestLowerBound(
+        dynamicType, simpleFunctionType, simpleFunctionType);
+  }
+
+  void test_dynamic_interface() {
+    DartType interfaceType = ElementFactory.classElement2('A', []).type;
+    _checkGreatestLowerBound(dynamicType, interfaceType, interfaceType);
+  }
+
+  void test_dynamic_typeParam() {
+    DartType typeParam = ElementFactory.typeParameterElement('T').type;
+    _checkGreatestLowerBound(dynamicType, typeParam, typeParam);
+  }
+
+  void test_dynamic_void() {
+    // Note: _checkGreatestLowerBound tests `GLB(x, y)` as well as `GLB(y, x)`
+    _checkGreatestLowerBound(dynamicType, voidType, voidType);
+  }
+
   void test_functionsDifferentNamedTakeUnion() {
     FunctionType type1 = _functionType([], named: {'a': intType, 'b': intType});
     FunctionType type2 =
@@ -1963,13 +1963,13 @@
   InterfaceType get doubleType => typeProvider.doubleType;
   DartType get dynamicType => typeProvider.dynamicType;
   InterfaceType get functionType => typeProvider.functionType;
+  InterfaceType get futureOrType => typeProvider.futureOrType;
   InterfaceType get intType => typeProvider.intType;
   InterfaceType get listType => typeProvider.listType;
   InterfaceType get numType => typeProvider.numType;
   InterfaceType get objectType => typeProvider.objectType;
   InterfaceType get stringType => typeProvider.stringType;
   DartType get voidType => VoidTypeImpl.instance;
-  InterfaceType get futureOrType => typeProvider.futureOrType;
 
   void setUp() {
     typeProvider = AnalysisContextFactory.contextWithCore().typeProvider;
@@ -2042,21 +2042,6 @@
     _checkGroups(dynamicType, equivalents: equivalents, subtypes: subtypes);
   }
 
-  void test_void_isTop() {
-    DartType interfaceType = ElementFactory.classElement2('A', []).type;
-    List<DartType> equivalents = <DartType>[dynamicType, objectType, voidType];
-    List<DartType> subtypes = <DartType>[
-      intType,
-      doubleType,
-      numType,
-      stringType,
-      functionType,
-      interfaceType,
-      bottomType
-    ];
-    _checkGroups(voidType, equivalents: equivalents, subtypes: subtypes);
-  }
-
   void test_function_subtypes_itself_top_types() {
     var tops = [dynamicType, objectType, voidType];
     // Add FutureOr<T> for T := dynamic, object, void
@@ -2308,6 +2293,21 @@
     _checkIsStrictSubtypeOf(bottom, top);
   }
 
+  void test_void_isTop() {
+    DartType interfaceType = ElementFactory.classElement2('A', []).type;
+    List<DartType> equivalents = <DartType>[dynamicType, objectType, voidType];
+    List<DartType> subtypes = <DartType>[
+      intType,
+      doubleType,
+      numType,
+      stringType,
+      functionType,
+      interfaceType,
+      bottomType
+    ];
+    _checkGroups(voidType, equivalents: equivalents, subtypes: subtypes);
+  }
+
   void _checkEquivalent(DartType type1, DartType type2) {
     _checkIsSubtypeOf(type1, type2);
     _checkIsSubtypeOf(type2, type1);