blob: a64d04d869d57f8ac25d47e9835669b251533ecb [file]
// 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:analyzer/dart/element/nullability_suffix.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:analyzer/src/test_utilities/test_library_builder.dart';
import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
import '../../../generated/type_system_base.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: '$type');
}
void isMoreBottom(TypeImpl T, TypeImpl S) {
_assertIsBottomOrNull(T);
_assertIsBottomOrNull(S);
var str = '$T vs $S';
_checkUniqueTypeStr(_isMoreBottomChecked, str);
expect(typeSystem.isMoreBottom(T, S), isTrue, reason: str);
}
void isMoreTop(TypeImpl T, TypeImpl S) {
_assertIsTopOrObject(T);
_assertIsTopOrObject(S);
var str = '$T vs $S';
_checkUniqueTypeStr(_isMoreTopChecked, str);
expect(typeSystem.isMoreTop(T, S), isTrue, reason: str);
}
void isNotBottom(TypeImpl type) {
expect(type.isBottom, isFalse, reason: '$type');
}
void isNotMoreBottom(TypeImpl T, TypeImpl S) {
_assertIsBottomOrNull(T);
_assertIsBottomOrNull(S);
var str = '$T vs $S';
_checkUniqueTypeStr(_isMoreBottomChecked, str);
expect(typeSystem.isMoreBottom(T, S), isFalse, reason: str);
}
void isNotMoreTop(TypeImpl T, TypeImpl S) {
_assertIsTopOrObject(T);
_assertIsTopOrObject(S);
var str = '$T vs $S';
_checkUniqueTypeStr(_isMoreTopChecked, str);
expect(typeSystem.isMoreTop(T, S), isFalse, reason: str);
}
void isNotNull(TypeImpl type) {
expect(typeSystem.isNull(type), isFalse, reason: '$type');
}
void isNotObject(TypeImpl type) {
expect(typeSystem.isObject(type), isFalse, reason: '$type');
}
void isNotTop(TypeImpl type) {
expect(typeSystem.isTop(type), isFalse, reason: '$type');
}
void isNull(TypeImpl type) {
expect(typeSystem.isNull(type), isTrue, reason: '$type');
}
void isObject(TypeImpl type) {
expect(typeSystem.isObject(type), isTrue, reason: '$type');
}
void isTop(TypeImpl type) {
expect(typeSystem.isTop(type), isTrue, reason: '$type');
}
test_isBottom() {
// BOTTOM(Never) is true
isBottom(parseType('Never'));
isNotBottom(parseType('Never?'));
// BOTTOM(X&T) is true iff BOTTOM(T)
withTypeParameterScope('T extends Object?', (scope) {
isBottom(scope.parseType('T & Never'));
isNotBottom(scope.parseType('(T & Never)?'));
isNotBottom(scope.parseType('T & Never?'));
isNotBottom(scope.parseType('(T & Never?)?'));
});
// BOTTOM(X extends T) is true iff BOTTOM(T)
withTypeParameterScope('T extends Never', (scope) {
isBottom(scope.parseType('T'));
isNotBottom(scope.parseType('T?'));
});
withTypeParameterScope('T extends Never?', (scope) {
isNotBottom(scope.parseType('T'));
isNotBottom(scope.parseType('T?'));
});
// BOTTOM(T) is false otherwise
isNotBottom(parseType('dynamic'));
isNotBottom(parseType('InvalidType'));
isNotBottom(parseType('void'));
isNotBottom(parseType('Object'));
isNotBottom(parseType('Object?'));
isNotBottom(parseType('int'));
isNotBottom(parseType('int?'));
withTypeParameterScope('T extends num', (scope) {
isNotBottom(scope.parseType('T'));
isNotBottom(scope.parseType('T?'));
isNotBottom(scope.parseType('T & int'));
isNotBottom(scope.parseType('(T & int)?'));
});
}
test_isMoreBottom() {
// MOREBOTTOM(Never, T) = true
isMoreBottom(parseType('Never'), parseType('Never'));
isMoreBottom(parseType('Never'), parseType('Never?'));
isMoreBottom(parseType('Never'), parseType('Null'));
// MOREBOTTOM(T, Never) = false
isNotMoreBottom(parseType('Never?'), parseType('Never'));
isNotMoreBottom(parseType('Null'), parseType('Never'));
// MOREBOTTOM(Null, T) = true
isMoreBottom(parseType('Null'), parseType('Never?'));
isMoreBottom(parseType('Null'), parseType('Null'));
// MOREBOTTOM(T, Null) = false
isNotMoreBottom(parseType('Never?'), parseType('Null'));
// MOREBOTTOM(X&T, Y&S) = MOREBOTTOM(T, S)
withTypeParameterScope('T extends Object?, S extends Object?', (scope) {
isMoreBottom(
scope.parseType('T & Never'),
scope.parseType('(S & Never)?'),
);
});
// MOREBOTTOM(X&T, S) = true
withTypeParameterScope('T extends Object?, S extends Never', (scope) {
isMoreBottom(scope.parseType('T & Never'), scope.parseType('S'));
});
// MOREBOTTOM(T, X&S) = false
withTypeParameterScope('T extends Never, S extends Object?', (scope) {
isNotMoreBottom(scope.parseType('T'), scope.parseType('S & Never'));
});
// MOREBOTTOM(X extends T, Y extends S) = MOREBOTTOM(T, S)
withTypeParameterScope('T extends Never, S extends Never', (scope) {
isMoreBottom(scope.parseType('T'), scope.parseType('S?'));
});
}
test_isMoreTop() {
// MORETOP(void, T) = true
isMoreTop(parseType('void'), parseType('void'));
isMoreTop(parseType('void'), parseType('dynamic'));
isMoreTop(parseType('void'), parseType('InvalidType'));
isMoreTop(parseType('void'), parseType('Object'));
isMoreTop(parseType('void'), parseType('Object?'));
isMoreTop(parseType('void'), parseType('FutureOr<Object>'));
isMoreTop(parseType('void'), parseType('FutureOr<Object?>'));
// MORETOP(T, void) = false
isNotMoreTop(parseType('dynamic'), parseType('void'));
isNotMoreTop(parseType('InvalidType'), parseType('void'));
isNotMoreTop(parseType('Object'), parseType('void'));
isNotMoreTop(parseType('Object?'), parseType('void'));
isNotMoreTop(parseType('FutureOr<Object>'), parseType('void'));
isNotMoreTop(parseType('FutureOr<Object?>'), parseType('void'));
// MORETOP(dynamic, T) = true
isMoreTop(parseType('dynamic'), parseType('dynamic'));
isMoreTop(parseType('dynamic'), parseType('Object'));
isMoreTop(parseType('dynamic'), parseType('Object?'));
isMoreTop(parseType('dynamic'), parseType('FutureOr<Object>'));
isMoreTop(parseType('dynamic'), parseType('FutureOr<Object?>'));
// MORETOP(parseType('InvalidType'), T) = true
isMoreTop(parseType('InvalidType'), parseType('dynamic'));
isMoreTop(parseType('InvalidType'), parseType('Object'));
isMoreTop(parseType('InvalidType'), parseType('Object?'));
isMoreTop(parseType('InvalidType'), parseType('FutureOr<Object>'));
isMoreTop(parseType('InvalidType'), parseType('FutureOr<Object?>'));
// MORETOP(T, dynamic) = false
isNotMoreTop(parseType('Object'), parseType('dynamic'));
isNotMoreTop(parseType('Object?'), parseType('dynamic'));
isNotMoreTop(parseType('FutureOr<Object>'), parseType('dynamic'));
isNotMoreTop(parseType('FutureOr<Object?>'), parseType('dynamic'));
// MORETOP(T, parseType('InvalidType')) = false
isNotMoreTop(parseType('Object'), parseType('InvalidType'));
isNotMoreTop(parseType('Object?'), parseType('InvalidType'));
isNotMoreTop(parseType('FutureOr<Object>'), parseType('InvalidType'));
isNotMoreTop(parseType('FutureOr<Object?>'), parseType('InvalidType'));
// MORETOP(Object, T) = true
isMoreTop(parseType('Object'), parseType('Object'));
isMoreTop(parseType('Object'), parseType('Object?'));
isMoreTop(parseType('Object'), parseType('FutureOr<Object>'));
isMoreTop(parseType('Object'), parseType('FutureOr<Object>?'));
// MORETOP(T, Object) = false
isNotMoreTop(parseType('Object?'), parseType('Object'));
isNotMoreTop(parseType('FutureOr<Object>'), parseType('Object'));
isNotMoreTop(parseType('FutureOr<Object>?'), parseType('Object'));
// MORETOP(T?, S?) = MORETOP(T, S)
isMoreTop(parseType('Object?'), parseType('Object?'));
isMoreTop(parseType('FutureOr<void>?'), parseType('FutureOr<void>?'));
isMoreTop(parseType('FutureOr<void>?'), parseType('FutureOr<dynamic>?'));
isMoreTop(
parseType('FutureOr<void>?'),
parseType('FutureOr<InvalidType>?'),
);
isMoreTop(parseType('FutureOr<void>?'), parseType('FutureOr<Object>?'));
// MORETOP(T, S?) = true
isMoreTop(parseType('FutureOr<Object>'), parseType('FutureOr<void>?'));
isMoreTop(parseType('FutureOr<Object>'), parseType('FutureOr<dynamic>?'));
isMoreTop(
parseType('FutureOr<Object>'),
parseType('FutureOr<InvalidType>?'),
);
isMoreTop(parseType('FutureOr<Object>'), parseType('FutureOr<Object>?'));
// MORETOP(T?, S) = false
isNotMoreTop(parseType('FutureOr<void>?'), parseType('FutureOr<Object>'));
isNotMoreTop(
parseType('FutureOr<dynamic>?'),
parseType('FutureOr<Object>'),
);
isNotMoreTop(
parseType('FutureOr<InvalidType>?'),
parseType('FutureOr<Object>'),
);
isNotMoreTop(parseType('FutureOr<Object>?'), parseType('FutureOr<Object>'));
// MORETOP(FutureOr<T>, FutureOr<S>) = MORETOP(T, S)
isMoreTop(parseType('FutureOr<void>'), parseType('FutureOr<void>'));
isMoreTop(parseType('FutureOr<void>'), parseType('FutureOr<dynamic>'));
isMoreTop(parseType('FutureOr<void>'), parseType('FutureOr<InvalidType>'));
isMoreTop(parseType('FutureOr<void>'), parseType('FutureOr<Object>'));
isNotMoreTop(parseType('FutureOr<dynamic>'), parseType('FutureOr<void>'));
isNotMoreTop(
parseType('FutureOr<InvalidType>'),
parseType('FutureOr<void>'),
);
isNotMoreTop(parseType('FutureOr<Object>'), parseType('FutureOr<void>'));
}
test_isNull() {
// NULL(Null) is true
isNull(parseType('Null'));
// NULL(T?) is true iff NULL(T) or BOTTOM(T)
isNull(parseType('Never?'));
withTypeParameterScope('T extends Never', (scope) {
isNull(scope.parseType('T?'));
});
// NULL(T) is false otherwise
isNotNull(parseType('dynamic'));
isNotNull(parseType('InvalidType'));
isNotNull(parseType('void'));
isNotNull(parseType('Object'));
isNotNull(parseType('Object?'));
isNotNull(parseType('int'));
isNotNull(parseType('int?'));
isNotNull(parseType('FutureOr<Null>'));
isNotNull(parseType('FutureOr<Null>?'));
}
test_isObject() {
// OBJECT(Object) is true
isObject(parseType('Object'));
isNotObject(parseType('Object?'));
// OBJECT(FutureOr<T>) is OBJECT(T)
isObject(parseType('FutureOr<Object>'));
isNotObject(parseType('FutureOr<Object?>'));
isNotObject(parseType('FutureOr<Object>?'));
isNotObject(parseType('FutureOr<Object?>?'));
// OBJECT(T) is false otherwise
isNotObject(parseType('dynamic'));
isNotObject(parseType('InvalidType'));
isNotObject(parseType('void'));
isNotObject(parseType('int'));
}
test_isTop() {
// TOP(T?) is true iff TOP(T) or OBJECT(T)
isTop(parseType('Object?'));
isTop(parseType('FutureOr<dynamic>?'));
isTop(parseType('FutureOr<InvalidType>?'));
isTop(parseType('FutureOr<void>?'));
isTop(parseType('FutureOr<Object>?'));
isTop(parseType('FutureOr<Object?>?'));
isNotTop(parseType('FutureOr<int>?'));
isNotTop(parseType('FutureOr<int?>?'));
// TOP(dynamic) is true
isTop(parseType('dynamic'));
isTop(parseType('InvalidType'));
expect(typeSystem.isTop(UnknownInferredType.instance), isTrue);
// TOP(void) is true
isTop(parseType('void'));
// TOP(FutureOr<T>) is TOP(T)
isTop(parseType('FutureOr<dynamic>'));
isTop(parseType('FutureOr<InvalidType>'));
isTop(parseType('FutureOr<void>'));
isNotTop(parseType('FutureOr<Object>'));
isTop(parseType('FutureOr<Object?>'));
// TOP(T) is false otherwise
isNotTop(parseType('Object'));
isNotTop(parseType('int'));
isNotTop(parseType('int?'));
isNotTop(parseType('Never'));
isNotTop(parseType('Never?'));
}
/// [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: '$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: '$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(parseType('Never'), parseType('Object'));
check(parseType('Never'), parseType('Object?'));
check(parseType('Never'), parseType('int'));
check(parseType('Never'), parseType('int?'));
check(parseType('Never'), parseType('List<int>'));
check(parseType('Never'), parseType('List<int>?'));
check(parseType('Never'), parseType('FutureOr<int>'));
check(parseType('Never'), parseType('FutureOr<int>?'));
withTypeParameterScope('T extends Never', (scope) {
var T = scope.parseType('T');
check(T, parseType('int'));
check(T, parseType('int?'));
});
withTypeParameterScope('T extends Object?', (scope) {
var T = scope.parseType('T & Never');
check(T, parseType('int'));
check(T, parseType('int?'));
});
}
test_bottom_bottom() {
void check(TypeImpl T1, TypeImpl T2) {
_assertBottom(T1);
_assertBottom(T2);
_checkGreatestLowerBound(T1, T2, T1);
}
withTypeParameterScope('T extends Never', (scope) {
check(parseType('Never'), scope.parseType('T'));
});
withTypeParameterScope('T extends Object?', (scope) {
check(parseType('Never'), scope.parseType('T & Never'));
});
}
test_functionType2_parameters_conflicts() {
_checkGreatestLowerBound(
parseFunctionType('void Function(int a)'),
parseFunctionType('void Function({int a})'),
parseType('Never'),
);
_checkGreatestLowerBound(
parseFunctionType('void Function([int a])'),
parseFunctionType('void Function({int a})'),
parseType('Never'),
);
}
test_functionType2_parameters_named() {
_checkGreatestLowerBound(
parseFunctionType('void Function()'),
parseFunctionType('void Function()'),
parseFunctionType('void Function()'),
);
{
_checkGreatestLowerBound(
parseFunctionType('void Function({int a})'),
parseFunctionType('void Function({int a})'),
parseFunctionType('void Function({int a})'),
);
_checkGreatestLowerBound(
parseFunctionType('void Function({int a})'),
parseFunctionType('void Function({required int a})'),
parseFunctionType('void Function({int a})'),
);
_checkGreatestLowerBound(
parseFunctionType('void Function({required int a})'),
parseFunctionType('void Function({required int a})'),
parseFunctionType('void Function({required int a})'),
);
}
{
_checkGreatestLowerBound(
parseFunctionType('void Function({int a, int b})'),
parseFunctionType('void Function({int a, int c})'),
parseFunctionType('void Function({int a, int b, int c})'),
);
_checkGreatestLowerBound(
parseFunctionType('void Function({int a, required int b})'),
parseFunctionType('void Function({int a, required int c})'),
parseFunctionType('void Function({int a, int b, int c})'),
);
}
{
_checkGreatestLowerBound(
parseFunctionType('void Function({int a})'),
parseFunctionType('void Function({num a})'),
parseFunctionType('void Function({num a})'),
);
_checkGreatestLowerBound(
parseFunctionType('void Function({int a})'),
parseFunctionType('void Function({double a})'),
parseFunctionType('void Function({num a})'),
);
_checkGreatestLowerBound(
parseFunctionType('void Function({int a})'),
parseFunctionType('void Function({double? a})'),
parseFunctionType('void Function({num? a})'),
);
_checkGreatestLowerBound(
parseFunctionType('void Function({int a})'),
parseFunctionType('void Function({double a})'),
parseFunctionType('void Function({num a})'),
);
}
}
test_functionType2_parameters_positional() {
_checkGreatestLowerBound(
parseFunctionType('void Function()'),
parseFunctionType('void Function()'),
parseFunctionType('void Function()'),
);
_checkGreatestLowerBound(
parseFunctionType('void Function(int)'),
parseFunctionType('void Function(int)'),
parseFunctionType('void Function(int)'),
);
_checkGreatestLowerBound(
parseFunctionType('void Function(int)'),
parseFunctionType('void Function(num)'),
parseFunctionType('void Function(num)'),
);
_checkGreatestLowerBound(
parseFunctionType('void Function(int)'),
parseFunctionType('void Function(double)'),
parseFunctionType('void Function(num)'),
);
_checkGreatestLowerBound(
parseFunctionType('void Function(int)'),
parseFunctionType('void Function(double?)'),
parseFunctionType('void Function(num?)'),
);
_checkGreatestLowerBound(
parseFunctionType('void Function(int)'),
parseFunctionType('void Function(double)'),
parseFunctionType('void Function(num)'),
);
{
_checkGreatestLowerBound(
parseFunctionType('void Function(int)'),
parseFunctionType('void Function([int])'),
parseFunctionType('void Function([int])'),
);
_checkGreatestLowerBound(
parseFunctionType('void Function(int)'),
parseFunctionType('void Function()'),
parseFunctionType('void Function([int])'),
);
_checkGreatestLowerBound(
parseFunctionType('void Function([int])'),
parseFunctionType('void Function([int])'),
parseFunctionType('void Function([int])'),
);
_checkGreatestLowerBound(
parseFunctionType('void Function([int])'),
parseFunctionType('void Function()'),
parseFunctionType('void Function([int])'),
);
}
}
test_functionType2_returnType() {
_checkGreatestLowerBound(
parseFunctionType('int Function()'),
parseFunctionType('int Function()'),
parseFunctionType('int Function()'),
);
_checkGreatestLowerBound(
parseFunctionType('int Function()'),
parseFunctionType('num Function()'),
parseFunctionType('int Function()'),
);
_checkGreatestLowerBound(
parseFunctionType('int Function()'),
parseFunctionType('void Function()'),
parseFunctionType('int Function()'),
);
_checkGreatestLowerBound(
parseFunctionType('int Function()'),
parseFunctionType('Never Function()'),
parseFunctionType('Never Function()'),
);
}
test_functionType2_typeParameters() {
void check(FunctionTypeImpl T1, FunctionTypeImpl T2, TypeImpl expected) {
_assertNullabilityNone(T1);
_assertNullabilityNone(T2);
_checkGreatestLowerBound(T1, T2, expected, checkSubtype: false);
}
check(
parseFunctionType('void Function<T>()'),
parseFunctionType('void Function()'),
parseType('Never'),
);
check(
parseFunctionType('void Function<T extends int>()'),
parseFunctionType('void Function<T extends num>()'),
parseType('Never'),
);
check(
parseFunctionType('T Function<T extends num>()'),
parseFunctionType('U Function<U extends num>()'),
parseFunctionType('R Function<R extends num>()'),
);
}
test_functionType_interfaceType() {
void check(FunctionTypeImpl T1, TypeImpl T2, TypeImpl expected) {
_checkGreatestLowerBound(T1, T2, expected);
}
check(
parseFunctionType('void Function()'),
parseType('int'),
parseType('Never'),
);
}
test_functionType_interfaceType_Function() {
void check(FunctionTypeImpl T1) {
_assertNullabilityNone(T1);
_checkGreatestLowerBound(T1, parseType('Function'), T1);
}
check(parseFunctionType('void Function()'));
check(parseFunctionType('int Function(num?)'));
}
test_futureOr() {
InterfaceTypeImpl futureOrFunction(String str) {
var result = parseInterfaceType(str);
expect(result.getDisplayString(), str);
return result;
}
// DOWN(FutureOr<T1>, FutureOr<T2>) = FutureOr<S>, S = DOWN(T1, T2)
_checkGreatestLowerBound(
parseType('FutureOr<int>'),
parseType('FutureOr<num>'),
parseType('FutureOr<int>'),
);
_checkGreatestLowerBound(
futureOrFunction('FutureOr<void Function(int)>'),
futureOrFunction('FutureOr<void Function(double)>'),
futureOrFunction('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(
parseType('FutureOr<num>'),
parseType('Future<int>'),
parseType('Future<int>'),
);
_checkGreatestLowerBound(
parseType('FutureOr<int>'),
parseType('Future<num>'),
parseType('Future<int>'),
);
// DOWN(FutureOr<T1>, T2) = S, S = DOWN(T1, T2)
// DOWN(T1, FutureOr<T2>) = S, S = DOWN(T1, T2)
_checkGreatestLowerBound(
parseType('FutureOr<num>'),
parseType('int'),
parseType('int'),
);
_checkGreatestLowerBound(
parseType('FutureOr<int>'),
parseType('num'),
parseType('int'),
);
}
test_identical() {
void check(TypeImpl type) {
_checkGreatestLowerBound(type, type, type);
}
check(parseType('int'));
check(parseType('int?'));
check(parseType('List<int>'));
}
test_interfaceType2() {
void check(TypeImpl T1, TypeImpl T2, TypeImpl expected) {
_assertNullabilityNone(T1);
_assertNullabilityNone(T2);
_checkGreatestLowerBound(T1, T2, expected);
}
check(parseType('int'), parseType('int'), parseType('int'));
check(parseType('num'), parseType('int'), parseType('int'));
check(parseType('double'), parseType('int'), parseType('Never'));
check(
parseType('List<int>'),
parseType('List<int>'),
parseType('List<int>'),
);
check(
parseType('List<num>'),
parseType('List<int>'),
parseType('List<int>'),
);
check(
parseType('List<double>'),
parseType('List<int>'),
parseType('Never'),
);
}
void test_interfaceType2_interfaces() {
// class A
// class B implements A
// class C implements B
buildTestLibrary(
classes: [
ClassSpec('class A'),
ClassSpec('class B implements A'),
ClassSpec('class C implements B'),
],
);
_checkGreatestLowerBound(
parseInterfaceType('A'),
parseInterfaceType('C'),
parseInterfaceType('C'),
);
}
void test_interfaceType2_mixins() {
// class A
// class B
// class C
// class D extends A with B, C
buildTestLibrary(
classes: [
ClassSpec('class A'),
ClassSpec('class B'),
ClassSpec('class C'),
ClassSpec('class D extends A with B, C'),
],
);
_checkGreatestLowerBound(
parseInterfaceType('A'),
parseInterfaceType('D'),
parseInterfaceType('D'),
);
_checkGreatestLowerBound(
parseInterfaceType('B'),
parseInterfaceType('D'),
parseInterfaceType('D'),
);
_checkGreatestLowerBound(
parseInterfaceType('C'),
parseInterfaceType('D'),
parseInterfaceType('D'),
);
}
void test_interfaceType2_superType() {
// class A
// class B extends A
// class C extends B
buildTestLibrary(
classes: [
ClassSpec('class A'),
ClassSpec('class B extends A'),
ClassSpec('class C extends B'),
],
);
_checkGreatestLowerBound(
parseInterfaceType('A'),
parseInterfaceType('C'),
parseInterfaceType('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(parseType('int'), parseType('int?'), parseType('int'));
check(parseType('num'), parseType('int?'), parseType('int'));
check(parseType('int'), parseType('num?'), parseType('int'));
check(parseType('double'), parseType('int?'), parseType('Never'));
check(parseType('int'), parseType('double?'), parseType('Never'));
}
test_null_any() {
void check(TypeImpl T2, TypeImpl expected) {
_assertNotBottom(T2);
_assertNotNull(T2);
_assertNotTop(T2);
_checkGreatestLowerBound(parseType('Null'), T2, expected);
}
void checkNull(TypeImpl T2) {
check(T2, parseType('Null'));
}
void checkNever(TypeImpl T2) {
check(T2, parseType('Never'));
}
checkNull(parseType('FutureOr<Null>'));
checkNull(parseType('FutureOr<Null>?'));
checkNever(parseType('Object'));
checkNever(parseType('int'));
checkNull(parseType('int?'));
checkNever(parseType('List<int>'));
checkNull(parseType('List<int>?'));
checkNever(parseType('List<int?>'));
checkNull(parseType('List<int?>?'));
}
test_null_null() {
void check(TypeImpl T1, TypeImpl T2) {
_assertNull(T1);
_assertNull(T2);
_assertNotBottom(T1);
_assertNotBottom(T2);
_checkGreatestLowerBound(T1, T2, T1);
}
check(parseType('Null'), parseType('Null'));
}
test_object_any() {
void check(TypeImpl T2, TypeImpl expected) {
_assertNotObject(T2);
_checkGreatestLowerBound(parseType('Object'), T2, expected);
}
void checkNever(TypeImpl T2) {
check(T2, parseType('Never'));
}
check(parseType('int'), parseType('int'));
check(parseType('int?'), parseType('int'));
check(parseType('FutureOr<int>'), parseType('FutureOr<int>'));
check(parseType('FutureOr<int>?'), parseType('FutureOr<int>'));
check(parseType('FutureOr<int>'), parseType('FutureOr<int>'));
checkNever(parseType('FutureOr<int?>'));
checkNever(parseType('FutureOr<int?>?'));
checkNever(parseType('FutureOr<int?>'));
withTypeParameterScope('T extends Object', (scope) {
check(scope.parseType('T'), scope.parseType('T'));
check(scope.parseType('T?'), scope.parseType('T'));
});
withTypeParameterScope('T extends Object?', (scope) {
check(scope.parseType('T'), scope.parseType('T & Object'));
check(scope.parseType('T?'), scope.parseType('T & Object'));
});
withTypeParameterScope('T extends FutureOr<Object?>', (scope) {
checkNever(scope.parseType('T'));
checkNever(scope.parseType('T?'));
});
}
test_object_object() {
void check(TypeImpl T1, TypeImpl T2) {
_assertObject(T1);
_assertObject(T2);
_checkGreatestLowerBound(T1, T2, T1);
}
check(parseType('FutureOr<Object>'), parseType('Object'));
check(
parseType('FutureOr<FutureOr<Object>>'),
parseType('FutureOr<Object>'),
);
}
test_question_question() {
void check(TypeImpl T1, TypeImpl T2, TypeImpl expected) {
_assertNullabilityQuestion(T1);
_assertNullabilityQuestion(T2);
_assertNotSpecial(T1);
_assertNotSpecial(T2);
_checkGreatestLowerBound(T1, T2, expected);
}
check(parseType('int?'), parseType('int?'), parseType('int?'));
check(parseType('num?'), parseType('int?'), parseType('int?'));
check(parseType('int?'), parseType('num?'), parseType('int?'));
check(parseType('double?'), parseType('int?'), parseType('Never?'));
check(parseType('int?'), parseType('double?'), parseType('Never?'));
}
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() {
withTypeParameterScope('T', (scope) {
List<TypeImpl> types = [
parseType('dynamic'),
parseType('InvalidType'),
parseType('void'),
parseType('Never'),
scope.parseType('T'),
parseType('int'),
parseFunctionType('void Function()'),
];
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(parseType('void'), parseType('Object'));
check(parseType('void'), parseType('int'));
check(parseType('void'), parseType('int?'));
check(parseType('void'), parseType('List<int>'));
check(parseType('void'), parseType('FutureOr<int>'));
check(parseType('void'), parseType('Never'));
check(parseType('void'), parseFunctionType('void Function()'));
check(parseType('void'), parseType('(int, int)'));
check(parseType('dynamic'), parseType('Object'));
check(parseType('dynamic'), parseType('int'));
check(parseType('dynamic'), parseType('int?'));
check(parseType('dynamic'), parseType('List<int>'));
check(parseType('dynamic'), parseType('FutureOr<int>'));
check(parseType('dynamic'), parseType('Never'));
check(parseType('dynamic'), parseFunctionType('void Function()'));
check(parseType('dynamic'), parseType('(int, int)'));
check(parseType('InvalidType'), parseType('Object'));
check(parseType('InvalidType'), parseType('int'));
check(parseType('InvalidType'), parseType('int?'));
check(parseType('InvalidType'), parseType('List<int>'));
check(parseType('InvalidType'), parseType('FutureOr<int>'));
check(parseType('InvalidType'), parseType('Never'));
check(parseType('InvalidType'), parseFunctionType('void Function()'));
check(parseType('InvalidType'), parseType('(int, int)'));
check(parseType('Object?'), parseType('Object'));
check(parseType('Object?'), parseType('int'));
check(parseType('Object?'), parseType('int?'));
check(parseType('Object?'), parseType('List<int>'));
check(parseType('Object?'), parseType('FutureOr<int>'));
check(parseType('Object?'), parseType('Never'));
check(parseType('Object?'), parseFunctionType('void Function()'));
check(parseType('Object?'), parseType('(int, int)'));
check(parseType('FutureOr<void>'), parseType('int'));
check(parseType('FutureOr<void>?'), parseType('int'));
}
test_top_top() {
void check(TypeImpl T1, TypeImpl T2) {
_assertTop(T1);
_assertTop(T2);
_checkGreatestLowerBound(T1, T2, T2);
}
check(parseType('void'), parseType('dynamic'));
check(parseType('void'), parseType('InvalidType'));
check(parseType('void'), parseType('Object?'));
check(parseType('void'), parseType('FutureOr<void>'));
check(parseType('void'), parseType('FutureOr<dynamic>'));
check(parseType('void'), parseType('FutureOr<InvalidType>'));
check(parseType('void'), parseType('FutureOr<Object?>'));
check(parseType('dynamic'), parseType('Object?'));
check(parseType('dynamic'), parseType('FutureOr<void>'));
check(parseType('dynamic'), parseType('FutureOr<dynamic>'));
check(parseType('dynamic'), parseType('FutureOr<Object?>'));
check(parseType('InvalidType'), parseType('Object?'));
check(parseType('InvalidType'), parseType('FutureOr<void>'));
check(parseType('InvalidType'), parseType('FutureOr<dynamic>'));
check(parseType('InvalidType'), parseType('FutureOr<Object?>'));
check(parseType('Object?'), parseType('FutureOr<void>?'));
check(parseType('Object?'), parseType('FutureOr<dynamic>?'));
check(parseType('Object?'), parseType('FutureOr<InvalidType>?'));
check(parseType('Object?'), parseType('FutureOr<Object>?'));
check(parseType('Object?'), parseType('FutureOr<Object?>?'));
check(parseType('FutureOr<void>'), parseType('Object?'));
check(parseType('FutureOr<dynamic>'), parseType('Object?'));
check(parseType('FutureOr<InvalidType>'), parseType('Object?'));
check(parseType('FutureOr<Object?>'), parseType('Object?'));
check(parseType('FutureOr<void>'), parseType('FutureOr<dynamic>'));
check(parseType('FutureOr<void>'), parseType('FutureOr<InvalidType>'));
check(parseType('FutureOr<void>'), parseType('FutureOr<Object?>'));
check(parseType('FutureOr<dynamic>'), parseType('FutureOr<Object?>'));
check(parseType('FutureOr<InvalidType>'), parseType('FutureOr<Object?>'));
}
test_typeParameter() {
void check({String? bound, required TypeImpl T2}) {
withTypeParameterScope(bound == null ? 'T' : 'T extends $bound', (scope) {
_checkGreatestLowerBound(scope.parseType('T'), T2, parseType('Never'));
});
}
check(T2: parseFunctionType('void Function()'));
check(T2: parseType('int'));
check(bound: 'num', T2: parseType('int'));
}
void _checkGreatestLowerBound(
TypeImpl T1,
TypeImpl T2,
TypeImpl expected, {
bool checkSubtype = true,
}) {
var expectedStr = '$expected';
var result = typeSystem.greatestLowerBound(T1, T2);
var resultStr = '$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 = '$result';
expect(
result,
expected,
reason:
'''
expected: $expectedStr
actual: $resultStr
''',
);
}
void _checkGreatestLowerBound2(String T1, String T2, String expected) {
_checkGreatestLowerBound(parseType(T1), parseType(T2), parseType(expected));
}
}
@reflectiveTest
class UpperBound_FunctionTypes_Test extends _BoundsTestBase {
void test_nested2_upParameterType() {
var T1 = parseFunctionType(
'void Function(void Function(String, int, int))',
);
expect('$T1', 'void Function(void Function(String, int, int))');
var T2 = parseFunctionType(
'void Function(void Function(int, double, num))',
);
expect('$T2', 'void Function(void Function(int, double, num))');
var expected = parseFunctionType(
'void Function(void Function(Object, num, num))',
);
expect('$expected', 'void Function(void Function(Object, num, num))');
_checkLeastUpperBound(T1, T2, expected);
}
void test_nested3_downParameterTypes() {
var T1 = parseFunctionType(
'void Function(void Function(void Function(String, int, int)))',
);
expect(
'$T1',
'void Function(void Function(void Function(String, int, int)))',
);
var T2 = parseFunctionType(
'void Function(void Function(void Function(int, double, num)))',
);
expect(
'$T2',
'void Function(void Function(void Function(int, double, num)))',
);
var expected = parseFunctionType(
'void Function(void Function(void Function(Never, Never, int)))',
);
expect(
'$expected',
'void Function(void Function(void Function(Never, Never, int)))',
);
_checkLeastUpperBound(T1, T2, expected);
}
void test_parameters_fuzzyArrows() {
var T1 = parseFunctionType('void Function(dynamic)');
var T2 = parseFunctionType('void Function(int)');
var expected = parseFunctionType('void Function(int)');
_checkLeastUpperBound(T1, T2, expected);
}
test_parameters_optionalNamed() {
_checkLeastUpperBound(
parseFunctionType('void Function({int a})'),
parseFunctionType('void Function()'),
parseFunctionType('void Function()'),
);
_checkLeastUpperBound(
parseFunctionType('void Function({int a})'),
parseFunctionType('void Function({int b})'),
parseFunctionType('void Function()'),
);
_checkLeastUpperBound(
parseFunctionType('void Function({int a})'),
parseFunctionType('void Function({int a})'),
parseFunctionType('void Function({int a})'),
);
_checkLeastUpperBound(
parseFunctionType('void Function({int a})'),
parseFunctionType('void Function({int? a})'),
parseFunctionType('void Function({int a})'),
);
_checkLeastUpperBound(
parseFunctionType('void Function({int a, double b})'),
parseFunctionType('void Function({int a})'),
parseFunctionType('void Function({int a})'),
);
}
test_parameters_optionalPositional() {
_checkLeastUpperBound(
parseFunctionType('void Function([int])'),
parseFunctionType('void Function()'),
parseFunctionType('void Function()'),
);
_checkLeastUpperBound(
parseFunctionType('void Function([int, double])'),
parseFunctionType('void Function([int])'),
parseFunctionType('void Function([int])'),
);
_checkLeastUpperBound(
parseFunctionType('void Function([int])'),
parseFunctionType('void Function([int])'),
parseFunctionType('void Function([int])'),
);
_checkLeastUpperBound(
parseFunctionType('void Function([int])'),
parseFunctionType('void Function([int?])'),
parseFunctionType('void Function([int])'),
);
_checkLeastUpperBound(
parseFunctionType('void Function([int])'),
parseFunctionType('void Function([double])'),
parseFunctionType('void Function([Never])'),
);
_checkLeastUpperBound(
parseFunctionType('void Function([int])'),
parseFunctionType('void Function([num])'),
parseFunctionType('void Function([int])'),
);
_checkLeastUpperBound(
parseFunctionType('void Function([double, num])'),
parseFunctionType('void Function([num, int])'),
parseFunctionType('void Function([double, int])'),
);
}
test_parameters_requiredNamed() {
_checkLeastUpperBound(
parseFunctionType('void Function(int a)'),
parseFunctionType('void Function({required int a})'),
parseType('Function'),
);
_checkLeastUpperBound(
parseFunctionType('void Function([int a])'),
parseFunctionType('void Function({required int a})'),
parseType('Function'),
);
_checkLeastUpperBound(
parseFunctionType('void Function({int b})'),
parseFunctionType('void Function({required int a})'),
parseType('Function'),
);
_checkLeastUpperBound(
parseFunctionType('void Function({int a})'),
parseFunctionType('void Function({required int a})'),
parseFunctionType('void Function({required int a})'),
);
_checkLeastUpperBound(
parseFunctionType('void Function({int a, required int b})'),
parseFunctionType('void Function({required int b})'),
parseFunctionType('void Function({required int b})'),
);
_checkLeastUpperBound(
parseFunctionType('void Function({required int a})'),
parseFunctionType('void Function({required num a})'),
parseFunctionType('void Function({required int a})'),
);
}
test_parameters_requiredPositional() {
_checkLeastUpperBound(
parseFunctionType('void Function(int)'),
parseFunctionType('void Function()'),
parseType('Function'),
);
_checkLeastUpperBound(
parseFunctionType('void Function(int)'),
parseFunctionType('void Function(int)'),
parseFunctionType('void Function(int)'),
);
_checkLeastUpperBound(
parseFunctionType('void Function(int)'),
parseFunctionType('void Function(int?)'),
parseFunctionType('void Function(int)'),
);
_checkLeastUpperBound(
parseFunctionType('void Function(int)'),
parseFunctionType('void Function(double)'),
parseFunctionType('void Function(Never)'),
);
_checkLeastUpperBound(
parseFunctionType('void Function(int)'),
parseFunctionType('void Function(num)'),
parseFunctionType('void Function(int)'),
);
_checkLeastUpperBound(
parseFunctionType('void Function(double, num)'),
parseFunctionType('void Function(num, int)'),
parseFunctionType('void Function(double, int)'),
);
}
void test_parameters_requiredPositional_differentArity() {
var T1 = parseFunctionType('void Function(int, int)');
var T2 = parseFunctionType('void Function(int, int, int)');
_checkLeastUpperBound(T1, T2, typeProvider.functionType);
}
test_returnType() {
_checkLeastUpperBound(
parseFunctionType('int Function()'),
parseFunctionType('int Function()'),
parseFunctionType('int Function()'),
);
_checkLeastUpperBound(
parseFunctionType('int Function()'),
parseFunctionType('int? Function()'),
parseFunctionType('int? Function()'),
);
_checkLeastUpperBound(
parseFunctionType('int Function()'),
parseFunctionType('num Function()'),
parseFunctionType('num Function()'),
);
_checkLeastUpperBound(
parseFunctionType('int? Function()'),
parseFunctionType('num Function()'),
parseFunctionType('num? Function()'),
);
_checkLeastUpperBound(
parseFunctionType('int Function()'),
parseFunctionType('dynamic Function()'),
parseFunctionType('dynamic Function()'),
);
_checkLeastUpperBound(
parseFunctionType('int Function()'),
parseFunctionType('InvalidType Function()'),
parseFunctionType('InvalidType Function()'),
);
_checkLeastUpperBound(
parseFunctionType('int Function()'),
parseFunctionType('Never Function()'),
parseFunctionType('int Function()'),
);
}
void test_sameType_withNamed() {
var T1 = parseFunctionType('int Function(String, int, num, {num n})');
var T2 = parseFunctionType('int Function(String, int, num, {num n})');
var expected = parseFunctionType('int Function(String, int, num, {num n})');
_checkLeastUpperBound(T1, T2, expected);
}
void test_sameType_withOptional() {
var T1 = parseFunctionType('int Function(String, int, num, [double])');
var T2 = parseFunctionType('int Function(String, int, num, [double])');
var expected = parseFunctionType(
'int Function(String, int, num, [double])',
);
_checkLeastUpperBound(T1, T2, expected);
}
test_typeParameters() {
void check(FunctionTypeImpl T1, FunctionTypeImpl T2, TypeImpl expected) {
_assertNullabilityNone(T1);
_assertNullabilityNone(T2);
_checkLeastUpperBound(T1, T2, expected);
}
check(
parseFunctionType('void Function<T>()'),
parseFunctionType('void Function()'),
parseType('Function'),
);
check(
parseFunctionType('void Function<T extends int>()'),
parseFunctionType('void Function<T extends num>()'),
parseType('Function'),
);
{
var T1 = parseFunctionType('T Function<T extends num>()');
var T2 = parseFunctionType('U Function<U extends num>()');
{
var result = typeSystem.leastUpperBound(T1, T2);
var resultStr = '$result';
expect(resultStr, 'T Function<T extends num>()');
}
{
var result = typeSystem.leastUpperBound(T2, T1);
var resultStr = '$result';
expect(resultStr, 'U Function<U extends num>()');
}
}
}
test_unrelated() {
var T1 = parseFunctionType('int Function()');
_checkLeastUpperBound(T1, parseType('int'), parseType('Object'));
_checkLeastUpperBound(T1, parseType('int?'), parseType('Object?'));
_checkLeastUpperBound(
T1,
parseType('FutureOr<Function?>'),
parseType('Object?'),
);
}
}
@reflectiveTest
class UpperBound_InterfaceTypes_Test extends _BoundsTestBase {
test_directInterface() {
// class A
// class B implements A
// class C implements B
buildTestLibrary(
classes: [
ClassSpec('class A'),
ClassSpec('class B implements A'),
ClassSpec('class C implements B'),
],
);
_checkLeastUpperBound(
parseInterfaceType('B'),
parseInterfaceType('C'),
parseInterfaceType('B'),
);
}
test_directSuperclass() {
// class A
// class B extends A
// class C extends B
buildTestLibrary(
classes: [
ClassSpec('class A'),
ClassSpec('class B extends A'),
ClassSpec('class C extends B'),
],
);
_checkLeastUpperBound(
parseInterfaceType('B'),
parseInterfaceType('C'),
parseInterfaceType('B'),
);
}
void test_directSuperclass_nullability() {
buildTestLibrary(
classes: [ClassSpec('class A'), ClassSpec('class B extends A')],
);
var aQuestion = parseInterfaceType('A?');
var aNone = parseInterfaceType('A');
var bNoneQuestion = parseInterfaceType('B?');
var bNoneNone = parseInterfaceType('B');
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(
parseType('String'),
parseType('num'),
parseType('Object'),
);
}
void test_mixinAndClass_constraintAndInterface() {
buildTestLibrary(
classes: [ClassSpec('class A'), ClassSpec('class B implements A')],
mixins: [MixinSpec('mixin M on A')],
);
_checkLeastUpperBound(
parseInterfaceType('B'),
parseInterfaceType('M'),
parseInterfaceType('A'),
);
}
void test_mixinAndClass_object() {
buildTestLibrary(
classes: [ClassSpec('class A')],
mixins: [MixinSpec('mixin M')],
);
_checkLeastUpperBound(
parseInterfaceType('A'),
parseInterfaceType('M'),
parseType('Object'),
);
}
void test_mixinAndClass_sharedInterface() {
buildTestLibrary(
classes: [ClassSpec('class A'), ClassSpec('class B implements A')],
mixins: [MixinSpec('mixin M implements A')],
);
_checkLeastUpperBound(
parseInterfaceType('B'),
parseInterfaceType('M'),
parseInterfaceType('A'),
);
}
void test_sameElement_nullability() {
buildTestLibrary(classes: [ClassSpec('class A')]);
var aQuestion = parseInterfaceType('A?');
var aNone = parseInterfaceType('A');
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 {}
buildTestLibrary(
classes: [ClassSpec('class B with M'), ClassSpec('class C with M')],
mixins: [MixinSpec('mixin M')],
);
_checkLeastUpperBound(
parseInterfaceType('B'),
parseInterfaceType('C'),
parseInterfaceType('M'),
);
}
void test_sharedMixin2() {
// mixin M1 {}
// mixin M2 {}
// mixin M3 {}
// class A with M1, M2 {}
// class B with M1, M3 {}
buildTestLibrary(
classes: [
ClassSpec('class A with M1, M2'),
ClassSpec('class B with M1, M3'),
],
mixins: [
MixinSpec('mixin M1'),
MixinSpec('mixin M2'),
MixinSpec('mixin M3'),
],
);
_checkLeastUpperBound(
parseInterfaceType('A'),
parseInterfaceType('B'),
parseInterfaceType('M1'),
);
}
void test_sharedMixin3() {
// mixin M1 {}
// mixin M2 {}
// mixin M3 {}
// class A with M2, M1 {}
// class B with M3, M1 {}
buildTestLibrary(
classes: [
ClassSpec('class A with M2, M1'),
ClassSpec('class B with M3, M1'),
],
mixins: [
MixinSpec('mixin M1'),
MixinSpec('mixin M2'),
MixinSpec('mixin M3'),
],
);
_checkLeastUpperBound(
parseInterfaceType('A'),
parseInterfaceType('B'),
parseInterfaceType('M1'),
);
}
void test_sharedSuperclass1() {
// class A {}
// class B extends A {}
// class C extends A {}
buildTestLibrary(
classes: [
ClassSpec('class A'),
ClassSpec('class B extends A'),
ClassSpec('class C extends A'),
],
);
_checkLeastUpperBound(
parseInterfaceType('B'),
parseInterfaceType('C'),
parseInterfaceType('A'),
);
}
void test_sharedSuperclass1_nullability() {
buildTestLibrary(
classes: [
ClassSpec('class A'),
ClassSpec('class B extends A'),
ClassSpec('class C extends A'),
],
);
var aQuestion = parseInterfaceType('A?');
var aNone = parseInterfaceType('A');
var bNoneQuestion = parseInterfaceType('B?');
var bNoneNone = parseInterfaceType('B');
var cNoneQuestion = parseInterfaceType('C?');
var cNoneNone = parseInterfaceType('C');
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 {}
buildTestLibrary(
classes: [
ClassSpec('class A'),
ClassSpec('class B extends A'),
ClassSpec('class C extends A'),
ClassSpec('class D extends C'),
],
);
_checkLeastUpperBound(
parseInterfaceType('B'),
parseInterfaceType('D'),
parseInterfaceType('A'),
);
}
void test_sharedSuperclass3() {
// class A {}
// class B extends A {}
// class C extends B {}
// class D extends B {}
buildTestLibrary(
classes: [
ClassSpec('class A'),
ClassSpec('class B extends A'),
ClassSpec('class C extends B'),
ClassSpec('class D extends B'),
],
);
_checkLeastUpperBound(
parseInterfaceType('C'),
parseInterfaceType('D'),
parseInterfaceType('B'),
);
}
void test_sharedSuperclass4() {
// class A {}
// class A2 {}
// class A3 {}
// class B extends A implements A2 {}
// class C extends A implement A3 {}
buildTestLibrary(
classes: [
ClassSpec('class A'),
ClassSpec('class A2'),
ClassSpec('class A3'),
ClassSpec('class B extends A implements A2'),
ClassSpec('class C extends A implements A3'),
],
);
_checkLeastUpperBound(
parseInterfaceType('B'),
parseInterfaceType('C'),
parseInterfaceType('A'),
);
}
void test_sharedSuperinterface1() {
// class A {}
// class B implements A {}
// class C implements A {}
buildTestLibrary(
classes: [
ClassSpec('class A'),
ClassSpec('class B implements A'),
ClassSpec('class C implements A'),
],
);
_checkLeastUpperBound(
parseInterfaceType('B'),
parseInterfaceType('C'),
parseInterfaceType('A'),
);
}
void test_sharedSuperinterface2() {
// class A {}
// class B implements A {}
// class C implements A {}
// class D implements C {}
buildTestLibrary(
classes: [
ClassSpec('class A'),
ClassSpec('class B implements A'),
ClassSpec('class C implements A'),
ClassSpec('class D implements C'),
],
);
_checkLeastUpperBound(
parseInterfaceType('B'),
parseInterfaceType('D'),
parseInterfaceType('A'),
);
}
void test_sharedSuperinterface3() {
// class A {}
// class B implements A {}
// class C implements B {}
// class D implements B {}
buildTestLibrary(
classes: [
ClassSpec('class A'),
ClassSpec('class B implements A'),
ClassSpec('class C implements B'),
ClassSpec('class D implements B'),
],
);
_checkLeastUpperBound(
parseInterfaceType('C'),
parseInterfaceType('D'),
parseInterfaceType('B'),
);
}
void test_sharedSuperinterface4() {
// class A {}
// class A2 {}
// class A3 {}
// class B implements A, A2 {}
// class C implements A, A3 {}
buildTestLibrary(
classes: [
ClassSpec('class A'),
ClassSpec('class A2'),
ClassSpec('class A3'),
ClassSpec('class B implements A, A2'),
ClassSpec('class C implements A, A3'),
],
);
_checkLeastUpperBound(
parseInterfaceType('B'),
parseInterfaceType('C'),
parseInterfaceType('A'),
);
}
}
@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(parseType('Never'), parseType('dynamic'));
check(parseType('Never'), parseType('InvalidType'));
check(parseType('Never'), parseType('Object'));
check(parseType('Never'), parseType('Object?'));
check(parseType('Never'), parseType('int'));
check(parseType('Never'), parseType('int?'));
check(parseType('Never'), parseType('List<int>'));
check(parseType('Never'), parseType('List<int>?'));
check(parseType('Never'), parseType('FutureOr<int>'));
check(parseType('Never'), parseType('FutureOr<int>?'));
check(parseType('Never'), parseFunctionType('void Function()'));
check(parseType('Never'), parseFunctionType('void Function()?'));
{
withTypeParameterScope('T', (scope) {
check(parseType('Never'), scope.parseType('T'));
check(parseType('Never'), scope.parseType('T?'));
});
}
{
withTypeParameterScope('T extends Never', (scope) {
var T = scope.parseType('T');
check(T, parseType('int'));
check(T, parseType('int?'));
});
}
{
withTypeParameterScope('T extends Object?', (scope) {
var T = scope.parseType('T & Never');
check(T, parseType('int'));
check(T, parseType('int?'));
});
}
}
test_bottom_bottom() {
void check(TypeImpl T1, TypeImpl T2) {
_assertBottom(T1);
_assertBottom(T2);
_checkLeastUpperBound(T1, T2, T2);
}
withTypeParameterScope('T extends Never', (scope) {
check(parseType('Never'), scope.parseType('T'));
});
withTypeParameterScope('T extends Object?', (scope) {
check(parseType('Never'), scope.parseType('T & Never'));
});
}
void test_extensionType_implementExtensionType_implicitObjectQuestion() {
// extension type A(Object?) {}
// extension type B(Object?) implements A {}
// extension type C(Object?) implements A {}
buildTestLibrary(
extensionTypes: [
ExtensionTypeSpec('extension type A(Object? it)'),
ExtensionTypeSpec('extension type B(Object? it) implements A'),
ExtensionTypeSpec('extension type C(Object? it) implements A'),
],
);
_checkLeastUpperBound(
parseInterfaceType('B'),
parseInterfaceType('C'),
parseInterfaceType('A'),
);
}
void test_extensionType_noTypeParameters_interfaces() {
// extension type A(int) implements int {}
// extension type B(double) implements double {}
buildTestLibrary(
extensionTypes: [
ExtensionTypeSpec('extension type A(int it) implements int'),
ExtensionTypeSpec('extension type B(double it) implements double'),
],
);
_checkLeastUpperBound(
parseInterfaceType('A'),
parseInterfaceType('B'),
parseType('num'),
);
}
void test_extensionType_noTypeParameters_noInterfaces() {
// extension type A(int) {}
// extension type B(double) {}
buildTestLibrary(
extensionTypes: [
ExtensionTypeSpec('extension type A(int it)'),
ExtensionTypeSpec('extension type B(double it)'),
],
);
_checkLeastUpperBound(
parseInterfaceType('A'),
parseInterfaceType('B'),
parseType('Object?'),
);
}
void test_extensionType_withTypeParameters_objectNone() {
buildTestLibrary(
extensionTypes: [
ExtensionTypeSpec('extension type A<T>(T it) implements Object?'),
ExtensionTypeSpec('extension type B<T>(T it) implements Object?'),
],
);
_checkLeastUpperBound(
parseInterfaceType('A<String>'),
parseInterfaceType('B<num>'),
parseType('Object'),
);
}
void test_extensionType_withTypeParameters_withInterfaces() {
buildTestLibrary(
extensionTypes: [
ExtensionTypeSpec('extension type E<T>(T it) implements Object?'),
ExtensionTypeSpec(
'extension type A<T1 extends String>(T1 it) implements E<T1>, String',
),
ExtensionTypeSpec(
'extension type B<T2 extends int>(T2 it) implements E<T2?>, num',
),
],
);
// A<T1> implements E<T1>, String
// B<T2> implements E<T2?>, num
_checkLeastUpperBound(
parseInterfaceType('A<String>'),
parseInterfaceType('B<num>'),
parseType('Object'),
);
}
test_functionType_interfaceType() {
void check(FunctionTypeImpl T1, TypeImpl T2, TypeImpl expected) {
_checkLeastUpperBound(T1, T2, expected);
}
check(
parseFunctionType('void Function()'),
parseType('int'),
parseType('Object'),
);
}
test_functionType_interfaceType_Function() {
void check(FunctionTypeImpl T1, TypeImpl T2, TypeImpl expected) {
_checkLeastUpperBound(T1, T2, expected);
}
void checkNone(FunctionTypeImpl T1) {
_assertNullabilityNone(T1);
check(T1, parseType('Function'), parseType('Function'));
}
checkNone(parseFunctionType('void Function()'));
checkNone(parseFunctionType('int Function(num?)'));
check(
parseFunctionType('void Function()?'),
parseType('Function'),
parseType('Function?'),
);
}
/// `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() {
_checkLeastUpperBound(
parseType('Future<int>'),
parseType('FutureOr<double>'),
parseType('FutureOr<num>'),
);
_checkLeastUpperBound(
parseType('Future<int>'),
parseType('FutureOr<String>'),
parseType('FutureOr<Object>'),
);
}
/// `UP(FutureOr<T1>, FutureOr<T2>) = FutureOr<T3> where T3 = UP(T1, T2)`
test_futureOr_futureOr() {
_checkLeastUpperBound(
parseType('FutureOr<int>'),
parseType('FutureOr<double>'),
parseType('FutureOr<num>'),
);
_checkLeastUpperBound(
parseType('FutureOr<int>'),
parseType('FutureOr<String>'),
parseType('FutureOr<Object>'),
);
}
/// `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() {
_checkLeastUpperBound(
parseType('FutureOr<int>'),
parseType('double'),
parseType('FutureOr<num>'),
);
_checkLeastUpperBound(
parseType('FutureOr<int>'),
parseType('String'),
parseType('FutureOr<Object>'),
);
}
test_futureOr_typeParameter_recursiveBound() {
withTypeParameterScope('X extends FutureOr<X>, Y extends FutureOr<Y>', (
scope,
) {
_checkLeastUpperBound(
scope.parseType('X'),
scope.parseType('Y'),
parseType('FutureOr<Object?>'),
);
});
}
test_identical() {
void check(TypeImpl type) {
_checkLeastUpperBound(type, type, type);
}
check(parseType('int'));
check(parseType('int?'));
check(parseType('List<int>'));
}
void test_interfaceType_functionType() {
buildTestLibrary(classes: [ClassSpec('class A')]);
_checkLeastUpperBound(
parseInterfaceType('A'),
parseFunctionType('void Function()'),
parseType('Object'),
);
}
test_none_question() {
void check(TypeImpl T1, TypeImpl T2, TypeImpl expected) {
_assertNullabilityNone(T1);
_assertNullabilityQuestion(T2);
_assertNotSpecial(T1);
_assertNotSpecial(T2);
_checkLeastUpperBound(T1, T2, expected);
}
check(parseType('double'), parseType('int?'), parseType('num?'));
check(parseType('num'), parseType('double?'), parseType('num?'));
check(parseType('num'), parseType('int?'), parseType('num?'));
}
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(parseType('Null'), parseType('Object'), parseType('Object?'));
check(parseType('Null'), parseType('int'), parseType('int?'));
check(parseType('Null'), parseType('int?'), parseType('int?'));
check(parseType('Null'), parseType('List<int>'), parseType('List<int>?'));
check(parseType('Null'), parseType('List<int>?'), parseType('List<int>?'));
check(
parseType('Null'),
parseType('FutureOr<int>'),
parseType('FutureOr<int>?'),
);
check(
parseType('Null'),
parseType('FutureOr<int>?'),
parseType('FutureOr<int>?'),
);
check(
parseType('Null'),
parseType('FutureOr<int?>'),
parseType('FutureOr<int?>'),
);
check(
parseType('Null'),
parseType('FutureOr<int?>?'),
parseType('FutureOr<int?>?'),
);
check(
parseType('Null'),
parseFunctionType('int Function()'),
parseFunctionType('int Function()?'),
);
}
test_null_null() {
void check(TypeImpl T1, TypeImpl T2) {
_assertNull(T1);
_assertNull(T2);
_assertNotBottom(T1);
_assertNotBottom(T2);
_checkLeastUpperBound(T1, T2, T2);
}
check(parseType('Null'), parseType('Null'));
}
test_object_any() {
void check(TypeImpl T1, TypeImpl T2, TypeImpl expected) {
_assertObject(T1);
_assertNotObject(T2);
_checkLeastUpperBound(T1, T2, expected);
}
check(parseType('Object'), parseType('int'), parseType('Object'));
check(parseType('Object'), parseType('int?'), parseType('Object?'));
check(
parseType('Object'),
parseType('FutureOr<int?>'),
parseType('Object?'),
);
check(
parseType('FutureOr<Object>'),
parseType('int'),
parseType('FutureOr<Object>'),
);
check(
parseType('FutureOr<Object>'),
parseType('int?'),
parseType('FutureOr<Object>?'),
);
}
test_object_object() {
void check(TypeImpl T1, TypeImpl T2) {
_assertObject(T1);
_assertObject(T2);
_checkLeastUpperBound(T1, T2, T2);
}
check(parseType('FutureOr<Object>'), parseType('Object'));
check(
parseType('FutureOr<FutureOr<Object>>'),
parseType('FutureOr<Object>'),
);
}
test_question_question() {
void check(TypeImpl T1, TypeImpl T2, TypeImpl expected) {
_assertNullabilityQuestion(T1);
_assertNullabilityQuestion(T2);
_assertNotSpecial(T1);
_assertNotSpecial(T2);
_checkLeastUpperBound(T1, T2, expected);
}
check(parseType('double?'), parseType('int?'), parseType('num?'));
check(parseType('num?'), parseType('double?'), parseType('num?'));
check(parseType('num?'), parseType('int?'), parseType('num?'));
}
test_top_any() {
void check(TypeImpl T1, TypeImpl T2) {
_assertTop(T1);
_assertNotTop(T2);
_checkLeastUpperBound(T1, T2, T1);
}
void check2(TypeImpl T1) {
check(T1, parseType('Object'));
check(T1, parseType('int'));
check(T1, parseType('int?'));
check(T1, parseType('List<int>'));
check(T1, parseType('FutureOr<int>'));
check(T1, parseFunctionType('void Function()'));
withTypeParameterScope('T', (scope) {
check(T1, scope.parseType('T'));
check(T1, scope.parseType('T?'));
});
}
check2(parseType('void'));
check2(parseType('dynamic'));
check2(parseType('InvalidType'));
check2(parseType('Object?'));
check2(parseType('FutureOr<void>'));
check2(parseType('FutureOr<void>?'));
}
test_top_top() {
void check(TypeImpl T1, TypeImpl T2) {
_assertTop(T1);
_assertTop(T2);
_checkLeastUpperBound(T1, T2, T1);
}
check(parseType('void'), parseType('dynamic'));
check(parseType('void'), parseType('InvalidType'));
check(parseType('void'), parseType('Object?'));
check(parseType('void'), parseType('FutureOr<void>'));
check(parseType('void'), parseType('FutureOr<dynamic>'));
check(parseType('void'), parseType('FutureOr<InvalidType>'));
check(parseType('void'), parseType('FutureOr<Object?>'));
check(parseType('dynamic'), parseType('Object?'));
check(parseType('dynamic'), parseType('FutureOr<void>'));
check(parseType('dynamic'), parseType('FutureOr<dynamic>'));
check(parseType('dynamic'), parseType('FutureOr<Object?>'));
check(parseType('InvalidType'), parseType('Object?'));
check(parseType('InvalidType'), parseType('FutureOr<void>'));
check(parseType('InvalidType'), parseType('FutureOr<dynamic>'));
check(parseType('InvalidType'), parseType('FutureOr<Object?>'));
check(parseType('Object?'), parseType('FutureOr<void>?'));
check(parseType('Object?'), parseType('FutureOr<dynamic>?'));
check(parseType('Object?'), parseType('FutureOr<InvalidType>?'));
check(parseType('Object?'), parseType('FutureOr<Object>?'));
check(parseType('Object?'), parseType('FutureOr<Object?>?'));
check(parseType('FutureOr<void>'), parseType('Object?'));
check(parseType('FutureOr<dynamic>'), parseType('Object?'));
check(parseType('FutureOr<InvalidType>'), parseType('Object?'));
check(parseType('FutureOr<Object?>'), parseType('Object?'));
check(parseType('FutureOr<void>'), parseType('FutureOr<dynamic>'));
check(parseType('FutureOr<void>'), parseType('FutureOr<InvalidType>'));
check(parseType('FutureOr<void>'), parseType('FutureOr<Object?>'));
check(parseType('FutureOr<dynamic>'), parseType('FutureOr<Object?>'));
check(parseType('FutureOr<InvalidType>'), parseType('FutureOr<Object?>'));
}
test_typeParameter_bound() {
void check(TypeParameterTypeImpl T1, TypeImpl T2, TypeImpl expected) {
_assertNullabilityNone(T1);
_assertNullabilityNone(T2);
_assertNotSpecial(T1);
_assertNotSpecial(T2);
_checkLeastUpperBound(T1, T2, expected);
}
withTypeParameterScope('T extends int', (scope) {
check(
scope.parseTypeParameterType('T'),
parseType('num'),
parseType('num'),
);
});
withTypeParameterScope('T extends int, U extends num', (scope) {
check(
scope.parseTypeParameterType('T'),
scope.parseTypeParameterType('U'),
parseType('num'),
);
});
withTypeParameterScope('T extends int, U extends num?', (scope) {
check(
scope.parseTypeParameterType('T'),
scope.parseTypeParameterType('U'),
parseType('num?'),
);
});
withTypeParameterScope('T extends int?, U extends num', (scope) {
check(
scope.parseTypeParameterType('T'),
scope.parseTypeParameterType('U'),
parseType('num?'),
);
});
withTypeParameterScope('T extends num, U extends T', (scope) {
var T = scope.parseTypeParameterType('T');
check(T, scope.parseTypeParameterType('U'), T);
});
}
void test_typeParameter_fBounded() {
// class A<T> {}
buildTestLibrary(classes: [ClassSpec('class A<T>')]);
withTypeParameterScope('S, U', (scope) {
var S = scope.typeParameter('S');
S.bound = scope.parseType('A<S>');
var U = scope.typeParameter('U');
U.bound = scope.parseType('A<U>');
_checkLeastUpperBound(
scope.parseType('S'),
scope.parseType('U'),
parseType('A<Object?>'),
);
});
}
void test_typeParameter_function_bounded() {
withTypeParameterScope('T extends Function', (scope) {
_checkLeastUpperBound(
scope.parseType('T'),
parseFunctionType('void Function()'),
typeProvider.functionType,
);
});
}
void test_typeParameter_function_noBound() {
withTypeParameterScope('T extends Object?', (scope) {
_checkLeastUpperBound(
scope.parseType('T'),
parseFunctionType('void Function()'),
parseType('Object?'),
);
});
}
void test_typeParameter_greatestClosure_functionBounded() {
withTypeParameterScope('T extends void Function(T)', (scope) {
_checkLeastUpperBound(
scope.parseType('T'),
parseFunctionType('void Function(Null)'),
parseFunctionType('void Function(Never)'),
);
});
}
void test_typeParameter_greatestClosure_functionPromoted() {
withTypeParameterScope('T', (scope) {
_checkLeastUpperBound(
scope.parseType('T & void Function(T)'),
parseFunctionType('void Function(Null)'),
parseFunctionType('void Function(Never)'),
);
});
}
void test_typeParameter_interface_bounded() {
buildTestLibrary(
classes: [
ClassSpec('class A'),
ClassSpec('class B extends A'),
ClassSpec('class C extends A'),
],
);
withTypeParameterScope('T extends B', (scope) {
_checkLeastUpperBound(
scope.parseType('T'),
parseInterfaceType('C'),
parseInterfaceType('A'),
);
});
}
void test_typeParameter_interface_bounded_objectQuestion() {
withTypeParameterScope('T extends Object?', (scope) {
_checkLeastUpperBound(
scope.parseType('T'),
parseType('int'),
parseType('Object?'),
);
});
}
void test_typeParameter_interface_noBound() {
withTypeParameterScope('T', (scope) {
_checkLeastUpperBound(
scope.parseType('T'),
parseType('int'),
parseType('Object?'),
);
});
}
void test_typeParameter_intersection_basic() {
withTypeParameterScope('X extends num?, Y extends X', (scope) {
var X_none = scope.parseType('X');
var Y_none = scope.parseType('Y');
var X_none_promoted = scope.parseType('X & num');
// `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,
parseType('num?'),
parseType('num?'),
);
// `UP(X & num, String) == Object`.
_checkLeastUpperBound(
X_none_promoted,
parseType('String'),
parseType('Object'),
);
});
}
void test_typeParameter_intersection_fbounded() {
// `X`, `class C<X> {}`, `Y extends C<Y>?`, `Y & C<Y>`.
buildTestLibrary(classes: [ClassSpec('class C<X>')]);
withTypeParameterScope('Y', (scope) {
var Y = scope.typeParameter('Y');
Y.bound = scope.parseType('C<Y>?');
// `UP(Y & C<Y>, C<Never>) == C<Object?>`.
_checkLeastUpperBound(
scope.parseType('Y & C<Y>'),
parseInterfaceType('C<Never>'),
parseInterfaceType('C<Object?>'),
);
});
}
void test_typeParameter_intersection_null() {
withTypeParameterScope('X', (scope) {
// UP(X & num?, Null) == num?
_checkLeastUpperBound(
scope.parseType('X & num?'),
parseType('Null'),
parseType('num?'),
);
// UP(X & num, Null) == num?
_checkLeastUpperBound(
scope.parseType('X & num'),
parseType('Null'),
parseType('num?'),
);
});
}
void test_typeParameter_mutuallyRecursiveBounds() {
buildTestLibrary(classes: [ClassSpec('class C<T>')]);
withTypeParameterScope('X extends C<Y>, Y extends C<X>', (scope) {
_checkLeastUpperBound(
scope.parseType('X'),
scope.parseType('Y'),
parseType('Object?'),
);
});
}
void test_typeParameters_contravariant_different() {
// class A<in T>
buildTestLibrary(classes: [ClassSpec('class A<in T>')]);
_checkLeastUpperBound(
parseInterfaceType('A<int>'),
parseInterfaceType('A<num>'),
parseInterfaceType('A<int>'),
);
}
void test_typeParameters_contravariant_same() {
// class A<in T>
buildTestLibrary(classes: [ClassSpec('class A<in T>')]);
_checkLeastUpperBound(
parseInterfaceType('A<num>'),
parseInterfaceType('A<num>'),
parseInterfaceType('A<num>'),
);
}
void test_typeParameters_covariant_different() {
// class A<out T>
buildTestLibrary(classes: [ClassSpec('class A<out T>')]);
_checkLeastUpperBound(
parseInterfaceType('A<int>'),
parseInterfaceType('A<num>'),
parseInterfaceType('A<num>'),
);
}
void test_typeParameters_covariant_same() {
// class A<out T>
buildTestLibrary(classes: [ClassSpec('class A<out T>')]);
_checkLeastUpperBound(
parseInterfaceType('A<num>'),
parseInterfaceType('A<num>'),
parseInterfaceType('A<num>'),
);
}
void test_typeParameters_invariant_object() {
// class A<inout T>
buildTestLibrary(classes: [ClassSpec('class A<inout T>')]);
_checkLeastUpperBound(
parseInterfaceType('A<num>'),
parseInterfaceType('A<int>'),
parseType('Object'),
);
}
void test_typeParameters_invariant_same() {
// class A<inout T>
buildTestLibrary(classes: [ClassSpec('class A<inout T>')]);
_checkLeastUpperBound(
parseInterfaceType('A<num>'),
parseInterfaceType('A<num>'),
parseInterfaceType('A<num>'),
);
}
void test_typeParameters_multi_basic() {
// class A<out T, inout U, in V>
buildTestLibrary(classes: [ClassSpec('class A<out T, inout U, in V>')]);
_checkLeastUpperBound(
parseInterfaceType('A<num, num, num>'),
parseInterfaceType('A<int, num, int>'),
parseInterfaceType('A<num, num, int>'),
);
}
void test_typeParameters_multi_objectInterface() {
// class A<out T, inout U, in V>
buildTestLibrary(classes: [ClassSpec('class A<out T, inout U, in V>')]);
_checkLeastUpperBound(
parseInterfaceType('A<num, String, num>'),
parseInterfaceType('A<int, num, int>'),
parseType('Object'),
);
}
void test_typeParameters_multi_objectType() {
// class A<out T, inout U, in V>
buildTestLibrary(classes: [ClassSpec('class A<out T, inout U, in V>')]);
_checkLeastUpperBound(
parseInterfaceType('A<String, num, num>'),
parseInterfaceType('A<int, num, int>'),
parseInterfaceType('A<Object, num, int>'),
);
}
/// Check least upper bound of the same class with different type parameters.
void test_typeParameters_noVariance_different() {
_checkLeastUpperBound(
parseType('List<int>'),
parseType('List<double>'),
parseType('List<num>'),
);
}
void test_typeParameters_noVariance_same() {
var listOfInt = parseType('List<int>');
_checkLeastUpperBound(listOfInt, listOfInt, listOfInt);
}
}
@reflectiveTest
class _BoundsTestBase extends AbstractTypeSystemTest {
void _assertBottom(TypeImpl type) {
if (!type.isBottom) {
fail('isBottom must be true: $type');
}
}
void _assertNotBottom(TypeImpl type) {
if (type.isBottom) {
fail('isBottom must be false: $type');
}
}
void _assertNotNull(TypeImpl type) {
if (typeSystem.isNull(type)) {
fail('isNull must be false: $type');
}
}
void _assertNotObject(TypeImpl type) {
if (typeSystem.isObject(type)) {
fail('isObject must be false: $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: $type');
}
}
void _assertNull(TypeImpl type) {
if (!typeSystem.isNull(type)) {
fail('isNull must be true: $type');
}
}
void _assertNullability(TypeImpl type, NullabilitySuffix expected) {
if (type.nullabilitySuffix != expected) {
fail('Expected $expected in $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: $type');
}
}
void _assertTop(TypeImpl type) {
if (!typeSystem.isTop(type)) {
fail('isTop must be true: $type');
}
}
void _checkLeastUpperBound(TypeImpl T1, TypeImpl T2, TypeImpl expected) {
var expectedStr = '$expected';
var result = typeSystem.leastUpperBound(T1, T2);
var resultStr = '$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 = '$result';
expect(
result,
expected,
reason:
'''
expected: $expectedStr
actual: $resultStr
''',
);
}
void _checkLeastUpperBound2(String T1, String T2, String expected) {
_checkLeastUpperBound(parseType(T1), parseType(T2), parseType(expected));
}
}