// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

import 'package:_fe_analyzer_shared/src/type_inference/type_analyzer_operations.dart';
import 'package:analyzer/dart/element/nullability_suffix.dart';
import 'package:analyzer/src/dart/element/element.dart';
import 'package:analyzer/src/dart/element/type.dart';
import 'package:analyzer/src/dart/element/type_schema.dart';
import 'package:analyzer/src/dart/element/type_system.dart';
import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';

import '../../../generated/type_system_base.dart';
import 'string_types.dart';

main() {
  defineReflectiveSuite(() {
    defineReflectiveTests(BoundsHelperPredicatesTest);
    defineReflectiveTests(LowerBoundTest);
    defineReflectiveTests(UpperBound_FunctionTypes_Test);
    defineReflectiveTests(UpperBound_InterfaceTypes_Test);
    defineReflectiveTests(UpperBound_RecordTypes_Test);
    defineReflectiveTests(UpperBoundTest);
  });
}

@reflectiveTest
class BoundsHelperPredicatesTest extends _BoundsTestBase {
  static final Map<String, StackTrace> _isMoreBottomChecked = {};
  static final Map<String, StackTrace> _isMoreTopChecked = {};

  void isBottom(TypeImpl type) {
    expect(type.isBottom, isTrue, reason: typeString(type));
  }

  void isMoreBottom(TypeImpl T, TypeImpl S) {
    _assertIsBottomOrNull(T);
    _assertIsBottomOrNull(S);

    var str = '${typeString(T)} vs ${typeString(S)}';
    _checkUniqueTypeStr(_isMoreBottomChecked, str);

    expect(typeSystem.isMoreBottom(T, S), isTrue, reason: str);
  }

  void isMoreTop(TypeImpl T, TypeImpl S) {
    _assertIsTopOrObject(T);
    _assertIsTopOrObject(S);

    var str = '${typeString(T)} vs ${typeString(S)}';
    _checkUniqueTypeStr(_isMoreTopChecked, str);

    expect(typeSystem.isMoreTop(T, S), isTrue, reason: str);
  }

  void isNotBottom(TypeImpl type) {
    expect(type.isBottom, isFalse, reason: typeString(type));
  }

  void isNotMoreBottom(TypeImpl T, TypeImpl S) {
    _assertIsBottomOrNull(T);
    _assertIsBottomOrNull(S);

    var str = '${typeString(T)} vs ${typeString(S)}';
    _checkUniqueTypeStr(_isMoreBottomChecked, str);

    expect(typeSystem.isMoreBottom(T, S), isFalse, reason: str);
  }

  void isNotMoreTop(TypeImpl T, TypeImpl S) {
    _assertIsTopOrObject(T);
    _assertIsTopOrObject(S);

    var str = '${typeString(T)} vs ${typeString(S)}';
    _checkUniqueTypeStr(_isMoreTopChecked, str);

    expect(typeSystem.isMoreTop(T, S), isFalse, reason: str);
  }

  void isNotNull(TypeImpl type) {
    expect(typeSystem.isNull(type), isFalse, reason: typeString(type));
  }

  void isNotObject(TypeImpl type) {
    expect(typeSystem.isObject(type), isFalse, reason: typeString(type));
  }

  void isNotTop(TypeImpl type) {
    expect(typeSystem.isTop(type), isFalse, reason: typeString(type));
  }

  void isNull(TypeImpl type) {
    expect(typeSystem.isNull(type), isTrue, reason: typeString(type));
  }

  void isObject(TypeImpl type) {
    expect(typeSystem.isObject(type), isTrue, reason: typeString(type));
  }

  void isTop(TypeImpl type) {
    expect(typeSystem.isTop(type), isTrue, reason: typeString(type));
  }

  test_isBottom() {
    TypeParameterElementImpl2 T;

    // BOTTOM(Never) is true
    isBottom(neverNone);
    isNotBottom(neverQuestion);

    // BOTTOM(X&T) is true iff BOTTOM(T)
    T = typeParameter('T', bound: objectQuestion);

    isBottom(promotedTypeParameterTypeNone(T, neverNone));
    isNotBottom(promotedTypeParameterTypeQuestion(T, neverNone));

    isNotBottom(promotedTypeParameterTypeNone(T, neverQuestion));
    isNotBottom(promotedTypeParameterTypeQuestion(T, neverQuestion));

    // BOTTOM(X extends T) is true iff BOTTOM(T)
    T = typeParameter('T', bound: neverNone);
    isBottom(typeParameterTypeNone(T));
    isNotBottom(typeParameterTypeQuestion(T));

    T = typeParameter('T', bound: neverQuestion);
    isNotBottom(typeParameterTypeNone(T));
    isNotBottom(typeParameterTypeQuestion(T));

    // BOTTOM(T) is false otherwise
    isNotBottom(dynamicType);
    isNotBottom(invalidType);
    isNotBottom(voidNone);

    isNotBottom(objectNone);
    isNotBottom(objectQuestion);

    isNotBottom(intNone);
    isNotBottom(intQuestion);

    T = typeParameter('T', bound: numNone);
    isNotBottom(typeParameterTypeNone(T));
    isNotBottom(typeParameterTypeQuestion(T));

    isNotBottom(promotedTypeParameterTypeNone(T, intNone));
    isNotBottom(promotedTypeParameterTypeQuestion(T, intNone));
  }

  test_isMoreBottom() {
    // MOREBOTTOM(Never, T) = true
    isMoreBottom(neverNone, neverNone);
    isMoreBottom(neverNone, neverQuestion);

    isMoreBottom(neverNone, nullNone);

    // MOREBOTTOM(T, Never) = false
    isNotMoreBottom(neverQuestion, neverNone);

    isNotMoreBottom(nullNone, neverNone);

    // MOREBOTTOM(Null, T) = true
    isMoreBottom(nullNone, neverQuestion);

    isMoreBottom(nullNone, nullNone);

    // MOREBOTTOM(T, Null) = false
    isNotMoreBottom(neverQuestion, nullNone);

    // MOREBOTTOM(X&T, Y&S) = MOREBOTTOM(T, S)
    isMoreBottom(
      promotedTypeParameterTypeNone(
        typeParameter('T', bound: objectQuestion),
        neverNone,
      ),
      promotedTypeParameterTypeQuestion(
        typeParameter('S', bound: objectQuestion),
        neverNone,
      ),
    );

    // MOREBOTTOM(X&T, S) = true
    isMoreBottom(
      promotedTypeParameterTypeNone(
        typeParameter('T', bound: objectQuestion),
        neverNone,
      ),
      typeParameterTypeNone(
        typeParameter('S', bound: neverNone),
      ),
    );

    // MOREBOTTOM(T, X&S) = false
    isNotMoreBottom(
      typeParameterTypeNone(
        typeParameter('T', bound: neverNone),
      ),
      promotedTypeParameterTypeNone(
        typeParameter('S', bound: objectQuestion),
        neverNone,
      ),
    );

    // MOREBOTTOM(X extends T, Y extends S) = MOREBOTTOM(T, S)
    isMoreBottom(
      typeParameterTypeNone(
        typeParameter('T', bound: neverNone),
      ),
      typeParameterTypeQuestion(
        typeParameter('S', bound: neverNone),
      ),
    );
  }

  test_isMoreTop() {
    // MORETOP(void, T) = true
    isMoreTop(voidNone, voidNone);
    isMoreTop(voidNone, dynamicType);
    isMoreTop(voidNone, invalidType);
    isMoreTop(voidNone, objectNone);
    isMoreTop(voidNone, objectQuestion);
    isMoreTop(voidNone, futureOrNone(objectNone));
    isMoreTop(voidNone, futureOrNone(objectQuestion));

    // MORETOP(T, void) = false
    isNotMoreTop(dynamicType, voidNone);
    isNotMoreTop(invalidType, voidNone);
    isNotMoreTop(objectNone, voidNone);
    isNotMoreTop(objectQuestion, voidNone);
    isNotMoreTop(futureOrNone(objectNone), voidNone);
    isNotMoreTop(futureOrNone(objectQuestion), voidNone);

    // MORETOP(dynamic, T) = true
    isMoreTop(dynamicType, dynamicType);
    isMoreTop(dynamicType, objectNone);
    isMoreTop(dynamicType, objectQuestion);
    isMoreTop(dynamicType, futureOrNone(objectNone));
    isMoreTop(dynamicType, futureOrNone(objectQuestion));

    // MORETOP(invalidType, T) = true
    isMoreTop(invalidType, dynamicType);
    isMoreTop(invalidType, objectNone);
    isMoreTop(invalidType, objectQuestion);
    isMoreTop(invalidType, futureOrNone(objectNone));
    isMoreTop(invalidType, futureOrNone(objectQuestion));

    // MORETOP(T, dynamic) = false
    isNotMoreTop(objectNone, dynamicType);
    isNotMoreTop(objectQuestion, dynamicType);
    isNotMoreTop(futureOrNone(objectNone), dynamicType);
    isNotMoreTop(futureOrNone(objectQuestion), dynamicType);

    // MORETOP(T, invalidType) = false
    isNotMoreTop(objectNone, invalidType);
    isNotMoreTop(objectQuestion, invalidType);
    isNotMoreTop(futureOrNone(objectNone), invalidType);
    isNotMoreTop(futureOrNone(objectQuestion), invalidType);

    // MORETOP(Object, T) = true
    isMoreTop(objectNone, objectNone);
    isMoreTop(objectNone, objectQuestion);
    isMoreTop(objectNone, futureOrNone(objectNone));
    isMoreTop(objectNone, futureOrQuestion(objectNone));

    // MORETOP(T, Object) = false
    isNotMoreTop(objectQuestion, objectNone);
    isNotMoreTop(futureOrNone(objectNone), objectNone);
    isNotMoreTop(futureOrQuestion(objectNone), objectNone);

    // MORETOP(T?, S?) = MORETOP(T, S)
    isMoreTop(objectQuestion, objectQuestion);
    isMoreTop(futureOrQuestion(voidNone), futureOrQuestion(voidNone));
    isMoreTop(futureOrQuestion(voidNone), futureOrQuestion(dynamicType));
    isMoreTop(futureOrQuestion(voidNone), futureOrQuestion(invalidType));
    isMoreTop(futureOrQuestion(voidNone), futureOrQuestion(objectNone));

    // MORETOP(T, S?) = true
    isMoreTop(futureOrNone(objectNone), futureOrQuestion(voidNone));
    isMoreTop(futureOrNone(objectNone), futureOrQuestion(dynamicType));
    isMoreTop(futureOrNone(objectNone), futureOrQuestion(invalidType));
    isMoreTop(futureOrNone(objectNone), futureOrQuestion(objectNone));

    // MORETOP(T?, S) = false
    isNotMoreTop(futureOrQuestion(voidNone), futureOrNone(objectNone));
    isNotMoreTop(futureOrQuestion(dynamicType), futureOrNone(objectNone));
    isNotMoreTop(futureOrQuestion(invalidType), futureOrNone(objectNone));
    isNotMoreTop(futureOrQuestion(objectNone), futureOrNone(objectNone));

    // MORETOP(FutureOr<T>, FutureOr<S>) = MORETOP(T, S)
    isMoreTop(futureOrNone(voidNone), futureOrNone(voidNone));
    isMoreTop(futureOrNone(voidNone), futureOrNone(dynamicType));
    isMoreTop(futureOrNone(voidNone), futureOrNone(invalidType));
    isMoreTop(futureOrNone(voidNone), futureOrNone(objectNone));
    isNotMoreTop(futureOrNone(dynamicType), futureOrNone(voidNone));
    isNotMoreTop(futureOrNone(invalidType), futureOrNone(voidNone));
    isNotMoreTop(futureOrNone(objectNone), futureOrNone(voidNone));
  }

  test_isNull() {
    // NULL(Null) is true
    isNull(nullNone);

    // NULL(T?) is true iff NULL(T) or BOTTOM(T)
    isNull(neverQuestion);
    isNull(
      typeParameterTypeQuestion(
        typeParameter('T', bound: neverNone),
      ),
    );

    // NULL(T) is false otherwise
    isNotNull(dynamicType);
    isNotNull(invalidType);
    isNotNull(voidNone);

    isNotNull(objectNone);
    isNotNull(objectQuestion);

    isNotNull(intNone);
    isNotNull(intQuestion);

    isNotNull(futureOrNone(nullNone));

    isNotNull(futureOrQuestion(nullNone));
  }

  test_isObject() {
    // OBJECT(Object) is true
    isObject(objectNone);
    isNotObject(objectQuestion);

    // OBJECT(FutureOr<T>) is OBJECT(T)
    isObject(futureOrNone(objectNone));
    isNotObject(futureOrNone(objectQuestion));

    isNotObject(futureOrQuestion(objectNone));
    isNotObject(futureOrQuestion(objectQuestion));

    // OBJECT(T) is false otherwise
    isNotObject(dynamicType);
    isNotObject(invalidType);
    isNotObject(voidNone);
    isNotObject(intNone);
  }

  test_isTop() {
    // TOP(T?) is true iff TOP(T) or OBJECT(T)
    isTop(objectQuestion);
    isTop(futureOrQuestion(dynamicType));
    isTop(futureOrQuestion(invalidType));
    isTop(futureOrQuestion(voidNone));

    isTop(futureOrQuestion(objectNone));
    isTop(futureOrQuestion(objectQuestion));

    isNotTop(futureOrQuestion(intNone));
    isNotTop(futureOrQuestion(intQuestion));

    // TOP(dynamic) is true
    isTop(dynamicType);
    isTop(invalidType);
    expect(typeSystem.isTop(UnknownInferredType.instance), isTrue);

    // TOP(void) is true
    isTop(voidNone);

    // TOP(FutureOr<T>) is TOP(T)
    isTop(futureOrNone(dynamicType));
    isTop(futureOrNone(invalidType));
    isTop(futureOrNone(voidNone));

    isNotTop(futureOrNone(objectNone));
    isTop(futureOrNone(objectQuestion));

    // TOP(T) is false otherwise
    isNotTop(objectNone);

    isNotTop(intNone);
    isNotTop(intQuestion);

    isNotTop(neverNone);
    isNotTop(neverQuestion);
  }

  /// [TypeSystemImpl.isMoreBottom] can be used only for `BOTTOM` or `NULL`
  /// types. No need to check other types.
  void _assertIsBottomOrNull(TypeImpl type) {
    expect(type.isBottom || typeSystem.isNull(type), isTrue,
        reason: typeString(type));
  }

  /// [TypeSystemImpl.isMoreTop] can be used only for `TOP` or `OBJECT`
  /// types. No need to check other types.
  void _assertIsTopOrObject(TypeImpl type) {
    expect(typeSystem.isTop(type) || typeSystem.isObject(type), isTrue,
        reason: typeString(type));
  }

  void _checkUniqueTypeStr(Map<String, StackTrace> map, String str) {
    var previousStack = map[str];
    if (previousStack != null) {
      fail('Not unique: $str\n$previousStack');
    } else {
      map[str] = StackTrace.current;
    }
  }
}

@reflectiveTest
class LowerBoundTest extends _BoundsTestBase {
  test_bottom_any() {
    void check(TypeImpl T1, TypeImpl T2) {
      _assertBottom(T1);
      _assertNotBottom(T2);
      _checkGreatestLowerBound(T1, T2, T1);
    }

    check(neverNone, objectNone);
    check(neverNone, objectQuestion);

    check(neverNone, intNone);
    check(neverNone, intQuestion);

    check(neverNone, listNone(intNone));
    check(neverNone, listQuestion(intNone));

    check(neverNone, futureOrNone(intNone));
    check(neverNone, futureOrQuestion(intNone));

    {
      var T = typeParameterTypeNone(
        typeParameter('T', bound: neverNone),
      );
      check(T, intNone);
      check(T, intQuestion);
    }

    {
      var T = promotedTypeParameterTypeNone(
        typeParameter('T', bound: objectQuestion),
        neverNone,
      );
      check(T, intNone);
      check(T, intQuestion);
    }
  }

  test_bottom_bottom() {
    void check(TypeImpl T1, TypeImpl T2) {
      _assertBottom(T1);
      _assertBottom(T2);
      _checkGreatestLowerBound(T1, T2, T1);
    }

    check(
      neverNone,
      typeParameterTypeNone(
        typeParameter('T', bound: neverNone),
      ),
    );

    check(
      neverNone,
      promotedTypeParameterTypeNone(
        typeParameter('T', bound: objectQuestion),
        neverNone,
      ),
    );
  }

  test_functionType2_parameters_conflicts() {
    _checkGreatestLowerBound(
      functionTypeNone(
        returnType: voidNone,
        formalParameters: [
          requiredParameter(name: 'a', type: intNone),
        ],
      ),
      functionTypeNone(
        returnType: voidNone,
        formalParameters: [
          namedParameter(name: 'a', type: intNone),
        ],
      ),
      neverNone,
    );

    _checkGreatestLowerBound(
      functionTypeNone(
        returnType: voidNone,
        formalParameters: [
          positionalParameter(name: 'a', type: intNone),
        ],
      ),
      functionTypeNone(
        returnType: voidNone,
        formalParameters: [
          namedParameter(name: 'a', type: intNone),
        ],
      ),
      neverNone,
    );
  }

  test_functionType2_parameters_named() {
    FunctionTypeImpl build(
      List<TypeImpl> requiredTypes,
      Map<String, TypeImpl> namedMap,
      Map<String, TypeImpl> namedRequiredMap,
    ) {
      var formalParameters = <FormalParameterElementImpl>[];

      for (var requiredType in requiredTypes) {
        formalParameters.add(
          requiredParameter(type: requiredType),
        );
      }

      for (var entry in namedMap.entries) {
        formalParameters.add(
          namedParameter(name: entry.key, type: entry.value),
        );
      }

      for (var entry in namedRequiredMap.entries) {
        formalParameters.add(
          namedRequiredParameter(name: entry.key, type: entry.value),
        );
      }

      return functionTypeNone(
        returnType: voidNone,
        formalParameters: formalParameters,
      );
    }

    void check(FunctionTypeImpl T1, FunctionTypeImpl T2, TypeImpl expected) {
      _checkGreatestLowerBound(T1, T2, expected);
    }

    check(
      build([], {}, {}),
      build([], {}, {}),
      build([], {}, {}),
    );

    {
      check(
        build([], {'a': intNone}, {}),
        build([], {'a': intNone}, {}),
        build([], {'a': intNone}, {}),
      );

      check(
        build([], {'a': intNone}, {}),
        build([], {}, {'a': intNone}),
        build([], {'a': intNone}, {}),
      );

      check(
        build([], {}, {'a': intNone}),
        build([], {}, {'a': intNone}),
        build([], {}, {'a': intNone}),
      );
    }

    {
      check(
        build([], {'a': intNone, 'b': intNone}, {}),
        build([], {'a': intNone, 'c': intNone}, {}),
        build([], {'a': intNone, 'b': intNone, 'c': intNone}, {}),
      );

      check(
        build([], {'a': intNone}, {'b': intNone}),
        build([], {'a': intNone}, {'c': intNone}),
        build([], {'a': intNone, 'b': intNone, 'c': intNone}, {}),
      );
    }

    {
      check(
        build([], {'a': intNone}, {}),
        build([], {'a': numNone}, {}),
        build([], {'a': numNone}, {}),
      );

      check(
        build([], {'a': intNone}, {}),
        build([], {'a': doubleNone}, {}),
        build([], {'a': numNone}, {}),
      );

      check(
        build([], {'a': intNone}, {}),
        build([], {'a': doubleQuestion}, {}),
        build([], {'a': numQuestion}, {}),
      );

      check(
        build([], {'a': intNone}, {}),
        build([], {'a': doubleNone}, {}),
        build([], {'a': numNone}, {}),
      );
    }
  }

  test_functionType2_parameters_positional() {
    FunctionTypeImpl build(
      List<TypeImpl> requiredTypes,
      List<TypeImpl> positionalTypes,
    ) {
      var formalParameters = <FormalParameterElementImpl>[];

      for (var requiredType in requiredTypes) {
        formalParameters.add(
          requiredParameter(type: requiredType),
        );
      }

      for (var positionalType in positionalTypes) {
        formalParameters.add(
          positionalParameter(type: positionalType),
        );
      }

      return functionTypeNone(
        returnType: voidNone,
        formalParameters: formalParameters,
      );
    }

    void check(FunctionTypeImpl T1, FunctionTypeImpl T2, TypeImpl expected) {
      _checkGreatestLowerBound(T1, T2, expected);
    }

    check(
      build([], []),
      build([], []),
      build([], []),
    );

    check(
      build([intNone], []),
      build([intNone], []),
      build([intNone], []),
    );

    check(
      build([intNone], []),
      build([numNone], []),
      build([numNone], []),
    );

    check(
      build([intNone], []),
      build([doubleNone], []),
      build([numNone], []),
    );

    check(
      build([intNone], []),
      build([doubleQuestion], []),
      build([numQuestion], []),
    );

    check(
      build([intNone], []),
      build([doubleNone], []),
      build([numNone], []),
    );

    {
      check(
        build([intNone], []),
        build([], [intNone]),
        build([], [intNone]),
      );

      check(
        build([intNone], []),
        build([], []),
        build([], [intNone]),
      );

      check(
        build([], [intNone]),
        build([], [intNone]),
        build([], [intNone]),
      );

      check(
        build([], [intNone]),
        build([], []),
        build([], [intNone]),
      );
    }
  }

  test_functionType2_returnType() {
    void check(TypeImpl T1_ret, TypeImpl T2_ret, TypeImpl expected_ret) {
      _checkGreatestLowerBound(
        functionTypeNone(returnType: T1_ret),
        functionTypeNone(returnType: T2_ret),
        functionTypeNone(returnType: expected_ret),
      );
    }

    check(intNone, intNone, intNone);
    check(intNone, numNone, intNone);

    check(intNone, voidNone, intNone);
    check(intNone, neverNone, neverNone);
  }

  test_functionType2_typeParameters() {
    void check(FunctionTypeImpl T1, FunctionTypeImpl T2, TypeImpl expected) {
      _assertNullabilityNone(T1);
      _assertNullabilityNone(T2);

      _checkGreatestLowerBound(T1, T2, expected, checkSubtype: false);
    }

    check(
      functionTypeNone(
        returnType: voidNone,
        typeParameters: [
          typeParameter('T'),
        ],
      ),
      functionTypeNone(returnType: voidNone),
      neverNone,
    );

    check(
      functionTypeNone(
        returnType: voidNone,
        typeParameters: [
          typeParameter('T', bound: intNone),
        ],
      ),
      functionTypeNone(
        returnType: voidNone,
        typeParameters: [
          typeParameter('T', bound: numNone),
        ],
      ),
      neverNone,
    );

    {
      var T = typeParameter('T', bound: numNone);
      var U = typeParameter('U', bound: numNone);
      var R = typeParameter('R', bound: numNone);
      check(
        functionTypeNone(
          returnType: typeParameterTypeNone(T),
          typeParameters: [T],
        ),
        functionTypeNone(
          returnType: typeParameterTypeNone(U),
          typeParameters: [U],
        ),
        functionTypeNone(
          returnType: typeParameterTypeNone(R),
          typeParameters: [R],
        ),
      );
    }
  }

  test_functionType_interfaceType() {
    void check(FunctionTypeImpl T1, InterfaceTypeImpl T2, TypeImpl expected) {
      _checkGreatestLowerBound(T1, T2, expected);
    }

    check(
      functionTypeNone(returnType: voidNone),
      intNone,
      neverNone,
    );
  }

  test_functionType_interfaceType_Function() {
    void check(FunctionTypeImpl T1) {
      _assertNullabilityNone(T1);
      _checkGreatestLowerBound(T1, functionNone, T1);
    }

    check(functionTypeNone(returnType: voidNone));

    check(
      functionTypeNone(
        returnType: intNone,
        formalParameters: [
          requiredParameter(type: numQuestion),
        ],
      ),
    );
  }

  test_futureOr() {
    InterfaceTypeImpl futureOrFunction(TypeImpl T, String str) {
      var result = futureOrNone(
        functionTypeNone(returnType: voidNone, formalParameters: [
          requiredParameter(type: T),
        ]),
      );
      expect(result.getDisplayString(), str);
      return result;
    }

    // DOWN(FutureOr<T1>, FutureOr<T2>) = FutureOr<S>, S = DOWN(T1, T2)
    _checkGreatestLowerBound(
      futureOrNone(intNone),
      futureOrNone(numNone),
      futureOrNone(intNone),
    );
    _checkGreatestLowerBound(
      futureOrFunction(intNone, 'FutureOr<void Function(int)>'),
      futureOrFunction(doubleNone, 'FutureOr<void Function(double)>'),
      futureOrFunction(numNone, 'FutureOr<void Function(num)>'),
    );

    // DOWN(FutureOr<T1>, Future<T2>) = Future<S>, S = DOWN(T1, T2)
    // DOWN(Future<T1>, FutureOr<T2>) = Future<S>, S = DOWN(T1, T2)
    _checkGreatestLowerBound(
      futureOrNone(numNone),
      futureNone(intNone),
      futureNone(intNone),
    );
    _checkGreatestLowerBound(
      futureOrNone(intNone),
      futureNone(numNone),
      futureNone(intNone),
    );

    // DOWN(FutureOr<T1>, T2) = S, S = DOWN(T1, T2)
    // DOWN(T1, FutureOr<T2>) = S, S = DOWN(T1, T2)
    _checkGreatestLowerBound(
      futureOrNone(numNone),
      intNone,
      intNone,
    );
    _checkGreatestLowerBound(
      futureOrNone(intNone),
      numNone,
      intNone,
    );
  }

  test_identical() {
    void check(TypeImpl type) {
      _checkGreatestLowerBound(type, type, type);
    }

    check(intNone);
    check(intQuestion);
    check(listNone(intNone));
  }

  test_interfaceType2() {
    void check(InterfaceTypeImpl T1, InterfaceTypeImpl T2, TypeImpl expected) {
      _assertNullabilityNone(T1);
      _assertNullabilityNone(T2);

      _checkGreatestLowerBound(T1, T2, expected);
    }

    check(intNone, intNone, intNone);
    check(numNone, intNone, intNone);
    check(doubleNone, intNone, neverNone);

    check(listNone(intNone), listNone(intNone), listNone(intNone));
    check(listNone(numNone), listNone(intNone), listNone(intNone));
    check(listNone(doubleNone), listNone(intNone), neverNone);
  }

  void test_interfaceType2_interfaces() {
    // class A
    // class B implements A
    // class C implements B
    var A = class_2(name: 'A');
    var B = class_2(name: 'B', interfaces: [interfaceTypeNone(A)]);
    var C = class_2(name: 'C', interfaces: [interfaceTypeNone(B)]);
    _checkGreatestLowerBound(
      interfaceTypeNone(A),
      interfaceTypeNone(C),
      interfaceTypeNone(C),
    );
  }

  void test_interfaceType2_mixins() {
    // class A
    // class B
    // class C
    // class D extends A with B, C
    var A = class_2(name: 'A');
    var typeA = interfaceTypeNone(A);

    var B = class_2(name: 'B');
    var typeB = interfaceTypeNone(B);

    var C = class_2(name: 'C');
    var typeC = interfaceTypeNone(C);

    var D = class_2(
      name: 'D',
      superType: interfaceTypeNone(A),
      mixins: [typeB, typeC],
    );
    var typeD = interfaceTypeNone(D);

    _checkGreatestLowerBound(typeA, typeD, typeD);
    _checkGreatestLowerBound(typeB, typeD, typeD);
    _checkGreatestLowerBound(typeC, typeD, typeD);
  }

  void test_interfaceType2_superType() {
    // class A
    // class B extends A
    // class C extends B
    var A = class_2(name: 'A');
    var B = class_2(name: 'B', superType: interfaceTypeNone(A));
    var C = class_2(name: 'C', superType: interfaceTypeNone(B));
    _checkGreatestLowerBound(
      interfaceTypeNone(A),
      interfaceTypeNone(C),
      interfaceTypeNone(C),
    );
  }

  test_none_question() {
    void check(TypeImpl T1, TypeImpl T2, TypeImpl expected) {
      _assertNullabilityNone(T1);
      _assertNullabilityQuestion(T2);

      _assertNotSpecial(T1);
      _assertNotSpecial(T2);

      _checkGreatestLowerBound(T1, T2, expected);
    }

    check(intNone, intQuestion, intNone);

    check(numNone, intQuestion, intNone);
    check(intNone, numQuestion, intNone);

    check(doubleNone, intQuestion, neverNone);
    check(intNone, doubleQuestion, neverNone);
  }

  test_null_any() {
    void check(TypeImpl T2, TypeImpl expected) {
      _assertNotBottom(T2);
      _assertNotNull(T2);
      _assertNotTop(T2);

      _checkGreatestLowerBound(nullNone, T2, expected);
    }

    void checkNull(TypeImpl T2) {
      check(T2, nullNone);
    }

    void checkNever(TypeImpl T2) {
      check(T2, neverNone);
    }

    checkNull(futureOrNone(nullNone));

    checkNull(futureOrQuestion(nullNone));

    checkNever(objectNone);

    checkNever(intNone);
    checkNull(intQuestion);

    checkNever(listNone(intNone));
    checkNull(listQuestion(intNone));

    checkNever(listNone(intQuestion));
    checkNull(listQuestion(intQuestion));
  }

  test_null_null() {
    void check(TypeImpl T1, TypeImpl T2) {
      _assertNull(T1);
      _assertNull(T2);

      _assertNotBottom(T1);
      _assertNotBottom(T2);

      _checkGreatestLowerBound(T1, T2, T1);
    }

    check(nullNone, nullNone);
  }

  test_object_any() {
    void check(TypeImpl T2, TypeImpl expected) {
      _assertNotObject(T2);

      _checkGreatestLowerBound(objectNone, T2, expected);
    }

    void checkNever(TypeImpl T2) {
      check(T2, neverNone);
    }

    check(intNone, intNone);
    check(intQuestion, intNone);

    check(futureOrNone(intNone), futureOrNone(intNone));
    check(futureOrQuestion(intNone), futureOrNone(intNone));
    check(futureOrNone(intNone), futureOrNone(intNone));

    checkNever(futureOrNone(intQuestion));
    checkNever(futureOrQuestion(intQuestion));
    checkNever(futureOrNone(intQuestion));

    {
      var T = typeParameter('T', bound: objectNone);
      check(typeParameterTypeNone(T), typeParameterTypeNone(T));
      check(typeParameterTypeQuestion(T), typeParameterTypeNone(T));
    }

    {
      var T = typeParameter('T', bound: objectQuestion);
      check(
        typeParameterTypeNone(T),
        promotedTypeParameterTypeNone(T, objectNone),
      );
      check(
        typeParameterTypeQuestion(T),
        promotedTypeParameterTypeNone(T, objectNone),
      );
    }

    {
      var T = typeParameter('T', bound: futureOrNone(objectQuestion));
      checkNever(typeParameterTypeNone(T));
      checkNever(typeParameterTypeQuestion(T));
    }
  }

  test_object_object() {
    void check(TypeImpl T1, TypeImpl T2) {
      _assertObject(T1);
      _assertObject(T2);

      _checkGreatestLowerBound(T1, T2, T1);
    }

    check(futureOrNone(objectNone), objectNone);

    check(
      futureOrNone(
        futureOrNone(objectNone),
      ),
      futureOrNone(objectNone),
    );
  }

  test_question_question() {
    void check(TypeImpl T1, TypeImpl T2, TypeImpl expected) {
      _assertNullabilityQuestion(T1);
      _assertNullabilityQuestion(T2);

      _assertNotSpecial(T1);
      _assertNotSpecial(T2);

      _checkGreatestLowerBound(T1, T2, expected);
    }

    check(intQuestion, intQuestion, intQuestion);

    check(numQuestion, intQuestion, intQuestion);
    check(intQuestion, numQuestion, intQuestion);

    check(doubleQuestion, intQuestion, neverQuestion);
    check(intQuestion, doubleQuestion, neverQuestion);
  }

  test_recordType2_differentShape() {
    void check(String T1, String T2) {
      _checkGreatestLowerBound2(T1, T2, 'Never');
    }

    check('(int,)', '(int, String)');
    check('(int,)', r'({int $1})');

    check('({int f1, String f2})', '({int f1})');
    check('({int f1})', '({int f2})');
  }

  test_recordType2_sameShape_named() {
    _checkGreatestLowerBound2(
      '({int f1})',
      '({int f1})',
      '({int f1})',
    );

    _checkGreatestLowerBound2(
      '({int f1})',
      '({num f1})',
      '({int f1})',
    );

    _checkGreatestLowerBound2(
      '({int f1})',
      '({double f1})',
      '({Never f1})',
    );

    _checkGreatestLowerBound2(
      '({int f1, double f2})',
      '({double f1, int f2})',
      '({Never f1, Never f2})',
    );
  }

  test_recordType2_sameShape_positional() {
    _checkGreatestLowerBound2('(int,)', '(int,)', '(int,)');
    _checkGreatestLowerBound2('(int,)', '(num,)', '(int,)');
    _checkGreatestLowerBound2('(int,)', '(double,)', '(Never,)');

    _checkGreatestLowerBound2(
      '(int, String)',
      '(int, String)',
      '(int, String)',
    );

    _checkGreatestLowerBound2(
      '(int, double)',
      '(double, int)',
      '(Never, Never)',
    );
  }

  test_recordType_andNot() {
    _checkGreatestLowerBound2('(int,)', 'int', 'Never');
    _checkGreatestLowerBound2('(int,)', 'void Function()', 'Never');
  }

  test_recordType_dartCoreRecord() {
    void check(String T) {
      _checkGreatestLowerBound2(T, 'Record', T);
    }

    check('(int, String)');
    check('({int f1, String f2})');
  }

  test_self() {
    var T = typeParameter('T');

    List<TypeImpl> types = [
      dynamicType,
      invalidType,
      voidNone,
      neverNone,
      typeParameterTypeNone(T),
      intNone,
      functionTypeNone(returnType: voidNone),
    ];

    for (var type in types) {
      _checkGreatestLowerBound(type, type, type);
    }
  }

  test_top_any() {
    void check(TypeImpl T1, TypeImpl T2) {
      _assertTop(T1);
      _assertNotTop(T2);
      _checkGreatestLowerBound(T1, T2, T2);
    }

    check(voidNone, objectNone);
    check(voidNone, intNone);
    check(voidNone, intQuestion);
    check(voidNone, listNone(intNone));
    check(voidNone, futureOrNone(intNone));
    check(voidNone, neverNone);
    check(voidNone, functionTypeNone(returnType: voidNone));
    check(voidNone, typeOfString('(int, int)'));

    check(dynamicType, objectNone);
    check(dynamicType, intNone);
    check(dynamicType, intQuestion);
    check(dynamicType, listNone(intNone));
    check(dynamicType, futureOrNone(intNone));
    check(dynamicType, neverNone);
    check(dynamicType, functionTypeNone(returnType: voidNone));
    check(dynamicType, typeOfString('(int, int)'));

    check(invalidType, objectNone);
    check(invalidType, intNone);
    check(invalidType, intQuestion);
    check(invalidType, listNone(intNone));
    check(invalidType, futureOrNone(intNone));
    check(invalidType, neverNone);
    check(invalidType, functionTypeNone(returnType: voidNone));
    check(invalidType, typeOfString('(int, int)'));

    check(objectQuestion, objectNone);
    check(objectQuestion, intNone);
    check(objectQuestion, intQuestion);
    check(objectQuestion, listNone(intNone));
    check(objectQuestion, futureOrNone(intNone));
    check(objectQuestion, neverNone);
    check(objectQuestion, functionTypeNone(returnType: voidNone));
    check(objectQuestion, typeOfString('(int, int)'));

    check(futureOrNone(voidNone), intNone);
    check(futureOrQuestion(voidNone), intNone);
  }

  test_top_top() {
    void check(TypeImpl T1, TypeImpl T2) {
      _assertTop(T1);
      _assertTop(T2);
      _checkGreatestLowerBound(T1, T2, T2);
    }

    check(voidNone, dynamicType);
    check(voidNone, invalidType);
    check(voidNone, objectQuestion);
    check(voidNone, futureOrNone(voidNone));
    check(voidNone, futureOrNone(dynamicType));
    check(voidNone, futureOrNone(invalidType));
    check(voidNone, futureOrNone(objectQuestion));

    check(dynamicType, objectQuestion);
    check(dynamicType, futureOrNone(voidNone));
    check(dynamicType, futureOrNone(dynamicType));
    check(dynamicType, futureOrNone(objectQuestion));

    check(invalidType, objectQuestion);
    check(invalidType, futureOrNone(voidNone));
    check(invalidType, futureOrNone(dynamicType));
    check(invalidType, futureOrNone(objectQuestion));

    check(objectQuestion, futureOrQuestion(voidNone));
    check(objectQuestion, futureOrQuestion(dynamicType));
    check(objectQuestion, futureOrQuestion(invalidType));
    check(objectQuestion, futureOrQuestion(objectNone));
    check(objectQuestion, futureOrQuestion(objectQuestion));

    check(futureOrNone(voidNone), objectQuestion);
    check(futureOrNone(dynamicType), objectQuestion);
    check(futureOrNone(invalidType), objectQuestion);
    check(futureOrNone(objectQuestion), objectQuestion);

    check(futureOrNone(voidNone), futureOrNone(dynamicType));
    check(futureOrNone(voidNone), futureOrNone(invalidType));
    check(futureOrNone(voidNone), futureOrNone(objectQuestion));
    check(futureOrNone(dynamicType), futureOrNone(objectQuestion));
    check(futureOrNone(invalidType), futureOrNone(objectQuestion));
  }

  test_typeParameter() {
    void check({TypeImpl? bound, required TypeImpl T2}) {
      var T1 = typeParameterTypeNone(
        typeParameter('T', bound: bound),
      );
      _checkGreatestLowerBound(T1, T2, neverNone);
    }

    check(
      T2: functionTypeNone(returnType: voidNone),
    );
    check(T2: intNone);
    check(bound: numNone, T2: intNone);
  }

  void _checkGreatestLowerBound(TypeImpl T1, TypeImpl T2, TypeImpl expected,
      {bool checkSubtype = true}) {
    var expectedStr = typeString(expected);

    var result = typeSystem.greatestLowerBound(T1, T2);
    var resultStr = typeString(result);
    expect(result, expected, reason: '''
expected: $expectedStr
actual: $resultStr
''');

    // Check that the result is a lower bound.
    if (checkSubtype) {
      expect(typeSystem.isSubtypeOf(result, T1), true);
      expect(typeSystem.isSubtypeOf(result, T2), true);
    }

    // Check for symmetry.
    result = typeSystem.greatestLowerBound(T2, T1);
    resultStr = typeString(result);
    expect(result, expected, reason: '''
expected: $expectedStr
actual: $resultStr
''');
  }

  void _checkGreatestLowerBound2(String T1, String T2, String expected) {
    _checkGreatestLowerBound(
      typeOfString(T1),
      typeOfString(T2),
      typeOfString(expected),
    );
  }
}

@reflectiveTest
class UpperBound_FunctionTypes_Test extends _BoundsTestBase {
  void test_nested2_upParameterType() {
    var T1 = functionTypeNone(
      formalParameters: [
        requiredParameter(
          type: functionTypeNone(
            formalParameters: [
              requiredParameter(type: stringNone),
              requiredParameter(type: intNone),
              requiredParameter(type: intNone),
            ],
            returnType: voidNone,
          ),
        ),
      ],
      returnType: voidNone,
    );
    expect(
      typeString(T1),
      'void Function(void Function(String, int, int))',
    );

    var T2 = functionTypeNone(
      formalParameters: [
        requiredParameter(
          type: functionTypeNone(
            formalParameters: [
              requiredParameter(type: intNone),
              requiredParameter(type: doubleNone),
              requiredParameter(type: numNone),
            ],
            returnType: voidNone,
          ),
        ),
      ],
      returnType: voidNone,
    );
    expect(
      typeString(T2),
      'void Function(void Function(int, double, num))',
    );

    var expected = functionTypeNone(
      formalParameters: [
        requiredParameter(
          type: functionTypeNone(
            formalParameters: [
              requiredParameter(type: objectNone),
              requiredParameter(type: numNone),
              requiredParameter(type: numNone),
            ],
            returnType: voidNone,
          ),
        ),
      ],
      returnType: voidNone,
    );
    expect(
      typeString(expected),
      'void Function(void Function(Object, num, num))',
    );

    _checkLeastUpperBound(T1, T2, expected);
  }

  void test_nested3_downParameterTypes() {
    var T1 = functionTypeNone(
      formalParameters: [
        requiredParameter(
          type: functionTypeNone(
            formalParameters: [
              requiredParameter(
                type: functionTypeNone(
                  formalParameters: [
                    requiredParameter(type: stringNone),
                    requiredParameter(type: intNone),
                    requiredParameter(type: intNone)
                  ],
                  returnType: voidNone,
                ),
              ),
            ],
            returnType: voidNone,
          ),
        ),
      ],
      returnType: voidNone,
    );
    expect(
      typeString(T1),
      'void Function(void Function(void Function(String, int, int)))',
    );

    var T2 = functionTypeNone(
      formalParameters: [
        requiredParameter(
          type: functionTypeNone(
            formalParameters: [
              requiredParameter(
                type: functionTypeNone(
                  formalParameters: [
                    requiredParameter(type: intNone),
                    requiredParameter(type: doubleNone),
                    requiredParameter(type: numNone)
                  ],
                  returnType: voidNone,
                ),
              ),
            ],
            returnType: voidNone,
          ),
        ),
      ],
      returnType: voidNone,
    );
    expect(
      typeString(T2),
      'void Function(void Function(void Function(int, double, num)))',
    );

    var expected = functionTypeNone(
      formalParameters: [
        requiredParameter(
          type: functionTypeNone(
            formalParameters: [
              requiredParameter(
                type: functionTypeNone(
                  formalParameters: [
                    requiredParameter(type: neverNone),
                    requiredParameter(type: neverNone),
                    requiredParameter(type: intNone)
                  ],
                  returnType: voidNone,
                ),
              ),
            ],
            returnType: voidNone,
          ),
        ),
      ],
      returnType: voidNone,
    );
    expect(
      typeString(expected),
      'void Function(void Function(void Function(Never, Never, int)))',
    );

    _checkLeastUpperBound(T1, T2, expected);
  }

  void test_parameters_fuzzyArrows() {
    var T1 = functionTypeNone(
      formalParameters: [
        requiredParameter(type: dynamicType),
      ],
      returnType: voidNone,
    );

    var T2 = functionTypeNone(
      formalParameters: [
        requiredParameter(type: intNone),
      ],
      returnType: voidNone,
    );

    var expected = functionTypeNone(
      formalParameters: [
        requiredParameter(type: intNone),
      ],
      returnType: voidNone,
    );

    _checkLeastUpperBound(T1, T2, expected);
  }

  test_parameters_optionalNamed() {
    FunctionTypeImpl build(Map<String, TypeImpl> namedTypes) {
      return functionTypeNone(
        returnType: voidNone,
        formalParameters: namedTypes.entries.map((entry) {
          return namedParameter(name: entry.key, type: entry.value);
        }).toList(),
      );
    }

    void check(Map<String, TypeImpl> T1_named, Map<String, TypeImpl> T2_named,
        Map<String, TypeImpl> expected_named) {
      var T1 = build(T1_named);
      var T2 = build(T2_named);
      var expected = build(expected_named);
      _checkLeastUpperBound(T1, T2, expected);
    }

    check({'a': intNone}, {}, {});
    check({'a': intNone}, {'b': intNone}, {});

    check({'a': intNone}, {'a': intNone}, {'a': intNone});
    check({'a': intNone}, {'a': intQuestion}, {'a': intNone});

    check({'a': intNone, 'b': doubleNone}, {'a': intNone}, {'a': intNone});
  }

  test_parameters_optionalPositional() {
    FunctionTypeImpl build(List<TypeImpl> positionalTypes) {
      return functionTypeNone(
        returnType: voidNone,
        formalParameters: positionalTypes.map((type) {
          return positionalParameter(type: type);
        }).toList(),
      );
    }

    void check(List<TypeImpl> T1_positional, List<TypeImpl> T2_positional,
        TypeImpl expected) {
      var T1 = build(T1_positional);
      var T2 = build(T2_positional);
      _checkLeastUpperBound(T1, T2, expected);
    }

    check([intNone], [], build([]));
    check([intNone, doubleNone], [intNone], build([intNone]));

    check([intNone], [intNone], build([intNone]));
    check([intNone], [intQuestion], build([intNone]));

    check([intNone], [doubleNone], build([neverNone]));

    check([intNone], [numNone], build([intNone]));

    check(
      [doubleNone, numNone],
      [numNone, intNone],
      build([doubleNone, intNone]),
    );
  }

  test_parameters_requiredNamed() {
    _checkLeastUpperBound(
      functionTypeNone(
        returnType: voidNone,
        formalParameters: [
          requiredParameter(name: 'a', type: intNone),
        ],
      ),
      functionTypeNone(
        returnType: voidNone,
        formalParameters: [
          namedRequiredParameter(name: 'a', type: intNone),
        ],
      ),
      functionNone,
    );

    _checkLeastUpperBound(
      functionTypeNone(
        returnType: voidNone,
        formalParameters: [
          positionalParameter(name: 'a', type: intNone),
        ],
      ),
      functionTypeNone(
        returnType: voidNone,
        formalParameters: [
          namedRequiredParameter(name: 'a', type: intNone),
        ],
      ),
      functionNone,
    );

    _checkLeastUpperBound(
      functionTypeNone(
        returnType: voidNone,
        formalParameters: [
          namedParameter(name: 'b', type: intNone),
        ],
      ),
      functionTypeNone(
        returnType: voidNone,
        formalParameters: [
          namedRequiredParameter(name: 'a', type: intNone),
        ],
      ),
      functionNone,
    );

    _checkLeastUpperBound(
      functionTypeNone(
        returnType: voidNone,
        formalParameters: [
          namedParameter(name: 'a', type: intNone),
        ],
      ),
      functionTypeNone(
        returnType: voidNone,
        formalParameters: [
          namedRequiredParameter(name: 'a', type: intNone),
        ],
      ),
      functionTypeNone(
        returnType: voidNone,
        formalParameters: [
          namedRequiredParameter(name: 'a', type: intNone),
        ],
      ),
    );

    _checkLeastUpperBound(
      functionTypeNone(
        returnType: voidNone,
        formalParameters: [
          namedParameter(name: 'a', type: intNone),
          namedRequiredParameter(name: 'b', type: intNone),
        ],
      ),
      functionTypeNone(
        returnType: voidNone,
        formalParameters: [
          namedRequiredParameter(name: 'b', type: intNone),
        ],
      ),
      functionTypeNone(
        returnType: voidNone,
        formalParameters: [
          namedRequiredParameter(name: 'b', type: intNone),
        ],
      ),
    );

    _checkLeastUpperBound(
      functionTypeNone(
        returnType: voidNone,
        formalParameters: [
          namedRequiredParameter(name: 'a', type: intNone),
        ],
      ),
      functionTypeNone(
        returnType: voidNone,
        formalParameters: [
          namedRequiredParameter(name: 'a', type: numNone),
        ],
      ),
      functionTypeNone(
        returnType: voidNone,
        formalParameters: [
          namedRequiredParameter(name: 'a', type: intNone),
        ],
      ),
    );
  }

  test_parameters_requiredPositional() {
    FunctionTypeImpl build(List<TypeImpl> requiredTypes) {
      return functionTypeNone(
        returnType: voidNone,
        formalParameters: requiredTypes.map((type) {
          return requiredParameter(type: type);
        }).toList(),
      );
    }

    void check(List<TypeImpl> T1_required, List<TypeImpl> T2_required,
        TypeImpl expected) {
      var T1 = build(T1_required);
      var T2 = build(T2_required);
      _checkLeastUpperBound(T1, T2, expected);
    }

    check([intNone], [], functionNone);

    check([intNone], [intNone], build([intNone]));
    check([intNone], [intQuestion], build([intNone]));

    check([intNone], [doubleNone], build([neverNone]));

    check([intNone], [numNone], build([intNone]));

    check(
      [doubleNone, numNone],
      [numNone, intNone],
      build([doubleNone, intNone]),
    );
  }

  void test_parameters_requiredPositional_differentArity() {
    var T1 = functionTypeNone(
      formalParameters: [
        requiredParameter(type: intNone),
        requiredParameter(type: intNone),
      ],
      returnType: voidNone,
    );

    var T2 = functionTypeNone(
      formalParameters: [
        requiredParameter(type: intNone),
        requiredParameter(type: intNone),
        requiredParameter(type: intNone),
      ],
      returnType: voidNone,
    );

    _checkLeastUpperBound(T1, T2, typeProvider.functionType);
  }

  test_returnType() {
    void check(TypeImpl T1_ret, TypeImpl T2_ret, TypeImpl expected_ret) {
      _checkLeastUpperBound(
        functionTypeNone(returnType: T1_ret),
        functionTypeNone(returnType: T2_ret),
        functionTypeNone(returnType: expected_ret),
      );
    }

    check(intNone, intNone, intNone);
    check(intNone, intQuestion, intQuestion);

    check(intNone, numNone, numNone);
    check(intQuestion, numNone, numQuestion);

    check(intNone, dynamicType, dynamicType);
    check(intNone, invalidType, invalidType);
    check(intNone, neverNone, intNone);
  }

  void test_sameType_withNamed() {
    var T1 = functionTypeNone(
      formalParameters: [
        requiredParameter(type: stringNone),
        requiredParameter(type: intNone),
        requiredParameter(type: numNone),
        namedParameter(name: 'n', type: numNone),
      ],
      returnType: intNone,
    );

    var T2 = functionTypeNone(
      formalParameters: [
        requiredParameter(type: stringNone),
        requiredParameter(type: intNone),
        requiredParameter(type: numNone),
        namedParameter(name: 'n', type: numNone),
      ],
      returnType: intNone,
    );

    var expected = functionTypeNone(
      formalParameters: [
        requiredParameter(type: stringNone),
        requiredParameter(type: intNone),
        requiredParameter(type: numNone),
        namedParameter(name: 'n', type: numNone),
      ],
      returnType: intNone,
    );

    _checkLeastUpperBound(T1, T2, expected);
  }

  void test_sameType_withOptional() {
    var T1 = functionTypeNone(
      formalParameters: [
        requiredParameter(type: stringNone),
        requiredParameter(type: intNone),
        requiredParameter(type: numNone),
        positionalParameter(type: doubleNone),
      ],
      returnType: intNone,
    );

    var T2 = functionTypeNone(
      formalParameters: [
        requiredParameter(type: stringNone),
        requiredParameter(type: intNone),
        requiredParameter(type: numNone),
        positionalParameter(type: doubleNone),
      ],
      returnType: intNone,
    );

    var expected = functionTypeNone(
      formalParameters: [
        requiredParameter(type: stringNone),
        requiredParameter(type: intNone),
        requiredParameter(type: numNone),
        positionalParameter(type: doubleNone),
      ],
      returnType: intNone,
    );

    _checkLeastUpperBound(T1, T2, expected);
  }

  test_typeParameters() {
    void check(FunctionTypeImpl T1, FunctionTypeImpl T2, TypeImpl expected) {
      _assertNullabilityNone(T1);
      _assertNullabilityNone(T2);

      _checkLeastUpperBound(T1, T2, expected);
    }

    check(
      functionTypeNone(
        returnType: voidNone,
        typeParameters: [
          typeParameter('T'),
        ],
      ),
      functionTypeNone(returnType: voidNone),
      functionNone,
    );

    check(
      functionTypeNone(
        returnType: voidNone,
        typeParameters: [
          typeParameter('T', bound: intNone),
        ],
      ),
      functionTypeNone(
        returnType: voidNone,
        typeParameters: [
          typeParameter('T', bound: numNone),
        ],
      ),
      functionNone,
    );

    {
      var T = typeParameter('T', bound: numNone);
      var U = typeParameter('U', bound: numNone);
      var T1 = functionTypeNone(
        returnType: typeParameterTypeNone(T),
        typeParameters: [T],
      );
      var T2 = functionTypeNone(
        returnType: typeParameterTypeNone(U),
        typeParameters: [U],
      );
      {
        var result = typeSystem.leastUpperBound(T1, T2);
        var resultStr = typeString(result);
        expect(resultStr, 'T Function<T extends num>()');
      }
      {
        var result = typeSystem.leastUpperBound(T2, T1);
        var resultStr = typeString(result);
        expect(resultStr, 'U Function<U extends num>()');
      }
    }
  }

  test_unrelated() {
    var T1 = functionTypeNone(returnType: intNone);

    _checkLeastUpperBound(T1, intNone, objectNone);
    _checkLeastUpperBound(T1, intQuestion, objectQuestion);

    _checkLeastUpperBound(
      T1,
      futureOrNone(functionQuestion),
      objectQuestion,
    );
  }
}

@reflectiveTest
class UpperBound_InterfaceTypes_Test extends _BoundsTestBase {
  test_directInterface() {
    // class A
    // class B implements A
    // class C implements B

    var A = class_2(name: 'A');
    var typeA = interfaceTypeNone(A);

    var B = class_2(name: 'B', interfaces: [typeA]);
    var typeB = interfaceTypeNone(B);

    var C = class_2(name: 'C', interfaces: [typeB]);
    var typeC = interfaceTypeNone(C);

    _checkLeastUpperBound(typeB, typeC, typeB);
  }

  test_directSuperclass() {
    // class A
    // class B extends A
    // class C extends B

    var A = class_2(name: 'A');
    var typeA = interfaceTypeNone(A);

    var B = class_2(name: 'B', superType: typeA);
    var typeB = interfaceTypeNone(B);

    var C = class_2(name: 'C', superType: typeB);
    var typeC = interfaceTypeNone(C);

    _checkLeastUpperBound(typeB, typeC, typeB);
  }

  void test_directSuperclass_nullability() {
    var aElement = class_2(name: 'A');
    var aQuestion = interfaceTypeQuestion2(aElement);
    var aNone = interfaceTypeNone(aElement);

    var bElementNone = class_2(name: 'B', superType: aNone);

    var bNoneQuestion = interfaceTypeQuestion2(bElementNone);

    var bNoneNone = interfaceTypeNone(bElementNone);

    void assertLUB(TypeImpl type1, TypeImpl type2, TypeImpl expected) {
      expect(typeSystem.leastUpperBound(type1, type2), expected);
      expect(typeSystem.leastUpperBound(type2, type1), expected);
    }

    assertLUB(bNoneQuestion, aQuestion, aQuestion);
    assertLUB(bNoneQuestion, aNone, aQuestion);

    assertLUB(bNoneNone, aQuestion, aQuestion);
    assertLUB(bNoneNone, aNone, aNone);
  }

  void test_implementationsOfComparable() {
    _checkLeastUpperBound(stringNone, numNone, objectNone);
  }

  void test_mixinAndClass_constraintAndInterface() {
    var A = class_2(name: 'A');
    var A_none = interfaceTypeNone(A);

    var B = class_2(name: 'B', interfaces: [A_none]);
    var M = mixin_2(name: 'M', constraints: [A_none]);

    _checkLeastUpperBound(
      interfaceTypeNone(B),
      interfaceTypeNone(M),
      A_none,
    );
  }

  void test_mixinAndClass_object() {
    var A = class_2(name: 'A');
    var M = mixin_2(name: 'M');

    _checkLeastUpperBound(
      interfaceTypeNone(A),
      interfaceTypeNone(M),
      objectNone,
    );
  }

  void test_mixinAndClass_sharedInterface() {
    var A = class_2(name: 'A');
    var A_none = interfaceTypeNone(A);

    var B = class_2(name: 'B', interfaces: [A_none]);
    var M = mixin_2(name: 'M', interfaces: [A_none]);

    _checkLeastUpperBound(
      interfaceTypeNone(B),
      interfaceTypeNone(M),
      A_none,
    );
  }

  void test_sameElement_nullability() {
    var aElement = class_2(name: 'A');

    var aQuestion = interfaceTypeQuestion2(aElement);
    var aNone = interfaceTypeNone(aElement);

    void assertLUB(TypeImpl type1, TypeImpl type2, TypeImpl expected) {
      expect(typeSystem.leastUpperBound(type1, type2), expected);
      expect(typeSystem.leastUpperBound(type2, type1), expected);
    }

    assertLUB(aQuestion, aQuestion, aQuestion);
    assertLUB(aQuestion, aNone, aQuestion);

    assertLUB(aNone, aQuestion, aQuestion);
    assertLUB(aNone, aNone, aNone);
  }

  void test_sharedMixin1() {
    // mixin M {}
    // class B with M {}
    // class C with M {}

    var M = mixin_2(name: 'M');
    var M_none = interfaceTypeNone(M);

    var B = class_2(name: 'B', mixins: [M_none]);
    var B_none = interfaceTypeNone(B);

    var C = class_2(name: 'C', mixins: [M_none]);
    var C_none = interfaceTypeNone(C);

    _checkLeastUpperBound(B_none, C_none, M_none);
  }

  void test_sharedMixin2() {
    // mixin M1 {}
    // mixin M2 {}
    // mixin M3 {}
    // class A with M1, M2 {}
    // class B with M1, M3 {}

    var M1 = mixin_2(name: 'M1');
    var M1_none = interfaceTypeNone(M1);

    var M2 = mixin_2(name: 'M2');
    var M2_none = interfaceTypeNone(M2);

    var M3 = mixin_2(name: 'M3');
    var M3_none = interfaceTypeNone(M3);

    var A = class_2(name: 'A', mixins: [M1_none, M2_none]);
    var A_none = interfaceTypeNone(A);

    var B = class_2(name: 'B', mixins: [M1_none, M3_none]);
    var B_none = interfaceTypeNone(B);

    _checkLeastUpperBound(A_none, B_none, M1_none);
  }

  void test_sharedMixin3() {
    // mixin M1 {}
    // mixin M2 {}
    // mixin M3 {}
    // class A with M2, M1 {}
    // class B with M3, M1 {}

    var M1 = mixin_2(name: 'M1');
    var M1_none = interfaceTypeNone(M1);

    var M2 = mixin_2(name: 'M2');
    var M2_none = interfaceTypeNone(M2);

    var M3 = mixin_2(name: 'M3');
    var M3_none = interfaceTypeNone(M3);

    var A = class_2(name: 'A', mixins: [M2_none, M1_none]);
    var A_none = interfaceTypeNone(A);

    var B = class_2(name: 'B', mixins: [M3_none, M1_none]);
    var B_none = interfaceTypeNone(B);

    _checkLeastUpperBound(A_none, B_none, M1_none);
  }

  void test_sharedSuperclass1() {
    // class A {}
    // class B extends A {}
    // class C extends A {}

    var A = class_2(name: 'A');
    var A_none = interfaceTypeNone(A);

    var B = class_2(name: 'B', superType: A_none);
    var B_none = interfaceTypeNone(B);

    var C = class_2(name: 'C', superType: A_none);
    var C_none = interfaceTypeNone(C);

    _checkLeastUpperBound(B_none, C_none, A_none);
  }

  void test_sharedSuperclass1_nullability() {
    var aElement = class_2(name: 'A');
    var aQuestion = interfaceTypeQuestion2(aElement);
    var aNone = interfaceTypeNone(aElement);

    var bElementNone = class_2(name: 'B', superType: aNone);
    var cElementNone = class_2(name: 'C', superType: aNone);

    var bNoneQuestion = interfaceTypeQuestion2(bElementNone);
    var bNoneNone = interfaceTypeNone(bElementNone);

    var cNoneQuestion = interfaceTypeQuestion2(cElementNone);
    var cNoneNone = interfaceTypeNone(cElementNone);

    void assertLUB(TypeImpl type1, TypeImpl type2, TypeImpl expected) {
      expect(typeSystem.leastUpperBound(type1, type2), expected);
      expect(typeSystem.leastUpperBound(type2, type1), expected);
    }

    assertLUB(bNoneQuestion, cNoneQuestion, aQuestion);
    assertLUB(bNoneQuestion, cNoneNone, aQuestion);

    assertLUB(bNoneNone, cNoneQuestion, aQuestion);
    assertLUB(bNoneNone, cNoneNone, aNone);
  }

  void test_sharedSuperclass2() {
    // class A {}
    // class B extends A {}
    // class C extends A {}
    // class D extends C {}

    var A = class_2(name: 'A');
    var A_none = interfaceTypeNone(A);

    var B = class_2(name: 'B', superType: A_none);
    var B_none = interfaceTypeNone(B);

    var C = class_2(name: 'C', superType: A_none);
    var C_none = interfaceTypeNone(C);

    var D = class_2(name: 'D', superType: C_none);
    var D_none = interfaceTypeNone(D);

    _checkLeastUpperBound(B_none, D_none, A_none);
  }

  void test_sharedSuperclass3() {
    // class A {}
    // class B extends A {}
    // class C extends B {}
    // class D extends B {}

    var A = class_2(name: 'A');
    var A_none = interfaceTypeNone(A);

    var B = class_2(name: 'B', superType: A_none);
    var B_none = interfaceTypeNone(B);

    var C = class_2(name: 'C', superType: B_none);
    var C_none = interfaceTypeNone(C);

    var D = class_2(name: 'D', superType: B_none);
    var D_none = interfaceTypeNone(D);

    _checkLeastUpperBound(C_none, D_none, B_none);
  }

  void test_sharedSuperclass4() {
    // class A {}
    // class A2 {}
    // class A3 {}
    // class B extends A implements A2 {}
    // class C extends A implement A3 {}

    var A = class_2(name: 'A');
    var A_none = interfaceTypeNone(A);

    var A2 = class_2(name: 'A2');
    var A2_none = interfaceTypeNone(A2);

    var A3 = class_2(name: 'A3');
    var A3_none = interfaceTypeNone(A3);

    var B = class_2(name: 'B', superType: A_none, interfaces: [A2_none]);
    var B_none = interfaceTypeNone(B);

    var C = class_2(name: 'C', superType: A_none, interfaces: [A3_none]);
    var C_none = interfaceTypeNone(C);

    _checkLeastUpperBound(B_none, C_none, A_none);
  }

  void test_sharedSuperinterface1() {
    // class A {}
    // class B implements A {}
    // class C implements A {}

    var A = class_2(name: 'A');
    var A_none = interfaceTypeNone(A);

    var B = class_2(name: 'B', interfaces: [A_none]);
    var B_none = interfaceTypeNone(B);

    var C = class_2(name: 'C', interfaces: [A_none]);
    var C_none = interfaceTypeNone(C);

    _checkLeastUpperBound(B_none, C_none, A_none);
  }

  void test_sharedSuperinterface2() {
    // class A {}
    // class B implements A {}
    // class C implements A {}
    // class D implements C {}

    var A = class_2(name: 'A');
    var A_none = interfaceTypeNone(A);

    var B = class_2(name: 'B', interfaces: [A_none]);
    var B_none = interfaceTypeNone(B);

    var C = class_2(name: 'C', interfaces: [A_none]);
    var C_none = interfaceTypeNone(C);

    var D = class_2(name: 'D', interfaces: [C_none]);
    var D_none = interfaceTypeNone(D);

    _checkLeastUpperBound(B_none, D_none, A_none);
  }

  void test_sharedSuperinterface3() {
    // class A {}
    // class B implements A {}
    // class C implements B {}
    // class D implements B {}

    var A = class_2(name: 'A');
    var A_none = interfaceTypeNone(A);

    var B = class_2(name: 'B', interfaces: [A_none]);
    var B_none = interfaceTypeNone(B);

    var C = class_2(name: 'C', interfaces: [B_none]);
    var C_none = interfaceTypeNone(C);

    var D = class_2(name: 'D', interfaces: [B_none]);
    var D_none = interfaceTypeNone(D);

    _checkLeastUpperBound(C_none, D_none, B_none);
  }

  void test_sharedSuperinterface4() {
    // class A {}
    // class A2 {}
    // class A3 {}
    // class B implements A, A2 {}
    // class C implements A, A3 {}

    var A = class_2(name: 'A');
    var A_none = interfaceTypeNone(A);

    var A2 = class_2(name: 'A2');
    var A2_none = interfaceTypeNone(A2);

    var A3 = class_2(name: 'A3');
    var A3_none = interfaceTypeNone(A3);

    var B = class_2(name: 'B', interfaces: [A_none, A2_none]);
    var B_none = interfaceTypeNone(B);

    var C = class_2(name: 'C', interfaces: [A_none, A3_none]);
    var C_none = interfaceTypeNone(C);

    _checkLeastUpperBound(B_none, C_none, A_none);
  }
}

@reflectiveTest
class UpperBound_RecordTypes_Test extends _BoundsTestBase {
  test_differentShape() {
    void check(String T1, String T2) {
      _checkLeastUpperBound2(T1, T2, 'Record');
    }

    check('(int,)', '(int, String)');
    check('(int,)', r'({int $1})');

    check('({int f1, String f2})', '({int f1})');
    check('({int f1})', '({int f2})');
  }

  test_Never() {
    _checkLeastUpperBound2('(int,)', 'Never', '(int,)');
  }

  test_record_andNot() {
    _checkLeastUpperBound2('(int,)', 'int', 'Object');
    _checkLeastUpperBound2('(int,)', 'void Function()', 'Object');
  }

  test_record_dartCoreRecord() {
    void check(String T1) {
      _checkLeastUpperBound2(T1, 'Record', 'Record');
    }

    check('(int, String)');
    check('({int f1, String f2})');
  }

  test_sameShape_named() {
    _checkLeastUpperBound2(
      '({int f1})',
      '({int f1})',
      '({int f1})',
    );

    _checkLeastUpperBound2(
      '({int f1})',
      '({num f1})',
      '({num f1})',
    );

    _checkLeastUpperBound2(
      '({int f1})',
      '({double f1})',
      '({num f1})',
    );

    _checkLeastUpperBound2(
      '({int f1, double f2})',
      '({double f1, int f2})',
      '({num f1, num f2})',
    );
  }

  test_sameShape_positional() {
    _checkLeastUpperBound2('(int,)', '(int,)', '(int,)');
    _checkLeastUpperBound2('(int,)', '(num,)', '(num,)');
    _checkLeastUpperBound2('(int,)', '(double,)', '(num,)');

    _checkLeastUpperBound2(
      '(int, String)',
      '(int, String)',
      '(int, String)',
    );

    _checkLeastUpperBound2(
      '(int, double)',
      '(double, int)',
      '(num, num)',
    );
  }

  test_top() {
    _checkLeastUpperBound2('(int,)', 'dynamic', 'dynamic');
    _checkLeastUpperBound2('(int,)', 'Object?', 'Object?');
  }
}

@reflectiveTest
class UpperBoundTest extends _BoundsTestBase {
  test_bottom_any() {
    void check(TypeImpl T1, TypeImpl T2) {
      _assertBottom(T1);
      _assertNotBottom(T2);
      _checkLeastUpperBound(T1, T2, T2);
    }

    check(neverNone, dynamicType);
    check(neverNone, invalidType);

    check(neverNone, objectNone);
    check(neverNone, objectQuestion);

    check(neverNone, intNone);
    check(neverNone, intQuestion);

    check(neverNone, listNone(intNone));
    check(neverNone, listQuestion(intNone));

    check(neverNone, futureOrNone(intNone));
    check(neverNone, futureOrQuestion(intNone));

    check(neverNone, functionTypeNone(returnType: voidNone));
    check(neverNone, functionTypeQuestion(returnType: voidNone));

    {
      var T = typeParameter('T');
      check(neverNone, typeParameterTypeNone(T));
      check(neverNone, typeParameterTypeQuestion(T));
    }

    {
      var T = typeParameterTypeNone(
        typeParameter('T', bound: neverNone),
      );
      check(T, intNone);
      check(T, intQuestion);
    }

    {
      var T = promotedTypeParameterTypeNone(
        typeParameter('T', bound: objectQuestion),
        neverNone,
      );
      check(T, intNone);
      check(T, intQuestion);
    }
  }

  test_bottom_bottom() {
    void check(TypeImpl T1, TypeImpl T2) {
      _assertBottom(T1);
      _assertBottom(T2);
      _checkLeastUpperBound(T1, T2, T2);
    }

    check(
      neverNone,
      typeParameterTypeNone(
        typeParameter('T', bound: neverNone),
      ),
    );

    check(
      neverNone,
      promotedTypeParameterTypeNone(
        typeParameter('T', bound: objectQuestion),
        neverNone,
      ),
    );
  }

  void test_extensionType_implementExtensionType_implicitObjectQuestion() {
    // extension type A(Object?) {}
    // extension type B(Object?) implements A {}
    // extension type C(Object?) implements A {}

    var A_none = interfaceTypeNone(
      extensionType2(
        'A',
        representationType: objectQuestion,
        interfaces: [],
      ),
    );

    _checkLeastUpperBound(
      interfaceTypeNone(
        extensionType2(
          'B',
          representationType: objectQuestion,
          interfaces: [A_none],
        ),
      ),
      interfaceTypeNone(
        extensionType2(
          'C',
          representationType: objectQuestion,
          interfaces: [A_none],
        ),
      ),
      A_none,
    );
  }

  void test_extensionType_noTypeParameters_interfaces() {
    // extension type A(int) implements int {}
    // extension type B(double) implements double {}

    _checkLeastUpperBound(
      interfaceTypeNone(
        extensionType2(
          'A',
          representationType: intNone,
          interfaces: [intNone],
        ),
      ),
      interfaceTypeNone(
        extensionType2(
          'B',
          representationType: doubleNone,
          interfaces: [doubleNone],
        ),
      ),
      numNone,
    );
  }

  void test_extensionType_noTypeParameters_noInterfaces() {
    // extension type A(int) {}
    // extension type B(double) {}

    _checkLeastUpperBound(
      interfaceTypeNone(
        extensionType2(
          'A',
          representationType: intNone,
        ),
      ),
      interfaceTypeNone(
        extensionType2(
          'B',
          representationType: doubleNone,
        ),
      ),
      objectQuestion,
    );
  }

  void test_extensionType_withTypeParameters_objectNone() {
    var T = typeParameter('T');

    _checkLeastUpperBound(
      interfaceTypeNone(
        extensionType2(
          'A',
          typeParameters: [T],
          representationType: typeParameterTypeNone(T),
          interfaces: [objectQuestion],
        ),
        typeArguments: [stringNone],
      ),
      interfaceTypeNone(
        extensionType2(
          'B',
          typeParameters: [T],
          representationType: typeParameterTypeNone(T),
          interfaces: [objectQuestion],
        ),
        typeArguments: [numNone],
      ),
      objectNone,
    );
  }

  void test_extensionType_withTypeParameters_withInterfaces() {
    var T = typeParameter('T');
    var T1 = typeParameter('T1', bound: stringNone);
    var T2 = typeParameter('T2', bound: intNone);

    var E = extensionType2(
      'E',
      typeParameters: [T],
      representationType: typeParameterTypeNone(T),
      interfaces: [objectQuestion],
    );

    // A<T1> implements E<T1>, String
    // B<T2> implements E<T2?>, num
    _checkLeastUpperBound(
      interfaceTypeNone(
        extensionType2(
          'A',
          typeParameters: [T1],
          representationType: typeParameterTypeNone(T1),
          interfaces: [
            interfaceTypeNone(E, typeArguments: [
              typeParameterTypeNone(T1),
            ]),
            stringNone,
          ],
        ),
        typeArguments: [stringNone],
      ),
      interfaceTypeNone(
        extensionType2(
          'B',
          typeParameters: [T2],
          representationType: typeParameterTypeNone(T2),
          interfaces: [
            interfaceTypeNone(E, typeArguments: [
              typeParameterTypeQuestion(T2),
            ]),
            numNone,
          ],
        ),
        typeArguments: [numNone],
      ),
      objectNone,
    );
  }

  test_functionType_interfaceType() {
    void check(
        FunctionTypeImpl T1, InterfaceTypeImpl T2, InterfaceTypeImpl expected) {
      _checkLeastUpperBound(T1, T2, expected);
    }

    check(
      functionTypeNone(returnType: voidNone),
      intNone,
      objectNone,
    );
  }

  test_functionType_interfaceType_Function() {
    void check(
        FunctionTypeImpl T1, InterfaceTypeImpl T2, InterfaceTypeImpl expected) {
      _checkLeastUpperBound(T1, T2, expected);
    }

    void checkNone(FunctionTypeImpl T1) {
      _assertNullabilityNone(T1);
      check(T1, functionNone, functionNone);
    }

    checkNone(functionTypeNone(returnType: voidNone));

    checkNone(
      functionTypeNone(
        returnType: intNone,
        formalParameters: [
          requiredParameter(type: numQuestion),
        ],
      ),
    );

    check(
      functionTypeQuestion(returnType: voidNone),
      functionNone,
      functionQuestion,
    );
  }

  /// `UP(Future<T1>, FutureOr<T2>) = FutureOr<T3> where T3 = UP(T1, T2)`
  /// `UP(FutureOr<T1>, Future<T2>) = FutureOr<T3> where T3 = UP(T1, T2)`
  test_futureOr_future() {
    void check(TypeImpl T1, TypeImpl T2, TypeImpl expected) {
      _checkLeastUpperBound(
        futureNone(T1),
        futureOrNone(T2),
        futureOrNone(expected),
      );
    }

    check(intNone, doubleNone, numNone);
    check(intNone, stringNone, objectNone);
  }

  /// `UP(FutureOr<T1>, FutureOr<T2>) = FutureOr<T3> where T3 = UP(T1, T2)`
  test_futureOr_futureOr() {
    void check(TypeImpl T1, TypeImpl T2, TypeImpl expected) {
      _checkLeastUpperBound(
        futureOrNone(T1),
        futureOrNone(T2),
        futureOrNone(expected),
      );
    }

    check(intNone, doubleNone, numNone);
    check(intNone, stringNone, objectNone);
  }

  /// `UP(T1, FutureOr<T2>) = FutureOr<T3> where T3 = UP(T1, T2)`
  /// `UP(FutureOr<T1>, T2) = FutureOr<T3> where T3 = UP(T1, T2)`
  test_futureOr_other() {
    void check(TypeImpl T1, TypeImpl T2, TypeImpl expected) {
      _checkLeastUpperBound(
        futureOrNone(T1),
        T2,
        futureOrNone(expected),
      );
    }

    check(intNone, doubleNone, numNone);
    check(intNone, stringNone, objectNone);
  }

  test_identical() {
    void check(TypeImpl type) {
      _checkLeastUpperBound(type, type, type);
    }

    check(intNone);
    check(intQuestion);
    check(listNone(intNone));
  }

  void test_interfaceType_functionType() {
    var A = class_2(name: 'A');

    _checkLeastUpperBound(
      interfaceTypeNone(A),
      functionTypeNone(returnType: voidNone),
      objectNone,
    );
  }

  test_none_question() {
    void check(TypeImpl T1, TypeImpl T2, TypeImpl expected) {
      _assertNullabilityNone(T1);
      _assertNullabilityQuestion(T2);

      _assertNotSpecial(T1);
      _assertNotSpecial(T2);

      _checkLeastUpperBound(T1, T2, expected);
    }

    check(doubleNone, intQuestion, numQuestion);
    check(numNone, doubleQuestion, numQuestion);
    check(numNone, intQuestion, numQuestion);
  }

  test_null_any() {
    void check(TypeImpl T1, TypeImpl T2, TypeImpl expected) {
      _assertNull(T1);
      _assertNotNull(T2);

      _assertNotTop(T1);
      _assertNotTop(T2);

      _assertNotBottom(T1);
      _assertNotBottom(T2);

      _checkLeastUpperBound(T1, T2, expected);
    }

    check(nullNone, objectNone, objectQuestion);

    check(nullNone, intNone, intQuestion);
    check(nullNone, intQuestion, intQuestion);

    check(nullNone, listNone(intNone), listQuestion(intNone));
    check(nullNone, listQuestion(intNone), listQuestion(intNone));

    check(nullNone, futureOrNone(intNone), futureOrQuestion(intNone));
    check(nullNone, futureOrQuestion(intNone), futureOrQuestion(intNone));

    check(nullNone, futureOrNone(intQuestion), futureOrNone(intQuestion));
    check(
      nullNone,
      futureOrQuestion(intQuestion),
      futureOrQuestion(intQuestion),
    );

    check(
      nullNone,
      functionTypeNone(returnType: intNone),
      functionTypeQuestion(returnType: intNone),
    );
  }

  test_null_null() {
    void check(TypeImpl T1, TypeImpl T2) {
      _assertNull(T1);
      _assertNull(T2);

      _assertNotBottom(T1);
      _assertNotBottom(T2);

      _checkLeastUpperBound(T1, T2, T2);
    }

    check(nullNone, nullNone);
  }

  test_object_any() {
    void check(TypeImpl T1, TypeImpl T2, TypeImpl expected) {
      _assertObject(T1);
      _assertNotObject(T2);

      _checkLeastUpperBound(T1, T2, expected);
    }

    check(objectNone, intNone, objectNone);
    check(objectNone, intQuestion, objectQuestion);

    check(objectNone, futureOrNone(intQuestion), objectQuestion);

    check(futureOrNone(objectNone), intNone, futureOrNone(objectNone));
    check(futureOrNone(objectNone), intQuestion, futureOrQuestion(objectNone));
  }

  test_object_object() {
    void check(TypeImpl T1, TypeImpl T2) {
      _assertObject(T1);
      _assertObject(T2);

      _checkLeastUpperBound(T1, T2, T2);
    }

    check(futureOrNone(objectNone), objectNone);

    check(
      futureOrNone(
        futureOrNone(objectNone),
      ),
      futureOrNone(objectNone),
    );
  }

  test_question_question() {
    void check(TypeImpl T1, TypeImpl T2, TypeImpl expected) {
      _assertNullabilityQuestion(T1);
      _assertNullabilityQuestion(T2);

      _assertNotSpecial(T1);
      _assertNotSpecial(T2);

      _checkLeastUpperBound(T1, T2, expected);
    }

    check(doubleQuestion, intQuestion, numQuestion);
    check(numQuestion, doubleQuestion, numQuestion);
    check(numQuestion, intQuestion, numQuestion);
  }

  test_top_any() {
    void check(TypeImpl T1, TypeImpl T2) {
      _assertTop(T1);
      _assertNotTop(T2);
      _checkLeastUpperBound(T1, T2, T1);
    }

    void check2(TypeImpl T1) {
      check(T1, objectNone);
      check(T1, intNone);
      check(T1, intQuestion);
      check(T1, listNone(intNone));
      check(T1, futureOrNone(intNone));
      check(T1, functionTypeNone(returnType: voidNone));

      {
        var T = typeParameter('T');
        check(T1, typeParameterTypeNone(T));
        check(T1, typeParameterTypeQuestion(T));
      }
    }

    check2(voidNone);
    check2(dynamicType);
    check2(invalidType);
    check2(objectQuestion);

    check2(futureOrNone(voidNone));
    check2(futureOrQuestion(voidNone));
  }

  test_top_top() {
    void check(TypeImpl T1, TypeImpl T2) {
      _assertTop(T1);
      _assertTop(T2);
      _checkLeastUpperBound(T1, T2, T1);
    }

    check(voidNone, dynamicType);
    check(voidNone, invalidType);
    check(voidNone, objectQuestion);
    check(voidNone, futureOrNone(voidNone));
    check(voidNone, futureOrNone(dynamicType));
    check(voidNone, futureOrNone(invalidType));
    check(voidNone, futureOrNone(objectQuestion));

    check(dynamicType, objectQuestion);
    check(dynamicType, futureOrNone(voidNone));
    check(dynamicType, futureOrNone(dynamicType));
    check(dynamicType, futureOrNone(objectQuestion));

    check(invalidType, objectQuestion);
    check(invalidType, futureOrNone(voidNone));
    check(invalidType, futureOrNone(dynamicType));
    check(invalidType, futureOrNone(objectQuestion));

    check(objectQuestion, futureOrQuestion(voidNone));
    check(objectQuestion, futureOrQuestion(dynamicType));
    check(objectQuestion, futureOrQuestion(invalidType));
    check(objectQuestion, futureOrQuestion(objectNone));
    check(objectQuestion, futureOrQuestion(objectQuestion));

    check(futureOrNone(voidNone), objectQuestion);
    check(futureOrNone(dynamicType), objectQuestion);
    check(futureOrNone(invalidType), objectQuestion);
    check(futureOrNone(objectQuestion), objectQuestion);

    check(futureOrNone(voidNone), futureOrNone(dynamicType));
    check(futureOrNone(voidNone), futureOrNone(invalidType));
    check(futureOrNone(voidNone), futureOrNone(objectQuestion));
    check(futureOrNone(dynamicType), futureOrNone(objectQuestion));
    check(futureOrNone(invalidType), futureOrNone(objectQuestion));
  }

  test_typeParameter_bound() {
    void check(TypeParameterTypeImpl T1, TypeImpl T2, TypeImpl expected) {
      _assertNullabilityNone(T1);
      _assertNullabilityNone(T2);

      _assertNotSpecial(T1);
      _assertNotSpecial(T2);

      _checkLeastUpperBound(T1, T2, expected);
    }

    {
      var T = typeParameter('T', bound: intNone);
      check(typeParameterTypeNone(T), numNone, numNone);
    }

    {
      var T = typeParameter('T', bound: intNone);
      var U = typeParameter('U', bound: numNone);
      check(typeParameterTypeNone(T), typeParameterTypeNone(U), numNone);
    }

    {
      var T = typeParameter('T', bound: intNone);
      var U = typeParameter('U', bound: numQuestion);
      check(typeParameterTypeNone(T), typeParameterTypeNone(U), numQuestion);
    }

    {
      var T = typeParameter('T', bound: intQuestion);
      var U = typeParameter('U', bound: numNone);
      check(typeParameterTypeNone(T), typeParameterTypeNone(U), numQuestion);
    }

    {
      var T = typeParameter('T', bound: numNone);
      var T_none = typeParameterTypeNone(T);
      var U = typeParameter('U', bound: T_none);
      check(T_none, typeParameterTypeNone(U), T_none);
    }
  }

  void test_typeParameter_fBounded() {
    // class A<T> {}
    var T = typeParameter('T');
    var A = class_2(name: 'A', typeParameters: [T]);

    // <S extends A<S>>
    var S = typeParameter('S');
    var S_none = typeParameterTypeNone(S);
    S.bound = interfaceTypeNone(A, typeArguments: [S_none]);

    // <U extends A<U>>
    var U = typeParameter('U');
    var U_none = typeParameterTypeNone(U);
    U.bound = interfaceTypeNone(A, typeArguments: [U_none]);

    _checkLeastUpperBound(
      S_none,
      typeParameterTypeNone(U),
      interfaceTypeNone(A, typeArguments: [objectQuestion]),
    );
  }

  void test_typeParameter_function_bounded() {
    var T = typeParameter('T', bound: typeProvider.functionType);

    _checkLeastUpperBound(
      typeParameterTypeNone(T),
      functionTypeNone(returnType: voidNone),
      typeProvider.functionType,
    );
  }

  void test_typeParameter_function_noBound() {
    var T = typeParameter('T', bound: objectQuestion);

    _checkLeastUpperBound(
      typeParameterTypeNone(T),
      functionTypeNone(returnType: voidNone),
      objectQuestion,
    );
  }

  void test_typeParameter_greatestClosure_functionBounded() {
    var T = typeParameter('T');
    var T_none = typeParameterTypeNone(T);
    T.bound = functionTypeNone(
      returnType: voidNone,
      formalParameters: [
        requiredParameter(type: T_none),
      ],
    );

    _checkLeastUpperBound(
      T_none,
      functionTypeNone(
        returnType: voidNone,
        formalParameters: [
          requiredParameter(type: nullNone),
        ],
      ),
      functionTypeNone(
        returnType: voidNone,
        formalParameters: [
          requiredParameter(type: neverNone),
        ],
      ),
    );
  }

  void test_typeParameter_greatestClosure_functionPromoted() {
    var T = typeParameter('T');
    var T_none = typeParameterTypeNone(T);
    var T_none_promoted = typeParameterTypeNone(
      T,
      promotedBound: functionTypeNone(
        returnType: voidNone,
        formalParameters: [
          requiredParameter(type: T_none),
        ],
      ),
    );

    _checkLeastUpperBound(
      T_none_promoted,
      functionTypeNone(
        returnType: voidNone,
        formalParameters: [
          requiredParameter(type: nullNone),
        ],
      ),
      functionTypeNone(
        returnType: voidNone,
        formalParameters: [
          requiredParameter(type: neverNone),
        ],
      ),
    );
  }

  void test_typeParameter_interface_bounded() {
    var A = class_2(name: 'A');
    var A_none = interfaceTypeNone(A);

    var B = class_2(name: 'B', superType: A_none);
    var B_none = interfaceTypeNone(B);

    var C = class_2(name: 'C', superType: A_none);
    var C_none = interfaceTypeNone(C);

    var T = typeParameter('T', bound: B_none);
    var typeT = typeParameterTypeNone(T);

    _checkLeastUpperBound(typeT, C_none, A_none);
  }

  void test_typeParameter_interface_bounded_objectQuestion() {
    var T = typeParameter('T', bound: objectQuestion);

    _checkLeastUpperBound(
      typeParameterTypeNone(T),
      intNone,
      objectQuestion,
    );
  }

  void test_typeParameter_interface_noBound() {
    var T = typeParameter('T');

    _checkLeastUpperBound(
      typeParameterTypeNone(T),
      intNone,
      objectQuestion,
    );
  }

  void test_typeParameter_intersection_basic() {
    // `X extends num?`, `Y extends X`, `X & num`.
    var X = typeParameter('X', bound: numQuestion);
    var X_none = typeParameterTypeNone(X);
    var Y = typeParameter('Y', bound: X_none);
    var Y_none = typeParameterTypeNone(Y);
    var X_none_promoted = typeParameterTypeNone(X, promotedBound: numNone);

    // `UP(X & num, Y) == X`, because `Y <: X`.
    _checkLeastUpperBound(
      X_none_promoted,
      Y_none,
      X_none,
    );

    // `UP(X & num, num?) == num?`, because `X <: num?`.
    _checkLeastUpperBound(
      X_none_promoted,
      numQuestion,
      numQuestion,
    );

    // `UP(X & num, String) == Object`.
    _checkLeastUpperBound(
      X_none_promoted,
      stringNone,
      objectNone,
    );
  }

  void test_typeParameter_intersection_fbounded() {
    // `X`, `class C<X> {}`, `Y extends C<Y>?`, `Y & C<Y>`.
    var X = typeParameter('X');
    var C = class_2(name: 'C', typeParameters: [X]);
    var Y = typeParameter('Y');
    var Y_none = typeParameterTypeNone(Y);
    Y.bound = interfaceTypeQuestion2(C, typeArguments: [Y_none]);
    var C_Y_none = interfaceTypeNone(C, typeArguments: [Y_none]);
    var Y_none_promoted = typeParameterTypeNone(
      Y,
      promotedBound: C_Y_none,
    );
    var C_Never_none = interfaceTypeNone(C, typeArguments: [neverNone]);
    var C_ObjectQuestion_none =
        interfaceTypeNone(C, typeArguments: [objectQuestion]);

    // `UP(Y & C<Y>, C<Never>) == C<Object?>`.
    _checkLeastUpperBound(
      Y_none_promoted,
      C_Never_none,
      C_ObjectQuestion_none,
    );
  }

  void test_typeParameter_intersection_null() {
    var X = typeParameter('X');
    var X_none_promoted_nullable = typeParameterTypeNone(
      X,
      promotedBound: numQuestion,
    );
    var X_none_promoted_nonnullable = typeParameterTypeNone(
      X,
      promotedBound: numNone,
    );

    // UP(X & num?, Null) == num?
    _checkLeastUpperBound(
      X_none_promoted_nullable,
      nullNone,
      numQuestion,
    );

    // UP(X & num, Null) == num?
    _checkLeastUpperBound(
      X_none_promoted_nonnullable,
      nullNone,
      numQuestion,
    );
  }

  void test_typeParameters_contravariant_different() {
    // class A<in T>
    var T = typeParameter('T', variance: Variance.contravariant);
    var A = class_2(name: 'A', typeParameters: [T]);

    // A<num>
    // A<int>
    var A_num = interfaceTypeNone(A, typeArguments: [numNone]);
    var A_int = interfaceTypeNone(A, typeArguments: [intNone]);

    _checkLeastUpperBound(A_int, A_num, A_int);
  }

  void test_typeParameters_contravariant_same() {
    // class A<in T>
    var T = typeParameter('T', variance: Variance.contravariant);
    var A = class_2(name: 'A', typeParameters: [T]);

    // A<num>
    var A_num = interfaceTypeNone(A, typeArguments: [numNone]);

    _checkLeastUpperBound(A_num, A_num, A_num);
  }

  void test_typeParameters_covariant_different() {
    // class A<out T>
    var T = typeParameter('T', variance: Variance.covariant);
    var A = class_2(name: 'A', typeParameters: [T]);

    // A<num>
    // A<int>
    var A_num = interfaceTypeNone(A, typeArguments: [numNone]);
    var A_int = interfaceTypeNone(A, typeArguments: [intNone]);

    _checkLeastUpperBound(A_int, A_num, A_num);
  }

  void test_typeParameters_covariant_same() {
    // class A<out T>
    var T = typeParameter('T', variance: Variance.covariant);
    var A = class_2(name: 'A', typeParameters: [T]);

    // A<num>
    var A_num = interfaceTypeNone(A, typeArguments: [numNone]);

    _checkLeastUpperBound(A_num, A_num, A_num);
  }

  void test_typeParameters_invariant_object() {
    // class A<inout T>
    var T = typeParameter('T', variance: Variance.invariant);
    var A = class_2(name: 'A', typeParameters: [T]);

    // A<num>
    // A<int>
    var A_num = interfaceTypeNone(A, typeArguments: [numNone]);
    var A_int = interfaceTypeNone(A, typeArguments: [intNone]);

    _checkLeastUpperBound(A_num, A_int, objectNone);
  }

  void test_typeParameters_invariant_same() {
    // class A<inout T>
    var T = typeParameter('T', variance: Variance.invariant);
    var A = class_2(name: 'A', typeParameters: [T]);

    // A<num>
    var A_num = interfaceTypeNone(A, typeArguments: [numNone]);

    _checkLeastUpperBound(A_num, A_num, A_num);
  }

  void test_typeParameters_multi_basic() {
    // class A<out T, inout U, in V>
    var T = typeParameter('T', variance: Variance.covariant);
    var U = typeParameter('U', variance: Variance.invariant);
    var V = typeParameter('V', variance: Variance.contravariant);
    var A = class_2(name: 'A', typeParameters: [T, U, V]);

    // A<num, num, num>
    // A<int, num, int>
    var A_num_num_num = interfaceTypeNone(
      A,
      typeArguments: [numNone, numNone, numNone],
    );
    var A_int_num_int = interfaceTypeNone(
      A,
      typeArguments: [intNone, numNone, intNone],
    );

    // We expect A<num, num, int>
    var A_num_num_int = interfaceTypeNone(
      A,
      typeArguments: [numNone, numNone, intNone],
    );

    _checkLeastUpperBound(A_num_num_num, A_int_num_int, A_num_num_int);
  }

  void test_typeParameters_multi_objectInterface() {
    // class A<out T, inout U, in V>
    var T = typeParameter('T', variance: Variance.covariant);
    var U = typeParameter('T', variance: Variance.invariant);
    var V = typeParameter('T', variance: Variance.contravariant);
    var A = class_2(name: 'A', typeParameters: [T, U, V]);

    // A<num, String, num>
    // A<int, num, int>
    var A_num_String_num = interfaceTypeNone(
      A,
      typeArguments: [numNone, stringNone, numNone],
    );
    var A_int_num_int = interfaceTypeNone(
      A,
      typeArguments: [intNone, numNone, intNone],
    );

    _checkLeastUpperBound(A_num_String_num, A_int_num_int, objectNone);
  }

  void test_typeParameters_multi_objectType() {
    // class A<out T, inout U, in V>
    var T = typeParameter('T', variance: Variance.covariant);
    var U = typeParameter('T', variance: Variance.invariant);
    var V = typeParameter('T', variance: Variance.contravariant);
    var A = class_2(name: 'A', typeParameters: [T, U, V]);

    // A<String, num, num>
    // A<int, num, int>
    var A_String_num_num = interfaceTypeNone(
      A,
      typeArguments: [stringNone, numNone, numNone],
    );
    var A_int_num_int = interfaceTypeNone(
      A,
      typeArguments: [intNone, numNone, intNone],
    );

    // We expect A<Object, num, int>
    var A_Object_num_int = interfaceTypeNone(
      A,
      typeArguments: [objectNone, numNone, intNone],
    );

    _checkLeastUpperBound(A_String_num_num, A_int_num_int, A_Object_num_int);
  }

  /// Check least upper bound of the same class with different type parameters.
  void test_typeParameters_noVariance_different() {
    _checkLeastUpperBound(
      listNone(intNone),
      listNone(doubleNone),
      listNone(numNone),
    );
  }

  void test_typeParameters_noVariance_same() {
    var listOfInt = listNone(intNone);
    _checkLeastUpperBound(listOfInt, listOfInt, listOfInt);
  }
}

@reflectiveTest
class _BoundsTestBase extends AbstractTypeSystemTest with StringTypes {
  @override
  void setUp() {
    super.setUp();
    defineStringTypes();
  }

  void _assertBottom(TypeImpl type) {
    if (!type.isBottom) {
      fail('isBottom must be true: ${typeString(type)}');
    }
  }

  void _assertNotBottom(TypeImpl type) {
    if (type.isBottom) {
      fail('isBottom must be false: ${typeString(type)}');
    }
  }

  void _assertNotNull(TypeImpl type) {
    if (typeSystem.isNull(type)) {
      fail('isNull must be false: ${typeString(type)}');
    }
  }

  void _assertNotObject(TypeImpl type) {
    if (typeSystem.isObject(type)) {
      fail('isObject must be false: ${typeString(type)}');
    }
  }

  void _assertNotSpecial(TypeImpl type) {
    _assertNotBottom(type);
    _assertNotNull(type);
    _assertNotObject(type);
    _assertNotTop(type);
  }

  void _assertNotTop(TypeImpl type) {
    if (typeSystem.isTop(type)) {
      fail('isTop must be false: ${typeString(type)}');
    }
  }

  void _assertNull(TypeImpl type) {
    if (!typeSystem.isNull(type)) {
      fail('isNull must be true: ${typeString(type)}');
    }
  }

  void _assertNullability(TypeImpl type, NullabilitySuffix expected) {
    if (type.nullabilitySuffix != expected) {
      fail('Expected $expected in ${typeString(type)}');
    }
  }

  void _assertNullabilityNone(TypeImpl type) {
    _assertNullability(type, NullabilitySuffix.none);
  }

  void _assertNullabilityQuestion(TypeImpl type) {
    _assertNullability(type, NullabilitySuffix.question);
  }

  void _assertObject(TypeImpl type) {
    if (!typeSystem.isObject(type)) {
      fail('isObject must be true: ${typeString(type)}');
    }
  }

  void _assertTop(TypeImpl type) {
    if (!typeSystem.isTop(type)) {
      fail('isTop must be true: ${typeString(type)}');
    }
  }

  void _checkLeastUpperBound(TypeImpl T1, TypeImpl T2, TypeImpl expected) {
    var expectedStr = typeString(expected);

    var result = typeSystem.leastUpperBound(T1, T2);
    var resultStr = typeString(result);
    expect(result, expected, reason: '''
expected: $expectedStr
actual: $resultStr
''');

    // Check that the result is an upper bound.
    expect(typeSystem.isSubtypeOf(T1, result), true);
    expect(typeSystem.isSubtypeOf(T2, result), true);

    // Check for symmetry.
    result = typeSystem.leastUpperBound(T2, T1);
    resultStr = typeString(result);
    expect(result, expected, reason: '''
expected: $expectedStr
actual: $resultStr
''');
  }

  void _checkLeastUpperBound2(String T1, String T2, String expected) {
    _checkLeastUpperBound(
      typeOfString(T1),
      typeOfString(T2),
      typeOfString(expected),
    );
  }
}
