anchor.diff:        for (String t2 in nullPredicateEnumeration.keys) {
           String typeParameters = joinTypeParameters(
               nullPredicateEnumeration[t1], nullPredicateEnumeration[t2]);
  -        String expected = env.morebottom(
  -                toType(t1, typeParameters: typeParameters),
  -                toType(t2, typeParameters: typeParameters))
  -            ? t2
  -            : t1;
  -        testUpper(t1, t2, expected, typeParameters: typeParameters);
  +        typeParserEnvironment.withTypeParameters(typeParameters, (_) {
  +          String expected =
  +              typeSchemaEnvironment.morebottom(parseType(t1), parseType(t2))
  +                  ? t2
  +                  : t1;
  +          checkUpperBound(
  +              type1: t1,
  +              type2: t2,
  +              upperBound: expected,
  +              typeParameters: typeParameters);
  +        });
         }
       }
   
  
positive.code:       class D implements B, C;
        class E implements B, C;
        class L implements K;
      """;
      _initialize(testSdk);
  
      // TODO(dmitryas): Test for various nullabilities.
      testUpper("B*", "E*", "B*");
      testUpper("D*", "C*", "C*");
      testUpper("D*", "E*", "A*");
      testUpper("D*", "A*", "A*");
      testUpper("B*", "K*", "A*");
      testUpper("B*", "L*", "A*");
    }
  
    void test_upper_bound_commonClass() {
      _initialize("");
  
      testUpper("List<int*>", "List<double*>", "List<num*>");
      testUpper("List<int?>", "List<double>", "List<num?>");
    }
  
    void test_upper_bound_object() {
      _initialize("");
  
      testUpper("Object", "FutureOr<Function?>", "Object?");
      testUpper("FutureOr<Function?>", "Object", "Object?");
    }
  
    void test_upper_bound_function() {
      const String testSdk = """
        class A;
        class B extends A;
      """;
      _initialize(testSdk);
  
      testUpper("() ->? A", "() -> B?", "() ->? A?");
      testUpper("([A*]) ->* void", "(A*) ->* void", "Function*");
      testUpper("() ->* void", "(A*, B*) ->* void", "Function*");
      testUpper("(A*, B*) ->* void", "() ->* void", "Function*");
      testUpper("(A*) ->* void", "(B*) ->* void", "(B*) ->* void");
      testUpper("(B*) ->* void", "(A*) ->* void", "(B*) ->* void");
      testUpper("({A* a}) ->* void", "({B* b}) ->* void", "() ->* void");
      testUpper("({B* b}) ->* void", "({A* a}) ->* void", "() ->* void");
      testUpper(
          "({A* a, A* c}) ->* void", "({B* b, B* d}) ->* void", "() ->* void");
      testUpper("({A* a, B* b}) ->* void", "({B* a, A* b}) ->* void",
          "({B* a, B* b}) ->* void");
      testUpper("({B* a, A* b}) ->* void", "({A* a, B* b}) ->* void",
          "({B* a, B* b}) ->* void");
      testUpper("(B*, {A* a}) ->* void", "(B*) ->* void", "(B*) ->* void");
      testUpper("({A* a}) ->* void", "(B*) ->* void", "Function*");
      testUpper("() ->* void", "([B*]) ->* void", "() ->* void");
      testUpper("<X>() -> void", "<Y>() -> void", "<Z>() -> void");
      testUpper("<X>(X) -> List<X>", "<Y>(Y) -> List<Y>", "<Z>(Z) -> List<Z>");
      testUpper(
          "<X1, X2 extends List<X1>>(X1) -> X2",
          "<Y1, Y2 extends List<Y1>>(Y1) -> Y2",
          "<Z1, Z2 extends List<Z1>>(Z1) -> Z2");
      testUpper("<X extends int>() -> void", "<Y extends double>() -> void",
          "Function");
  
      testUpper("({required A a, B b}) -> A", "({B a, required A b}) -> B",
          "({required B a, required B b}) -> A");
  
      testUpper("<X extends dynamic>() -> void", "<Y extends Object?>() -> void",
          "<Z extends dynamic>() -> void");
      testUpper("<X extends Null>() -> void", "<Y extends Never?>() -> void",
          "<Z extends Null>() -> void");
      testUpper(
          "<X extends FutureOr<dynamic>?>() -> void",
          "<Y extends FutureOr<Object?>>() -> void",
          "<Z extends FutureOr<dynamic>?>() -> void");
  
      testUpper("([dynamic]) -> dynamic", "([dynamic]) -> dynamic",
          "([dynamic]) -> dynamic");
    }
  
    void test_upper_bound_identical() {
      _initialize("class A;");
  
      testUpper("A*", "A*", "A*");
      testUpper("A*", "A?", "A?");
      testUpper("A*", "A", "A*");
  
      testUpper("A?", "A*", "A?");
      testUpper("A?", "A?", "A?");
      testUpper("A?", "A", "A?");
  
      testUpper("A", "A*", "A*");
      testUpper("A", "A?", "A?");
      testUpper("A", "A", "A");
    }
  
    void test_upper_bound_sameClass() {
      const String testSdk = """
        class A;
        class B extends A;
        class Pair<X, Y>;
      """;
      _initialize(testSdk);
  
      testUpper("Pair<A*, B*>", "Pair<B*, A*>", "Pair<A*, A*>");
      testUpper("Pair<A*, B*>", "Pair<B?, A>", "Pair<A?, A*>");
      testUpper("Pair<A?, B?>", "Pair<B, A>", "Pair<A?, A?>");
    }
  
    void test_upper_bound_subtype() {
      const String testSdk = """
        class A;
        class B extends A;
      """;
      _initialize(testSdk);
  
      // UP(T1, T2) = T2 if T1 <: T2
      //   Note that both types must be class types at this point
      testUpper("List<B*>", "Iterable<A*>", "Iterable<A*>");
      testUpper("List<B*>", "Iterable<A?>", "Iterable<A?>");
      testUpper("List<B*>", "Iterable<A>", "Iterable<A>");
      testUpper("List<B>*", "Iterable<A>*", "Iterable<A>*");
      testUpper("List<B>*", "Iterable<A>?", "Iterable<A>?");
      testUpper("List<B>*", "Iterable<A>", "Iterable<A>*");
      testUpper("List<B>?", "Iterable<A>*", "Iterable<A>?");
      testUpper("List<B>?", "Iterable<A>?", "Iterable<A>?");
      testUpper("List<B>?", "Iterable<A>", "Iterable<A>?");
  
      // UP(T1, T2) = T2 if T1 <: T2
      //   Note that both types must be class types at this point
      testUpper("List<B?>", "Iterable<A*>", "Iterable<A*>");
      testUpper("List<B?>", "Iterable<A?>", "Iterable<A?>");
      testUpper("List<B>?", "Iterable<A>*", "Iterable<A>?");
      testUpper("List<B>?", "Iterable<A>?", "Iterable<A>?");
      testUpper("List<B>?", "Iterable<A>", "Iterable<A>?");
      // UP(C0<T0, ..., Tn>, C1<S0, ..., Sk>)
      //     = least upper bound of two interfaces as in Dart 1.
      testUpper("List<B?>", "Iterable<A>", "Object");
  
      // UP(T1, T2) = T2 if T1 <: T2
      //   Note that both types must be class types at this point
      testUpper("List<B>", "Iterable<A*>", "Iterable<A*>");
      testUpper("List<B>", "Iterable<A?>", "Iterable<A?>");
      testUpper("List<B>", "Iterable<A>", "Iterable<A>");
      testUpper("List<B>", "Iterable<A>*", "Iterable<A>*");
      testUpper("List<B>", "Iterable<A>?", "Iterable<A>?");
  
      // UP(T1, T2) = T1 if T2 <: T1
      //   Note that both types must be class types at this point
      testUpper("Iterable<A*>", "List<B*>", "Iterable<A*>");
      testUpper("Iterable<A*>", "List<B?>", "Iterable<A*>");
      testUpper("Iterable<A*>", "List<B>", "Iterable<A*>");
      testUpper("Iterable<A>*", "List<B>*", "Iterable<A>*");
      testUpper("Iterable<A>*", "List<B>?", "Iterable<A>?");
      testUpper("Iterable<A>*", "List<B>", "Iterable<A>*");
  
      // UP(T1, T2) = T1 if T2 <: T1
      //   Note that both types must be class types at this point
      testUpper("Iterable<A?>", "List<B*>", "Iterable<A?>");
      testUpper("Iterable<A?>", "List<B?>", "Iterable<A?>");
      testUpper("Iterable<A?>", "List<B>", "Iterable<A?>");
      testUpper("Iterable<A>?", "List<B>*", "Iterable<A>?");
      testUpper("Iterable<A>?", "List<B>?", "Iterable<A>?");
      testUpper("Iterable<A>?", "List<B>", "Iterable<A>?");
  
      // UP(T1, T2) = T1 if T2 <: T1
      //   Note that both types must be class types at this point
      testUpper("Iterable<A>", "List<B*>", "Iterable<A>");
      testUpper("Iterable<A>", "List<B>*", "Iterable<A>*");
      // UP(C0<T0, ..., Tn>, C1<S0, ..., Sk>)
      //     = least upper bound of two interfaces as in Dart 1.
      testUpper("Iterable<A>", "List<B?>", "Object");
      // UP(T1, T2) = T1 if T2 <: T1
      //   Note that both types must be class types at this point
      testUpper("Iterable<A>", "List<B>", "Iterable<A>");
    }
  
    void test_upper_bound_top() {
      _initialize("class A;");
  
      // UP(T1, T2) where TOP(T1) and TOP(T2) =
      //   T1 if MORETOP(T1, T2)
  
negative.code:       for (String t2 in nullPredicateEnumeration.keys) {
          String typeParameters = joinTypeParameters(
              nullPredicateEnumeration[t1], nullPredicateEnumeration[t2]);
          typeParserEnvironment.withTypeParameters(typeParameters, (_) {
            String expected =
                typeSchemaEnvironment.morebottom(parseType(t1), parseType(t2))
                    ? t1
                    : t2;
            checkLowerBound(
                type1: t1,
                type2: t2,
                lowerBound: expected,
                typeParameters: typeParameters);
          });
        }
      }
  
  