anchor.diff:      return "$typeParameters1, $typeParameters2";
     }
   
  -  void testLower(String first, String second, String expected,
  -      {String typeParameters}) {
  -    TypeParserEnvironment environment = extend(typeParameters);
  -    DartType firstType = toType(first, environment: environment);
  -    DartType secondType = toType(second, environment: environment);
  -    DartType expectedType = toType(expected, environment: environment);
  -    DartType producedType =
  -        env.getStandardLowerBound(firstType, secondType, testLib);
  -    expect(producedType, expectedType,
  -        reason: "DOWN(${firstType}, ${secondType}) produced '${producedType}', "
  -            "but expected '${expectedType}'.");
  -  }
  -
  -  void testUpper(String first, String second, String expected,
  -      {String typeParameters}) {
  -    TypeParserEnvironment environment = extend(typeParameters);
  -    DartType firstType = toType(first, environment: environment);
  -    DartType secondType = toType(second, environment: environment);
  -    DartType expectedType = toType(expected, environment: environment);
  -    DartType producedType =
  -        env.getStandardUpperBound(firstType, secondType, testLib);
  -    expect(producedType, expectedType,
  -        reason: "UP(${firstType}, ${secondType}) produced '${producedType}', "
  -            "but expected '${expectedType}'.");
  -  }
  -
  -  void testConstraint(TypeConstraint typeConstraint,
  -      {DartType lowerExpected, DartType upperExpected}) {
  -    assert(lowerExpected != null || upperExpected != null);
  -    if (lowerExpected != null) {
  -      expect(typeConstraint.lower, lowerExpected,
  -          reason: "Expected the lower bound to be '${lowerExpected}' "
  -              "for the following type constraint: ${typeConstraint}");
  -    }
  -    if (upperExpected != null) {
  -      expect(typeConstraint.upper, upperExpected,
  -          reason: "Expected the upper bound to be '${upperExpected}' "
  -              "for the following type constraint: ${typeConstraint}");
  -    }
  -  }
  -
     void test_lower_bound_bottom() {
  -    _initialize("class A;");
  +    parseTestLibrary("class A;");
   
       for (String type in ["A*", "A?", "A"]) {
  -      testLower("bottom", type, "bottom");
  -      testLower(type, "bottom", "bottom");
  +      checkLowerBound(type1: "bottom", type2: type, lowerBound: "bottom");
  +      checkLowerBound(type1: type, type2: "bottom", lowerBound: "bottom");
       }
   
       // DOWN(T1, T2) where BOTTOM(T1) and BOTTOM(T2) =
  
positive.code:     //   T2 if T2 is nullable
      //   T2? otherwise
      for (String t1 in nullPredicateEnumeration.keys) {
        testUpper(t1, "A*", "A?", typeParameters: nullPredicateEnumeration[t1]);
        testUpper(t1, "A?", "A?", typeParameters: nullPredicateEnumeration[t1]);
        testUpper(t1, "A", "A?", typeParameters: nullPredicateEnumeration[t1]);
      }
  
      // UP(T1, T2) where NULL(T2) =
      //   T1 if T1 is nullable
      //   T1? otherwise
      for (String t2 in nullPredicateEnumeration.keys) {
        testUpper("A*", t2, "A?", typeParameters: nullPredicateEnumeration[t2]);
        testUpper("A?", t2, "A?", typeParameters: nullPredicateEnumeration[t2]);
        testUpper("A", t2, "A?", typeParameters: nullPredicateEnumeration[t2]);
      }
    }
  
    void test_upper_bound_typeParameter() {
      _initialize("");
  
      // TODO(dmitryas): Test for various nullabilities.
      testUpper("T", "T", "T", typeParameters: "T extends Object");
      testUpper("T", "List<Never>", "List<Object?>",
          typeParameters: "T extends List<T>");
      testUpper("List<Never>", "T", "List<Object?>",
          typeParameters: "T extends List<T>");
      testUpper("T", "U", "List<Object?>",
          typeParameters: "T extends List<T>, U extends List<Never>");
      testUpper("U", "T", "List<Object?>",
          typeParameters: "T extends List<T>, U extends List<Never>");
    }
  
    void test_upper_bound_unknown() {
      _initialize("class A;");
  
      testLower("A*", "unknown", "A*");
      testLower("A?", "unknown", "A?");
      testLower("A", "unknown", "A");
  
      testLower("unknown", "A*", "A*");
      testLower("unknown", "A?", "A?");
      testLower("unknown", "A", "A");
    }
  
    void test_solveTypeConstraint() {
      const String testSdk = """
        class A<X>;
        class B<Y> extends A<Y>;
      """;
      _initialize(testSdk);
  
      // TODO(dmitryas): Test for various nullabilities.
  
      // Solve(? <: T <: ?) => ?
      expect(env.solveTypeConstraint(_makeConstraint(), topType, bottomType),
          new UnknownType());
  
      // Solve(? <: T <: ?, grounded) => dynamic
      expect(
          env.solveTypeConstraint(_makeConstraint(), topType, bottomType,
              grounded: true),
          new DynamicType());
  
      // Solve(A <: T <: ?) => A
      expect(
          env.solveTypeConstraint(
              _makeConstraint(lower: toType("A<dynamic>*")), topType, bottomType),
          toType("A<dynamic>*"));
  
      // Solve(A <: T <: ?, grounded) => A
      expect(
          env.solveTypeConstraint(
              _makeConstraint(lower: toType("A<dynamic>*")), topType, bottomType,
              grounded: true),
          toType("A<dynamic>*"));
  
      // Solve(A<?>* <: T <: ?) => A<?>*
      expect(
          env.solveTypeConstraint(
              _makeConstraint(lower: toType("A<unknown>*")), topType, bottomType),
          toType("A<unknown>*"));
  
      // Solve(A<?>* <: T <: ?, grounded) => A<Never>*
      expect(
          env.solveTypeConstraint(
              _makeConstraint(lower: toType("A<unknown>*")), topType, bottomType,
              grounded: true),
          toType("A<Never>*"));
  
      // Solve(? <: T <: A*) => A*
      expect(
          env.solveTypeConstraint(
              _makeConstraint(upper: toType("A<dynamic>*")), topType, bottomType),
          toType("A<dynamic>*"));
  
      // Solve(? <: T <: A*, grounded) => A*
      expect(
          env.solveTypeConstraint(
              _makeConstraint(upper: toType("A<dynamic>*")), topType, bottomType,
              grounded: true),
          toType("A<dynamic>*"));
  
      // Solve(? <: T <: A<?>*) => A<?>*
      expect(
          env.solveTypeConstraint(
              _makeConstraint(upper: toType("A<unknown>*")), topType, bottomType),
          toType("A<unknown>*"));
  
      // Solve(? <: T <: A<?>*, grounded) => A<dynamic>*
      expect(
          env.solveTypeConstraint(
              _makeConstraint(upper: toType("A<unknown>*")), topType, bottomType,
              grounded: true),
          toType("A<Object?>*"));
  
      // Solve(B* <: T <: A*) => B*
      expect(
          env.solveTypeConstraint(
              _makeConstraint(
                  lower: toType("B<dynamic>*"), upper: toType("A<dynamic>*")),
              topType,
              bottomType),
          toType("B<dynamic>*"));
  
      // Solve(B* <: T <: A*, grounded) => B*
      expect(
          env.solveTypeConstraint(
              _makeConstraint(
                  lower: toType("B<dynamic>*"), upper: toType("A<dynamic>*")),
              topType,
              bottomType,
              grounded: true),
          toType("B<dynamic>*"));
  
      // Solve(B<?>* <: T <: A*) => A*
      expect(
          env.solveTypeConstraint(
              _makeConstraint(
                  lower: toType("B<unknown>*"), upper: toType("A<dynamic>*")),
              topType,
              bottomType),
          toType("A<dynamic>*"));
  
      // Solve(B<?>* <: T <: A*, grounded) => A*
      expect(
          env.solveTypeConstraint(
              _makeConstraint(
                  lower: toType("B<unknown>*"), upper: toType("A<dynamic>*")),
              topType,
              bottomType,
              grounded: true),
          toType("A<dynamic>*"));
  
      // Solve(B* <: T <: A<?>*) => B*
      expect(
          env.solveTypeConstraint(
              _makeConstraint(
                  lower: toType("B<dynamic>*"), upper: toType("A<unknown>*")),
              topType,
              bottomType),
          toType("B<dynamic>*"));
  
      // Solve(B* <: T <: A<?>*, grounded) => B*
      expect(
          env.solveTypeConstraint(
              _makeConstraint(
                  lower: toType("B<dynamic>*"), upper: toType("A<unknown>*")),
              topType,
              bottomType,
              grounded: true),
          toType("B<dynamic>*"));
  
      // Solve(B<?>* <: T <: A<?>*) => B<?>*
      expect(
          env.solveTypeConstraint(
              _makeConstraint(
                  lower: toType("B<unknown>*"), upper: toType("A<unknown>*")),
              topType,
              bottomType),
          toType("B<unknown>*"));
  
      // Solve(B<?>* <: T <: A<?>*, grounded) => B<Never>*
      expect(
          env.solveTypeConstraint(
              _makeConstraint(
                  lower: toType("B<unknown>*"), upper: toType("A<unknown>*")),
              topType,
              bottomType,
              grounded: true),
          toType("B<Never>*"));
    }
  
    void test_typeConstraint_default() {
      TypeConstraint typeConstraint = new TypeConstraint();
      expect(typeConstraint.lower, new UnknownType());
      expect(typeConstraint.upper, new UnknownType());
    }
  
    void test_typeSatisfiesConstraint() {
      const String testSdk = """
        class A;
        class B extends A;
        class C extends B;
        class D extends C;
        class E extends D;
      """;
      _initialize(testSdk);
  
      // TODO(dmitryas): Test for various nullabilities.
      TypeConstraint typeConstraint =
          _makeConstraint(upper: toType("B*"), lower: toType("D*"));
  
      expect(env.typeSatisfiesConstraint(toType("A*"), typeConstraint), isFalse);
      expect(env.typeSatisfiesConstraint(toType("B*"), typeConstraint), isTrue);
      expect(env.typeSatisfiesConstraint(toType("C*"), typeConstraint), isTrue);
      expect(env.typeSatisfiesConstraint(toType("D*"), typeConstraint), isTrue);
      expect(env.typeSatisfiesConstraint(toType("E*"), typeConstraint), isFalse);
    }
  
    void test_unknown_at_bottom() {
      _initialize("class A;");
  
      // TODO(dmitryas): Test for various nullabilities.
      expect(
          env.isSubtypeOf(new UnknownType(), toType("A*"),
              SubtypeCheckMode.ignoringNullabilities),
          isTrue);
    }
  
    void test_unknown_at_top() {
      const String testSdk = """
        class A;
        class Pair<X, Y>;
      """;
      _initialize(testSdk);
  
      expect(
          env.isSubtypeOf(toType("A*"), new UnknownType(),
              SubtypeCheckMode.ignoringNullabilities),
          isTrue);
      expect(
          env.isSubtypeOf(
              toType("Pair<A*, Null>*"),
              toType("Pair<unknown, unknown>*"),
              SubtypeCheckMode.withNullabilities),
          isTrue);
    }
  
    DartType _list(DartType elementType) {
      return new InterfaceType(listClass, Nullability.nonNullable, [elementType]);
    }
  
    TypeConstraint _makeConstraint({DartType lower, DartType upper}) {
      lower ??= new UnknownType();
      upper ??= new UnknownType();
      return new TypeConstraint()
        ..lower = lower
        ..upper = upper;
    }
  
    void _initialize(String testSdk) {
      Uri uri = Uri.parse("dart:core");
      typeParserEnvironment = new TypeSchemaTypeParserEnvironment(uri);
      Library library =
          parseLibrary(uri, mockSdk + testSdk, environment: typeParserEnvironment)
            ..isNonNullableByDefault = true;
      component = new Component(libraries: <Library>[library]);
      coreTypes = new CoreTypes(component);
      env = new TypeSchemaEnvironment(
          coreTypes, new ClassHierarchy(component, coreTypes));
    }
  }
  
  class TypeSchemaTypeParserEnvironment extends TypeParserEnvironment {
    TypeSchemaTypeParserEnvironment(Uri uri) : super(uri, uri);
  
    DartType getPredefinedNamedType(String name) {
      if (name == "unknown") {
        // Don't return a const object to ensure we test implementations that use
        // identical.
        return new UnknownType();
      }
      return null;
    }
  }
  
negative.code:       for (String t2 in topPredicateEnumeration.keys) {
          String typeParameters = joinTypeParameters(
              topPredicateEnumeration[t1], topPredicateEnumeration[t2]);
          typeParserEnvironment.withTypeParameters(typeParameters, (_) {
            String expected =
                typeSchemaEnvironment.moretop(parseType(t2), parseType(t1))
                    ? t1
                    : t2;
            checkLowerBound(
                type1: t1,
                type2: t2,
                lowerBound: expected,
                typeParameters: typeParameters);
          });
        }
      }
  
      // DOWN(T1, T2) = T2 if TOP(T1)
      for (String t1 in topPredicateEnumeration.keys) {
        checkLowerBound(
            type1: t1,
            type2: "A*",
            lowerBound: "A*",
            typeParameters: topPredicateEnumeration[t1]);
      }
  
      // DOWN(T1, T2) = T1 if TOP(T2)
      for (String t2 in topPredicateEnumeration.keys) {
        checkLowerBound(
            type1: "A*",
            type2: t2,
            lowerBound: "A*",
            typeParameters: topPredicateEnumeration[t2]);
      }
    }
  
    void test_lower_bound_unknown() {
      parseTestLibrary("class A;");
  
      checkLowerBound(type1: "A*", type2: "UNKNOWN", lowerBound: "A*");
      checkLowerBound(type1: "A?", type2: "UNKNOWN", lowerBound: "A?");
      checkLowerBound(type1: "A", type2: "UNKNOWN", lowerBound: "A");
  
      checkLowerBound(type1: "UNKNOWN", type2: "A*", lowerBound: "A*");
      checkLowerBound(type1: "UNKNOWN", type2: "A?", lowerBound: "A?");
      checkLowerBound(type1: "UNKNOWN", type2: "A", lowerBound: "A");
    }
  
    void test_lower_bound_unrelated() {
      parseTestLibrary("""
        class A;
        class B;
      """);
  
      checkLowerBound(type1: "A*", type2: "B*", lowerBound: "Never*");
      checkLowerBound(type1: "A*", type2: "B?", lowerBound: "Never*");
      checkLowerBound(type1: "A*", type2: "B", lowerBound: "Never");
  
      checkLowerBound(type1: "A?", type2: "B*", lowerBound: "Never*");
      checkLowerBound(type1: "A?", type2: "B?", lowerBound: "Never?");
      checkLowerBound(type1: "A?", type2: "B", lowerBound: "Never");
  
      checkLowerBound(type1: "A", type2: "B*", lowerBound: "Never");
      checkLowerBound(type1: "A", type2: "B?", lowerBound: "Never");
      checkLowerBound(type1: "A", type2: "B", lowerBound: "Never");
    }
  
    void test_inferGenericFunctionOrType() {
      parseTestLibrary("");
  
      // TODO(dmitryas): Test for various nullabilities.
  
      // Test an instantiation of [1, 2.0] with no context.  This should infer
      // as List<?> during downwards inference.
      checkInference(
          typeParametersToInfer: "T extends Object?",
          functionType: "(T, T) -> List<T>",
          actualParameterTypes: null,
          returnContextType: null,
          expectedTypes: "UNKNOWN");
      // And upwards inference should refine it to List<num>.
      checkInference(
          typeParametersToInfer: "T extends Object?",
          functionType: "(T, T) -> List<T>",
          actualParameterTypes: "int, double",
          returnContextType: null,
          inferredTypesFromDownwardPhase: "UNKNOWN",
          expectedTypes: "num");
  
      // Test an instantiation of [1, 2.0] with a context of List<Object>.  This
      // should infer as List<Object> during downwards inference.
      checkInference(
          typeParametersToInfer: "T extends Object?",
          functionType: "(T, T) -> List<T>",
          actualParameterTypes: null,
          returnContextType: "List<Object>",
          expectedTypes: "Object");
      // And upwards inference should preserve the type.
      checkInference(
          typeParametersToInfer: "T extends Object?",
          functionType: "(T, T) -> List<T>",
          actualParameterTypes: "int, double",
          returnContextType: "List<Object>",
          inferredTypesFromDownwardPhase: "Object",
          expectedTypes: "Object");
  
      // Test an instantiation of [1, 2.0, null] with no context.  This should
      // infer as List<?> during downwards inference.
      checkInference(
          typeParametersToInfer: "T extends Object?",
          functionType: "(T, T, T) -> List<T>",
          actualParameterTypes: null,
          returnContextType: null,
          expectedTypes: "UNKNOWN");
      // And upwards inference should refine it to List<num?>.
      checkInference(
          typeParametersToInfer: "T extends Object?",
          functionType: "(T, T, T) -> List<T>",
          actualParameterTypes: "int, double, Null",
          returnContextType: null,
          inferredTypesFromDownwardPhase: "UNKNOWN",
          expectedTypes: "num?");
  
      // Test an instantiation of legacy [1, 2.0] with no context.
      // This should infer as List<?> during downwards inference.
      checkInference(
          typeParametersToInfer: "T extends Object?",
          functionType: "(T, T) -> List<T>",
          actualParameterTypes: null,
          returnContextType: null,
          expectedTypes: "UNKNOWN");
      checkInference(
          typeParametersToInfer: "T extends Object?",
          functionType: "(T, T) -> List<T>",
          actualParameterTypes: "int*, double*",
          returnContextType: null,
          inferredTypesFromDownwardPhase: "UNKNOWN",
          expectedTypes: "num*");
    }
  
    void test_inferTypeFromConstraints_applyBound() {
      parseTestLibrary("");
  
      // Assuming: class A<T extends num*> {}
  
      // TODO(dmitryas): Test for various nullabilities.
  
      // With no constraints:
      // Downward inference should infer A<?>
      checkInferenceFromConstraints(
          typeParameter: "T extends num*",
          constraints: "",
          downwardsInferPhase: true,
          expected: "UNKNOWN");
      // Upward inference should infer A<num*>
      checkInferenceFromConstraints(
          typeParameter: "T extends num*",
          constraints: "",
          downwardsInferPhase: false,
          inferredTypeFromDownwardPhase: "UNKNOWN",
          expected: "num*");
  
      // With an upper bound of Object*:
      // Downward inference should infer A<num*>
      checkInferenceFromConstraints(
          typeParameter: "T extends num*",
          constraints: "<: Object*",
          downwardsInferPhase: true,
          expected: "num*");
      // Upward inference should infer A<num*>
      checkInferenceFromConstraints(
          typeParameter: "T extends num*",
          constraints: "<: Object*",
          downwardsInferPhase: false,
          inferredTypeFromDownwardPhase: "num*",
          expected: "num*");
      // Upward inference should still infer A<num*> even if there are more
      // constraints now, because num was finalized during downward inference.
      checkInferenceFromConstraints(
          typeParameter: "T extends num*",
          constraints: ":> int* <: int*",
          downwardsInferPhase: false,
          inferredTypeFromDownwardPhase: "num*",
          expected: "num*");
    }
  
    void test_inferTypeFromConstraints_simple() {
      parseTestLibrary("");
  
      // TODO(dmitryas): Test for various nullabilities.
  
      // With an upper bound of List<?>*:
      // Downwards inference should infer List<List<?>*>*
      checkInferenceFromConstraints(
          typeParameter: "T extends Object?",
          constraints: "<: List<UNKNOWN>",
          downwardsInferPhase: true,
          expected: "List<UNKNOWN>");
      // Upwards inference should refine that to List<List<Object?>*>*
      checkInferenceFromConstraints(
          typeParameter: "T extends Object?",
          constraints: "<: List<UNKNOWN>",
          downwardsInferPhase: false,
          inferredTypeFromDownwardPhase: "List<UNKNOWN>",
          expected: "List<Object?>");
    }
  
    void test_upper_bound_classic() {
  