| // Copyright (c) 2015, 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. |
| |
| // Tests related to the [TypeSystem] class. |
| |
| library analyzer.test.generated.type_system_test; |
| |
| import 'package:analyzer/analyzer.dart' |
| show ErrorReporter, ParameterKind, StrongModeCode; |
| import 'package:analyzer/dart/ast/standard_ast_factory.dart' show astFactory; |
| import 'package:analyzer/dart/ast/token.dart' show Keyword; |
| import 'package:analyzer/dart/element/element.dart'; |
| import 'package:analyzer/dart/element/type.dart'; |
| import 'package:analyzer/src/dart/ast/token.dart' show KeywordToken; |
| import 'package:analyzer/src/dart/element/element.dart'; |
| import 'package:analyzer/src/dart/element/type.dart'; |
| import 'package:analyzer/src/generated/engine.dart'; |
| import 'package:analyzer/src/generated/resolver.dart'; |
| import 'package:analyzer/src/generated/source.dart' |
| show NonExistingSource, UriKind; |
| import 'package:analyzer/src/generated/testing/element_factory.dart'; |
| import 'package:analyzer/src/generated/testing/test_type_provider.dart'; |
| import 'package:path/path.dart' show toUri; |
| import 'package:test/test.dart'; |
| import 'package:test_reflective_loader/test_reflective_loader.dart'; |
| |
| import 'analysis_context_factory.dart'; |
| |
| main() { |
| defineReflectiveSuite(() { |
| defineReflectiveTests(ConstraintMatchingTest); |
| defineReflectiveTests(StrongAssignabilityTest); |
| defineReflectiveTests(StrongSubtypingTest); |
| defineReflectiveTests(StrongGenericFunctionInferenceTest); |
| defineReflectiveTests(LeastUpperBoundTest); |
| defineReflectiveTests(StrongLeastUpperBoundTest); |
| defineReflectiveTests(StrongGreatestLowerBoundTest); |
| }); |
| } |
| |
| /** |
| * Base class for testing LUB and GLB in spec and strong mode. |
| */ |
| abstract class BoundTestBase { |
| TypeProvider typeProvider; |
| TypeSystem typeSystem; |
| FunctionType simpleFunctionType; |
| |
| DartType get bottomType => typeProvider.bottomType; |
| InterfaceType get doubleType => typeProvider.doubleType; |
| DartType get dynamicType => typeProvider.dynamicType; |
| InterfaceType get functionType => typeProvider.functionType; |
| InterfaceType get futureOrType => typeProvider.futureOrType; |
| InterfaceType get intType => typeProvider.intType; |
| InterfaceType get iterableType => typeProvider.iterableType; |
| InterfaceType get listType => typeProvider.listType; |
| InterfaceType get numType => typeProvider.numType; |
| InterfaceType get objectType => typeProvider.objectType; |
| InterfaceType get stringType => typeProvider.stringType; |
| StrongTypeSystemImpl get strongTypeSystem => |
| typeSystem as StrongTypeSystemImpl; |
| |
| DartType get voidType => VoidTypeImpl.instance; |
| |
| void setUp() { |
| InternalAnalysisContext context = AnalysisContextFactory.contextWithCore(); |
| typeProvider = context.typeProvider; |
| var simpleFunctionElement = |
| ElementFactory.genericTypeAliasElement('A', returnType: voidType); |
| simpleFunctionType = simpleFunctionElement.type; |
| } |
| |
| void _checkGreatestLowerBound( |
| DartType type1, DartType type2, DartType expectedResult) { |
| DartType glb = strongTypeSystem.getGreatestLowerBound(type1, type2); |
| expect(glb, expectedResult); |
| // Check that the result is a lower bound. |
| expect(typeSystem.isSubtypeOf(glb, type1), true); |
| expect(typeSystem.isSubtypeOf(glb, type2), true); |
| // Check for symmetry while we're at it. Unfortunately, |
| // for function types, the current version of equality |
| // does not respect re-ordering of named parameters, so |
| // for function types we just check if they are mutual subtypes. |
| // https://github.com/dart-lang/sdk/issues/26126 |
| // TODO(leafp): Fix this. |
| glb = strongTypeSystem.getGreatestLowerBound(type2, type1); |
| if (glb is FunctionTypeImpl) { |
| expect(typeSystem.isSubtypeOf(glb, expectedResult), true); |
| expect(typeSystem.isSubtypeOf(expectedResult, glb), true); |
| } else { |
| expect(glb, expectedResult); |
| } |
| } |
| |
| void _checkLeastUpperBound( |
| DartType type1, DartType type2, DartType expectedResult) { |
| DartType lub = typeSystem.getLeastUpperBound(type1, type2); |
| expect(lub, expectedResult); |
| // Check that the result is an upper bound. |
| expect(typeSystem.isSubtypeOf(type1, lub), true); |
| expect(typeSystem.isSubtypeOf(type2, lub), true); |
| |
| // Check for symmetry while we're at it. Unfortunately, |
| // for function types, the current version of equality |
| // does not respect re-ordering of named parameters, so |
| // for function types we just check if they are mutual subtypes. |
| // https://github.com/dart-lang/sdk/issues/26126 |
| // TODO(leafp): Fix this. |
| lub = typeSystem.getLeastUpperBound(type2, type1); |
| if (lub is FunctionTypeImpl) { |
| expect(typeSystem.isSubtypeOf(lub, expectedResult), true); |
| expect(typeSystem.isSubtypeOf(expectedResult, lub), true); |
| } else { |
| expect(lub, expectedResult); |
| } |
| } |
| |
| /** |
| * Creates a function type with the given parameter and return types. |
| * |
| * The return type defaults to `void` if omitted. |
| */ |
| FunctionType _functionType(List<DartType> required, |
| {List<DartType> optional, |
| Map<String, DartType> named, |
| DartType returns}) { |
| if (returns == null) { |
| returns = voidType; |
| } |
| |
| return ElementFactory |
| .functionElement8(required, returns, optional: optional, named: named) |
| .type; |
| } |
| } |
| |
| @reflectiveTest |
| class ConstraintMatchingTest { |
| TypeProvider typeProvider; |
| TypeSystem typeSystem; |
| TypeParameterType T; |
| |
| DartType get dynamicType => DynamicTypeImpl.instance; |
| |
| InterfaceType get functionType => typeProvider.functionType; |
| |
| InterfaceType get intType => typeProvider.intType; |
| |
| InterfaceType get nullType => typeProvider.nullType; |
| |
| InterfaceType get objectType => typeProvider.objectType; |
| |
| InterfaceType get stringType => typeProvider.stringType; |
| |
| DartType get voidType => VoidTypeImpl.instance; |
| |
| DartType fn(DartType paramType, DartType returnType) => |
| new FunctionElementImpl.synthetic([ |
| new ParameterElementImpl.synthetic( |
| 'value', paramType, ParameterKind.REQUIRED) |
| ], returnType) |
| .type; |
| |
| DartType future(DartType T) => typeProvider.futureType.instantiate([T]); |
| |
| DartType futureOr(DartType T) => typeProvider.futureOrType.instantiate([T]); |
| |
| DartType iterable(DartType T) => typeProvider.iterableType.instantiate([T]); |
| |
| DartType list(DartType T) => typeProvider.listType.instantiate([T]); |
| |
| void setUp() { |
| typeProvider = AnalysisContextFactory.contextWithCore().typeProvider; |
| typeSystem = new StrongTypeSystemImpl(typeProvider); |
| T = _newTypeParameter('T'); |
| } |
| |
| void test_function_coreFunction() { |
| _checkOrdinarySubtypeMatch(fn(intType, stringType), functionType, [T], |
| covariant: true); |
| } |
| |
| void test_function_parameter_types() { |
| _checkIsSubtypeMatchOf( |
| fn(T, intType), fn(stringType, intType), [T], ['String <: T'], |
| covariant: true); |
| } |
| |
| void test_function_return_types() { |
| _checkIsSubtypeMatchOf( |
| fn(intType, T), fn(intType, stringType), [T], ['T <: String'], |
| covariant: true); |
| } |
| |
| void test_futureOr_futureOr() { |
| _checkIsSubtypeMatchOf( |
| futureOr(T), futureOr(stringType), [T], ['T <: String'], |
| covariant: true); |
| } |
| |
| void test_futureOr_x_fail_future_branch() { |
| // FutureOr<List<T>> <: List<String> can't be satisfied because |
| // Future<List<T>> <: List<String> can't be satisfied |
| _checkIsNotSubtypeMatchOf(futureOr(list(T)), list(stringType), [T], |
| covariant: true); |
| } |
| |
| void test_futureOr_x_fail_nonFuture_branch() { |
| // FutureOr<List<T>> <: Future<List<String>> can't be satisfied because |
| // List<T> <: Future<List<String>> can't be satisfied |
| _checkIsNotSubtypeMatchOf(futureOr(list(T)), future(list(stringType)), [T], |
| covariant: true); |
| } |
| |
| void test_futureOr_x_success() { |
| // FutureOr<T> <: Future<T> can be satisfied by T=Null. At this point in |
| // the type inference algorithm all we figure out is that T must be a |
| // subtype of both String and Future<String>. |
| _checkIsSubtypeMatchOf(futureOr(T), future(stringType), [T], |
| ['T <: String', 'T <: Future<String>'], |
| covariant: true); |
| } |
| |
| void test_lhs_null() { |
| // Null <: T is trivially satisfied by the constraint Null <: T. |
| _checkIsSubtypeMatchOf(nullType, T, [T], ['Null <: T'], covariant: false); |
| // For any other type X, Null <: X is satisfied without the need for any |
| // constraints. |
| _checkOrdinarySubtypeMatch(nullType, list(T), [T], covariant: false); |
| _checkOrdinarySubtypeMatch(nullType, stringType, [T], covariant: false); |
| _checkOrdinarySubtypeMatch(nullType, voidType, [T], covariant: false); |
| _checkOrdinarySubtypeMatch(nullType, dynamicType, [T], covariant: false); |
| _checkOrdinarySubtypeMatch(nullType, objectType, [T], covariant: false); |
| _checkOrdinarySubtypeMatch(nullType, nullType, [T], covariant: false); |
| _checkOrdinarySubtypeMatch(nullType, fn(intType, stringType), [T], |
| covariant: false); |
| } |
| |
| void test_param_on_lhs_contravariant_direct() { |
| // When doing a contravariant match, the type parameters we're trying to |
| // find types for are on the right hand side. Is a type parameter also |
| // appears on the left hand side, there is a condition in which the |
| // constraint can be satisfied without consulting the bound of the LHS type |
| // parameter: the condition where both parameters appear at corresponding |
| // locations in the type tree. |
| // |
| // In other words, List<S> <: List<T> is satisfied provided that |
| // S <: T. |
| var S = _newTypeParameter('S'); |
| _checkIsSubtypeMatchOf(list(S), list(T), [T], ['S <: T'], covariant: false); |
| } |
| |
| void test_param_on_lhs_contravariant_via_bound() { |
| // When doing a contravariant match, the type parameters we're trying to |
| // find types for are on the right hand side. Is a type parameter also |
| // appears on the left hand side, we may have to constrain the RHS type |
| // parameter using the bounds of the LHS type parameter. |
| // |
| // In other words, S <: List<T> is satisfied provided that |
| // bound(S) <: List<T>. |
| var S = _newTypeParameter('S', list(stringType)); |
| _checkIsSubtypeMatchOf(S, list(T), [T], ['String <: T'], covariant: false); |
| } |
| |
| void test_param_on_lhs_covariant() { |
| // When doing a covariant match, the type parameters we're trying to find |
| // types for are on the left hand side. |
| _checkIsSubtypeMatchOf(T, stringType, [T], ['T <: String'], |
| covariant: true); |
| } |
| |
| void test_param_on_rhs_contravariant() { |
| // When doing a contravariant match, the type parameters we're trying to |
| // find types for are on the right hand side. |
| _checkIsSubtypeMatchOf(stringType, T, [T], ['String <: T'], |
| covariant: false); |
| } |
| |
| void test_param_on_rhs_covariant_match() { |
| // When doing a covariant match, the type parameters we're trying to find |
| // types for are on the left hand side. If a type parameter appears on the |
| // right hand side, there is a condition in which the constraint can be |
| // satisfied: where both parameters appear at corresponding locations in the |
| // type tree. |
| // |
| // In other words, T <: S can be satisfied trivially by the constraint |
| // T <: S. |
| var S = _newTypeParameter('S'); |
| _checkIsSubtypeMatchOf(T, S, [T], ['T <: S'], covariant: true); |
| } |
| |
| void test_param_on_rhs_covariant_no_match() { |
| // When doing a covariant match, the type parameters we're trying to find |
| // types for are on the left hand side. If a type parameter appears on the |
| // right hand side, it's probable that the constraint can't be satisfied, |
| // because there is no possible type for the LHS (other than bottom) |
| // that's guaranteed to satisfy the relation for all possible assignments of |
| // the RHS type parameter. |
| // |
| // In other words, no match can be found for List<T> <: S because regardless |
| // of T, we can't guarantee that List<T> <: S for all S. |
| var S = _newTypeParameter('S'); |
| _checkIsNotSubtypeMatchOf(list(T), S, [T], covariant: true); |
| } |
| |
| void test_related_interface_types_failure() { |
| _checkIsNotSubtypeMatchOf(iterable(T), list(stringType), [T], |
| covariant: true); |
| } |
| |
| void test_related_interface_types_success() { |
| _checkIsSubtypeMatchOf(list(T), iterable(stringType), [T], ['T <: String'], |
| covariant: true); |
| } |
| |
| void test_rhs_dynamic() { |
| // T <: dynamic is trivially satisfied by the constraint T <: dynamic. |
| _checkIsSubtypeMatchOf(T, dynamicType, [T], ['T <: dynamic'], |
| covariant: true); |
| // For any other type X, X <: dynamic is satisfied without the need for any |
| // constraints. |
| _checkOrdinarySubtypeMatch(list(T), dynamicType, [T], covariant: true); |
| _checkOrdinarySubtypeMatch(stringType, dynamicType, [T], covariant: true); |
| _checkOrdinarySubtypeMatch(voidType, dynamicType, [T], covariant: true); |
| _checkOrdinarySubtypeMatch(dynamicType, dynamicType, [T], covariant: true); |
| _checkOrdinarySubtypeMatch(objectType, dynamicType, [T], covariant: true); |
| _checkOrdinarySubtypeMatch(nullType, dynamicType, [T], covariant: true); |
| _checkOrdinarySubtypeMatch(fn(intType, stringType), dynamicType, [T], |
| covariant: true); |
| } |
| |
| void test_rhs_object() { |
| // T <: Object is trivially satisfied by the constraint T <: Object. |
| _checkIsSubtypeMatchOf(T, objectType, [T], ['T <: Object'], |
| covariant: true); |
| // For any other type X, X <: Object is satisfied without the need for any |
| // constraints. |
| _checkOrdinarySubtypeMatch(list(T), objectType, [T], covariant: true); |
| _checkOrdinarySubtypeMatch(stringType, objectType, [T], covariant: true); |
| _checkOrdinarySubtypeMatch(voidType, objectType, [T], covariant: true); |
| _checkOrdinarySubtypeMatch(dynamicType, objectType, [T], covariant: true); |
| _checkOrdinarySubtypeMatch(objectType, objectType, [T], covariant: true); |
| _checkOrdinarySubtypeMatch(nullType, objectType, [T], covariant: true); |
| _checkOrdinarySubtypeMatch(fn(intType, stringType), objectType, [T], |
| covariant: true); |
| } |
| |
| void test_rhs_void() { |
| // T <: void is trivially satisfied by the constraint T <: void. |
| _checkIsSubtypeMatchOf(T, voidType, [T], ['T <: void'], covariant: true); |
| // For any other type X, X <: void is satisfied without the need for any |
| // constraints. |
| _checkOrdinarySubtypeMatch(list(T), voidType, [T], covariant: true); |
| _checkOrdinarySubtypeMatch(stringType, voidType, [T], covariant: true); |
| _checkOrdinarySubtypeMatch(voidType, voidType, [T], covariant: true); |
| _checkOrdinarySubtypeMatch(dynamicType, voidType, [T], covariant: true); |
| _checkOrdinarySubtypeMatch(objectType, voidType, [T], covariant: true); |
| _checkOrdinarySubtypeMatch(nullType, voidType, [T], covariant: true); |
| _checkOrdinarySubtypeMatch(fn(intType, stringType), voidType, [T], |
| covariant: true); |
| } |
| |
| void test_same_interface_types() { |
| _checkIsSubtypeMatchOf(list(T), list(stringType), [T], ['T <: String'], |
| covariant: true); |
| } |
| |
| void test_x_futureOr_fail_both_branches() { |
| // List<T> <: FutureOr<String> can't be satisfied because neither |
| // List<T> <: Future<String> nor List<T> <: int can be satisfied |
| _checkIsNotSubtypeMatchOf(list(T), futureOr(stringType), [T], |
| covariant: true); |
| } |
| |
| void test_x_futureOr_pass_both_branches_constraints_from_both_branches() { |
| // Future<String> <: FutureOr<T> can be satisfied because both |
| // Future<String> <: Future<T> and Future<String> <: T can be satisfied. |
| // Trying to match Future<String> <: Future<T> generates the constraint |
| // String <: T, whereas trying to match Future<String> <: T generates the |
| // constraint Future<String> <: T. We keep the constraint based on trying |
| // to match Future<String> <: Future<T>, so String <: T. |
| _checkIsSubtypeMatchOf( |
| future(stringType), futureOr(T), [T], ['String <: T'], |
| covariant: false); |
| } |
| |
| void test_x_futureOr_pass_both_branches_constraints_from_future_branch() { |
| // Future<T> <: FutureOr<Object> can be satisfied because both |
| // Future<T> <: Future<Object> and Future<T> <: Object can be satisfied. |
| // Trying to match Future<T> <: Future<Object> generates the constraint |
| // T <: Object, whereas trying to match Future<T> <: Object generates no |
| // constraints, so we keep the constraint T <: Object. |
| _checkIsSubtypeMatchOf( |
| future(T), futureOr(objectType), [T], ['T <: Object'], |
| covariant: true); |
| } |
| |
| void test_x_futureOr_pass_both_branches_constraints_from_nonFuture_branch() { |
| // Null <: FutureOr<T> can be satisfied because both |
| // Null <: Future<T> and Null <: T can be satisfied. |
| // Trying to match Null <: FutureOr<T> generates no constraints, whereas |
| // trying to match Null <: T generates the constraint Null <: T, |
| // so we keep the constraint Null <: T. |
| _checkIsSubtypeMatchOf(nullType, futureOr(T), [T], ['Null <: T'], |
| covariant: false); |
| } |
| |
| void test_x_futureOr_pass_both_branches_no_constraints() { |
| // Future<String> <: FutureOr<Object> is satisfied because both |
| // Future<String> <: Future<Object> and Future<String> <: Object. |
| // No constraints are recorded. |
| _checkIsSubtypeMatchOf(future(stringType), futureOr(objectType), [T], [], |
| covariant: true); |
| } |
| |
| void test_x_futureOr_pass_future_branch() { |
| // Future<T> <: FutureOr<String> can be satisfied because |
| // Future<T> <: Future<String> can be satisfied |
| _checkIsSubtypeMatchOf( |
| future(T), futureOr(stringType), [T], ['T <: String'], |
| covariant: true); |
| } |
| |
| void test_x_futureOr_pass_nonFuture_branch() { |
| // List<T> <: FutureOr<List<String>> can be satisfied because |
| // List<T> <: List<String> can be satisfied |
| _checkIsSubtypeMatchOf( |
| list(T), futureOr(list(stringType)), [T], ['T <: String'], |
| covariant: true); |
| } |
| |
| void _checkIsNotSubtypeMatchOf( |
| DartType t1, DartType t2, Iterable<TypeParameterType> typeFormals, |
| {bool covariant}) { |
| var inferrer = new GenericInferrer( |
| typeProvider, typeSystem, typeFormals.map((t) => t.element)); |
| var success = |
| inferrer.tryMatchSubtypeOf(t1, t2, null, covariant: covariant); |
| expect(success, isFalse); |
| inferrer.constraints.forEach((typeParameter, constraintsForTypeParameter) { |
| expect(constraintsForTypeParameter, isEmpty); |
| }); |
| } |
| |
| void _checkIsSubtypeMatchOf( |
| DartType t1, |
| DartType t2, |
| Iterable<TypeParameterType> typeFormals, |
| Iterable<String> expectedConstraints, |
| {bool covariant}) { |
| var inferrer = new GenericInferrer( |
| typeProvider, typeSystem, typeFormals.map((t) => t.element)); |
| var success = |
| inferrer.tryMatchSubtypeOf(t1, t2, null, covariant: covariant); |
| expect(success, isTrue); |
| var formattedConstraints = <String>[]; |
| inferrer.constraints.forEach((typeParameter, constraintsForTypeParameter) { |
| for (var constraint in constraintsForTypeParameter) { |
| formattedConstraints.add(constraint.format(typeParameter.toString())); |
| } |
| }); |
| expect(formattedConstraints, unorderedEquals(expectedConstraints)); |
| } |
| |
| void _checkOrdinarySubtypeMatch( |
| DartType t1, DartType t2, Iterable<TypeParameterType> typeFormals, |
| {bool covariant}) { |
| bool expectSuccess = typeSystem.isSubtypeOf(t1, t2); |
| if (expectSuccess) { |
| _checkIsSubtypeMatchOf(t1, t2, typeFormals, [], covariant: covariant); |
| } else { |
| _checkIsNotSubtypeMatchOf(t1, t2, typeFormals); |
| } |
| } |
| |
| TypeParameterType _newTypeParameter(String name, [DartType bound]) { |
| var element = new TypeParameterElementImpl(name, 0); |
| if (bound != null) { |
| element.bound = bound; |
| } |
| return new TypeParameterTypeImpl(element); |
| } |
| } |
| |
| /** |
| * Tests LUB in spec mode. |
| * |
| * Tests defined in this class are ones whose behavior is spec mode-specific. |
| * In particular, function parameters are compared using LUB in spec mode, but |
| * GLB in strong mode. |
| */ |
| @reflectiveTest |
| class LeastUpperBoundTest extends LeastUpperBoundTestBase { |
| void setUp() { |
| super.setUp(); |
| typeSystem = new TypeSystemImpl(typeProvider); |
| } |
| |
| void test_functionsLubNamedParams() { |
| FunctionType type1 = |
| _functionType([], named: {'a': stringType, 'b': intType}); |
| FunctionType type2 = _functionType([], named: {'a': intType, 'b': numType}); |
| FunctionType expected = |
| _functionType([], named: {'a': objectType, 'b': numType}); |
| _checkLeastUpperBound(type1, type2, expected); |
| } |
| |
| void test_functionsLubPositionalParams() { |
| FunctionType type1 = _functionType([], optional: [stringType, intType]); |
| FunctionType type2 = _functionType([], optional: [intType, numType]); |
| FunctionType expected = _functionType([], optional: [objectType, numType]); |
| _checkLeastUpperBound(type1, type2, expected); |
| } |
| |
| void test_functionsLubRequiredParams() { |
| FunctionType type1 = _functionType([stringType, intType, intType]); |
| FunctionType type2 = _functionType([intType, doubleType, numType]); |
| FunctionType expected = _functionType([objectType, numType, numType]); |
| _checkLeastUpperBound(type1, type2, expected); |
| } |
| |
| void test_nestedNestedFunctionsLubInnermostParamTypes() { |
| FunctionType type1 = _functionType([ |
| _functionType([ |
| _functionType([stringType, intType, intType]) |
| ]) |
| ]); |
| FunctionType type2 = _functionType([ |
| _functionType([ |
| _functionType([intType, doubleType, numType]) |
| ]) |
| ]); |
| FunctionType expected = _functionType([ |
| _functionType([ |
| _functionType([objectType, numType, numType]) |
| ]) |
| ]); |
| _checkLeastUpperBound(type1, type2, expected); |
| } |
| |
| void test_typeParam_class_implements_Function() { |
| DartType typeA = ElementFactory.classElement('A', functionType).type; |
| TypeParameterElementImpl typeParamElement = |
| ElementFactory.typeParameterElement('T'); |
| typeParamElement.bound = typeA; |
| DartType typeParam = typeParamElement.type; |
| _checkLeastUpperBound(typeParam, simpleFunctionType, functionType); |
| } |
| |
| /// Check least upper bound of the same class with different type parameters. |
| void test_typeParameters_different() { |
| // class List<int> |
| // class List<double> |
| InterfaceType listOfIntType = listType.instantiate(<DartType>[intType]); |
| InterfaceType listOfDoubleType = |
| listType.instantiate(<DartType>[doubleType]); |
| _checkLeastUpperBound(listOfIntType, listOfDoubleType, objectType); |
| } |
| |
| /// Check least upper bound of two related classes with different |
| /// type parameters. |
| void test_typeParametersAndClass_different() { |
| // class List<int> |
| // class Iterable<double> |
| InterfaceType listOfIntType = listType.instantiate(<DartType>[intType]); |
| InterfaceType iterableOfDoubleType = |
| iterableType.instantiate(<DartType>[doubleType]); |
| _checkLeastUpperBound(listOfIntType, iterableOfDoubleType, objectType); |
| } |
| } |
| |
| /** |
| * Base class for testing LUB in spec and strong mode. |
| * Defines helper functions and tests. Tests here are ones whose behavior is |
| * the same in strong and spec mode. |
| */ |
| abstract class LeastUpperBoundTestBase extends BoundTestBase { |
| void test_bottom_function() { |
| _checkLeastUpperBound(bottomType, simpleFunctionType, simpleFunctionType); |
| } |
| |
| void test_bottom_interface() { |
| DartType interfaceType = ElementFactory.classElement2('A', []).type; |
| _checkLeastUpperBound(bottomType, interfaceType, interfaceType); |
| } |
| |
| void test_bottom_typeParam() { |
| DartType typeParam = ElementFactory.typeParameterElement('T').type; |
| _checkLeastUpperBound(bottomType, typeParam, typeParam); |
| } |
| |
| void test_directInterfaceCase() { |
| // class A |
| // class B implements A |
| // class C implements B |
| ClassElementImpl classA = ElementFactory.classElement2("A"); |
| ClassElementImpl classB = ElementFactory.classElement2("B"); |
| ClassElementImpl classC = ElementFactory.classElement2("C"); |
| InterfaceType typeA = classA.type; |
| InterfaceType typeB = classB.type; |
| InterfaceType typeC = classC.type; |
| classB.interfaces = <InterfaceType>[typeA]; |
| classC.interfaces = <InterfaceType>[typeB]; |
| _checkLeastUpperBound(typeB, typeC, typeB); |
| } |
| |
| void test_directSubclassCase() { |
| // class A |
| // class B extends A |
| // class C extends B |
| ClassElementImpl classA = ElementFactory.classElement2("A"); |
| ClassElementImpl classB = ElementFactory.classElement("B", classA.type); |
| ClassElementImpl classC = ElementFactory.classElement("C", classB.type); |
| InterfaceType typeB = classB.type; |
| InterfaceType typeC = classC.type; |
| _checkLeastUpperBound(typeB, typeC, typeB); |
| } |
| |
| void test_dynamic_bottom() { |
| _checkLeastUpperBound(dynamicType, bottomType, dynamicType); |
| } |
| |
| void test_dynamic_function() { |
| _checkLeastUpperBound(dynamicType, simpleFunctionType, dynamicType); |
| } |
| |
| void test_dynamic_interface() { |
| DartType interfaceType = ElementFactory.classElement2('A', []).type; |
| _checkLeastUpperBound(dynamicType, interfaceType, dynamicType); |
| } |
| |
| void test_dynamic_typeParam() { |
| DartType typeParam = ElementFactory.typeParameterElement('T').type; |
| _checkLeastUpperBound(dynamicType, typeParam, dynamicType); |
| } |
| |
| void test_dynamic_void() { |
| // Note: _checkLeastUpperBound tests `LUB(x, y)` as well as `LUB(y, x)` |
| _checkLeastUpperBound(dynamicType, voidType, voidType); |
| } |
| |
| void test_functionsDifferentRequiredArity() { |
| FunctionType type1 = _functionType([intType, intType]); |
| FunctionType type2 = _functionType([intType, intType, intType]); |
| _checkLeastUpperBound(type1, type2, functionType); |
| } |
| |
| void test_functionsIgnoreExtraNamedParams() { |
| FunctionType type1 = _functionType([], named: {'a': intType, 'b': intType}); |
| FunctionType type2 = _functionType([], named: {'a': intType, 'c': intType}); |
| FunctionType expected = _functionType([], named: {'a': intType}); |
| _checkLeastUpperBound(type1, type2, expected); |
| } |
| |
| void test_functionsIgnoreExtraPositionalParams() { |
| FunctionType type1 = |
| _functionType([], optional: [intType, intType, stringType]); |
| FunctionType type2 = _functionType([], optional: [intType]); |
| FunctionType expected = _functionType([], optional: [intType]); |
| _checkLeastUpperBound(type1, type2, expected); |
| } |
| |
| void test_functionsLubReturnType() { |
| FunctionType type1 = _functionType([], returns: intType); |
| FunctionType type2 = _functionType([], returns: doubleType); |
| FunctionType expected = _functionType([], returns: numType); |
| _checkLeastUpperBound(type1, type2, expected); |
| } |
| |
| void test_functionsSameType() { |
| FunctionType type1 = _functionType([stringType, intType, numType], |
| optional: [doubleType], named: {'n': numType}, returns: intType); |
| FunctionType type2 = _functionType([stringType, intType, numType], |
| optional: [doubleType], named: {'n': numType}, returns: intType); |
| FunctionType expected = _functionType([stringType, intType, numType], |
| optional: [doubleType], named: {'n': numType}, returns: intType); |
| _checkLeastUpperBound(type1, type2, expected); |
| } |
| |
| void test_interface_function() { |
| DartType interfaceType = ElementFactory.classElement2('A', []).type; |
| _checkLeastUpperBound(interfaceType, simpleFunctionType, objectType); |
| } |
| |
| void test_mixinCase() { |
| // class A |
| // class B extends A |
| // class C extends A |
| // class D extends B with M, N, O, P |
| ClassElement classA = ElementFactory.classElement2("A"); |
| ClassElement classB = ElementFactory.classElement("B", classA.type); |
| ClassElement classC = ElementFactory.classElement("C", classA.type); |
| ClassElementImpl classD = ElementFactory.classElement("D", classB.type); |
| InterfaceType typeA = classA.type; |
| InterfaceType typeC = classC.type; |
| InterfaceType typeD = classD.type; |
| classD.mixins = <InterfaceType>[ |
| ElementFactory.classElement2("M").type, |
| ElementFactory.classElement2("N").type, |
| ElementFactory.classElement2("O").type, |
| ElementFactory.classElement2("P").type |
| ]; |
| _checkLeastUpperBound(typeD, typeC, typeA); |
| } |
| |
| void test_nestedFunctionsLubInnerParamTypes() { |
| FunctionType type1 = _functionType([ |
| _functionType([stringType, intType, intType]) |
| ]); |
| FunctionType type2 = _functionType([ |
| _functionType([intType, doubleType, numType]) |
| ]); |
| FunctionType expected = _functionType([ |
| _functionType([objectType, numType, numType]) |
| ]); |
| _checkLeastUpperBound(type1, type2, expected); |
| } |
| |
| void test_object() { |
| ClassElementImpl classA = ElementFactory.classElement2("A"); |
| ClassElementImpl classB = ElementFactory.classElement2("B"); |
| InterfaceType typeA = classA.type; |
| InterfaceType typeB = classB.type; |
| DartType typeObject = typeA.element.supertype; |
| // assert that object does not have a super type |
| expect((typeObject.element as ClassElement).supertype, isNull); |
| // assert that both A and B have the same super type of Object |
| expect(typeB.element.supertype, typeObject); |
| // finally, assert that the only least upper bound of A and B is Object |
| _checkLeastUpperBound(typeA, typeB, typeObject); |
| } |
| |
| void test_self() { |
| DartType typeParam = ElementFactory.typeParameterElement('T').type; |
| DartType interfaceType = ElementFactory.classElement2('A', []).type; |
| |
| List<DartType> types = [ |
| dynamicType, |
| voidType, |
| bottomType, |
| typeParam, |
| interfaceType, |
| simpleFunctionType |
| ]; |
| |
| for (DartType type in types) { |
| _checkLeastUpperBound(type, type, type); |
| } |
| } |
| |
| void test_sharedSuperclass1() { |
| ClassElementImpl classA = ElementFactory.classElement2("A"); |
| ClassElementImpl classB = ElementFactory.classElement("B", classA.type); |
| ClassElementImpl classC = ElementFactory.classElement("C", classA.type); |
| InterfaceType typeA = classA.type; |
| InterfaceType typeB = classB.type; |
| InterfaceType typeC = classC.type; |
| _checkLeastUpperBound(typeB, typeC, typeA); |
| } |
| |
| void test_sharedSuperclass2() { |
| ClassElementImpl classA = ElementFactory.classElement2("A"); |
| ClassElementImpl classB = ElementFactory.classElement("B", classA.type); |
| ClassElementImpl classC = ElementFactory.classElement("C", classA.type); |
| ClassElementImpl classD = ElementFactory.classElement("D", classC.type); |
| InterfaceType typeA = classA.type; |
| InterfaceType typeB = classB.type; |
| InterfaceType typeD = classD.type; |
| _checkLeastUpperBound(typeB, typeD, typeA); |
| } |
| |
| void test_sharedSuperclass3() { |
| ClassElementImpl classA = ElementFactory.classElement2("A"); |
| ClassElementImpl classB = ElementFactory.classElement("B", classA.type); |
| ClassElementImpl classC = ElementFactory.classElement("C", classB.type); |
| ClassElementImpl classD = ElementFactory.classElement("D", classB.type); |
| InterfaceType typeB = classB.type; |
| InterfaceType typeC = classC.type; |
| InterfaceType typeD = classD.type; |
| _checkLeastUpperBound(typeC, typeD, typeB); |
| } |
| |
| void test_sharedSuperclass4() { |
| ClassElement classA = ElementFactory.classElement2("A"); |
| ClassElement classA2 = ElementFactory.classElement2("A2"); |
| ClassElement classA3 = ElementFactory.classElement2("A3"); |
| ClassElementImpl classB = ElementFactory.classElement("B", classA.type); |
| ClassElementImpl classC = ElementFactory.classElement("C", classA.type); |
| InterfaceType typeA = classA.type; |
| InterfaceType typeA2 = classA2.type; |
| InterfaceType typeA3 = classA3.type; |
| InterfaceType typeB = classB.type; |
| InterfaceType typeC = classC.type; |
| classB.interfaces = <InterfaceType>[typeA2]; |
| classC.interfaces = <InterfaceType>[typeA3]; |
| _checkLeastUpperBound(typeB, typeC, typeA); |
| } |
| |
| void test_sharedSuperinterface1() { |
| ClassElementImpl classA = ElementFactory.classElement2("A"); |
| ClassElementImpl classB = ElementFactory.classElement2("B"); |
| ClassElementImpl classC = ElementFactory.classElement2("C"); |
| InterfaceType typeA = classA.type; |
| InterfaceType typeB = classB.type; |
| InterfaceType typeC = classC.type; |
| classB.interfaces = <InterfaceType>[typeA]; |
| classC.interfaces = <InterfaceType>[typeA]; |
| _checkLeastUpperBound(typeB, typeC, typeA); |
| } |
| |
| void test_sharedSuperinterface2() { |
| ClassElementImpl classA = ElementFactory.classElement2("A"); |
| ClassElementImpl classB = ElementFactory.classElement2("B"); |
| ClassElementImpl classC = ElementFactory.classElement2("C"); |
| ClassElementImpl classD = ElementFactory.classElement2("D"); |
| InterfaceType typeA = classA.type; |
| InterfaceType typeB = classB.type; |
| InterfaceType typeC = classC.type; |
| InterfaceType typeD = classD.type; |
| classB.interfaces = <InterfaceType>[typeA]; |
| classC.interfaces = <InterfaceType>[typeA]; |
| classD.interfaces = <InterfaceType>[typeC]; |
| _checkLeastUpperBound(typeB, typeD, typeA); |
| } |
| |
| void test_sharedSuperinterface3() { |
| ClassElementImpl classA = ElementFactory.classElement2("A"); |
| ClassElementImpl classB = ElementFactory.classElement2("B"); |
| ClassElementImpl classC = ElementFactory.classElement2("C"); |
| ClassElementImpl classD = ElementFactory.classElement2("D"); |
| InterfaceType typeA = classA.type; |
| InterfaceType typeB = classB.type; |
| InterfaceType typeC = classC.type; |
| InterfaceType typeD = classD.type; |
| classB.interfaces = <InterfaceType>[typeA]; |
| classC.interfaces = <InterfaceType>[typeB]; |
| classD.interfaces = <InterfaceType>[typeB]; |
| _checkLeastUpperBound(typeC, typeD, typeB); |
| } |
| |
| void test_sharedSuperinterface4() { |
| ClassElement classA = ElementFactory.classElement2("A"); |
| ClassElement classA2 = ElementFactory.classElement2("A2"); |
| ClassElement classA3 = ElementFactory.classElement2("A3"); |
| ClassElementImpl classB = ElementFactory.classElement2("B"); |
| ClassElementImpl classC = ElementFactory.classElement2("C"); |
| InterfaceType typeA = classA.type; |
| InterfaceType typeA2 = classA2.type; |
| InterfaceType typeA3 = classA3.type; |
| InterfaceType typeB = classB.type; |
| InterfaceType typeC = classC.type; |
| classB.interfaces = <InterfaceType>[typeA, typeA2]; |
| classC.interfaces = <InterfaceType>[typeA, typeA3]; |
| _checkLeastUpperBound(typeB, typeC, typeA); |
| } |
| |
| void test_twoComparables() { |
| _checkLeastUpperBound(stringType, numType, objectType); |
| } |
| |
| void test_typeParam_function_bounded() { |
| TypeParameterElementImpl typeParamElement = |
| ElementFactory.typeParameterElement('T'); |
| typeParamElement.bound = functionType; |
| DartType typeParam = typeParamElement.type; |
| _checkLeastUpperBound(typeParam, simpleFunctionType, functionType); |
| } |
| |
| void test_typeParam_function_noBound() { |
| DartType typeParam = ElementFactory.typeParameterElement('T').type; |
| _checkLeastUpperBound(typeParam, simpleFunctionType, objectType); |
| } |
| |
| void test_typeParam_interface_bounded() { |
| DartType typeA = ElementFactory.classElement2('A', []).type; |
| DartType typeB = ElementFactory.classElement('B', typeA).type; |
| DartType typeC = ElementFactory.classElement('C', typeA).type; |
| TypeParameterElementImpl typeParamElement = |
| ElementFactory.typeParameterElement('T'); |
| typeParamElement.bound = typeB; |
| DartType typeParam = typeParamElement.type; |
| _checkLeastUpperBound(typeParam, typeC, typeA); |
| } |
| |
| void test_typeParam_interface_noBound() { |
| DartType typeParam = ElementFactory.typeParameterElement('T').type; |
| DartType interfaceType = ElementFactory.classElement2('A', []).type; |
| _checkLeastUpperBound(typeParam, interfaceType, objectType); |
| } |
| |
| void test_typeParameters_same() { |
| // List<int> |
| // List<int> |
| InterfaceType listOfIntType = listType.instantiate(<DartType>[intType]); |
| _checkLeastUpperBound(listOfIntType, listOfIntType, listOfIntType); |
| } |
| |
| void test_void() { |
| List<DartType> types = [ |
| bottomType, |
| simpleFunctionType, |
| ElementFactory.classElement2('A', []).type, |
| ElementFactory.typeParameterElement('T').type |
| ]; |
| for (DartType type in types) { |
| _checkLeastUpperBound( |
| _functionType([], returns: voidType), |
| _functionType([], returns: type), |
| _functionType([], returns: voidType)); |
| } |
| } |
| } |
| |
| @reflectiveTest |
| class StrongAssignabilityTest { |
| TypeProvider typeProvider; |
| TypeSystem typeSystem; |
| |
| DartType get bottomType => typeProvider.bottomType; |
| InterfaceType get doubleType => typeProvider.doubleType; |
| DartType get dynamicType => typeProvider.dynamicType; |
| InterfaceType get functionType => typeProvider.functionType; |
| InterfaceType get intType => typeProvider.intType; |
| InterfaceType get listType => typeProvider.listType; |
| InterfaceType get numType => typeProvider.numType; |
| InterfaceType get objectType => typeProvider.objectType; |
| InterfaceType get stringType => typeProvider.stringType; |
| DartType get voidType => VoidTypeImpl.instance; |
| |
| void setUp() { |
| typeProvider = new TestTypeProvider(); |
| typeSystem = new StrongTypeSystemImpl(typeProvider); |
| } |
| |
| void test_isAssignableTo_bottom_isBottom() { |
| DartType interfaceType = ElementFactory.classElement2('A', []).type; |
| List<DartType> interassignable = <DartType>[ |
| dynamicType, |
| objectType, |
| intType, |
| doubleType, |
| numType, |
| stringType, |
| interfaceType, |
| bottomType |
| ]; |
| |
| _checkGroups(bottomType, interassignable: interassignable); |
| } |
| |
| void test_isAssignableTo_call_method() { |
| ClassElementImpl classBottom = ElementFactory.classElement2("B"); |
| MethodElement methodBottom = |
| ElementFactory.methodElement("call", objectType, <DartType>[intType]); |
| classBottom.methods = <MethodElement>[methodBottom]; |
| |
| DartType top = |
| TypeBuilder.function(required: <DartType>[intType], result: objectType); |
| InterfaceType bottom = classBottom.type; |
| |
| _checkIsStrictAssignableTo(bottom, top); |
| } |
| |
| void test_isAssignableTo_classes() { |
| ClassElement classTop = ElementFactory.classElement2("A"); |
| ClassElement classLeft = ElementFactory.classElement("B", classTop.type); |
| ClassElement classRight = ElementFactory.classElement("C", classTop.type); |
| ClassElement classBottom = ElementFactory.classElement("D", classLeft.type) |
| ..interfaces = <InterfaceType>[classRight.type]; |
| InterfaceType top = classTop.type; |
| InterfaceType left = classLeft.type; |
| InterfaceType right = classRight.type; |
| InterfaceType bottom = classBottom.type; |
| |
| _checkLattice(top, left, right, bottom); |
| } |
| |
| void test_isAssignableTo_double() { |
| DartType interfaceType = ElementFactory.classElement2('A', []).type; |
| List<DartType> interassignable = <DartType>[ |
| dynamicType, |
| objectType, |
| doubleType, |
| numType, |
| bottomType |
| ]; |
| List<DartType> unrelated = <DartType>[ |
| intType, |
| stringType, |
| interfaceType, |
| ]; |
| |
| _checkGroups(doubleType, |
| interassignable: interassignable, unrelated: unrelated); |
| } |
| |
| void test_isAssignableTo_dynamic_isTop() { |
| DartType interfaceType = ElementFactory.classElement2('A', []).type; |
| List<DartType> interassignable = <DartType>[ |
| dynamicType, |
| objectType, |
| intType, |
| doubleType, |
| numType, |
| stringType, |
| interfaceType, |
| bottomType |
| ]; |
| _checkGroups(dynamicType, interassignable: interassignable); |
| } |
| |
| void test_isAssignableTo_generics() { |
| ClassElementImpl LClass = ElementFactory.classElement2('L', ["T"]); |
| InterfaceType LType = LClass.type; |
| ClassElementImpl MClass = ElementFactory.classElement2('M', ["T"]); |
| DartType typeParam = MClass.typeParameters[0].type; |
| InterfaceType superType = LType.instantiate(<DartType>[typeParam]); |
| MClass.interfaces = <InterfaceType>[superType]; |
| InterfaceType MType = MClass.type; |
| |
| InterfaceType top = LType.instantiate(<DartType>[dynamicType]); |
| InterfaceType left = MType.instantiate(<DartType>[dynamicType]); |
| InterfaceType right = LType.instantiate(<DartType>[intType]); |
| InterfaceType bottom = MType.instantiate(<DartType>[intType]); |
| |
| _checkCrossLattice(top, left, right, bottom); |
| } |
| |
| void test_isAssignableTo_int() { |
| DartType interfaceType = ElementFactory.classElement2('A', []).type; |
| List<DartType> interassignable = <DartType>[ |
| dynamicType, |
| objectType, |
| intType, |
| numType, |
| bottomType |
| ]; |
| List<DartType> unrelated = <DartType>[ |
| doubleType, |
| stringType, |
| interfaceType, |
| ]; |
| |
| _checkGroups(intType, |
| interassignable: interassignable, unrelated: unrelated); |
| } |
| |
| void test_isAssignableTo_named_optional() { |
| DartType r = |
| TypeBuilder.function(required: <DartType>[intType], result: intType); |
| DartType o = TypeBuilder.function( |
| required: <DartType>[], optional: <DartType>[intType], result: intType); |
| DartType n = TypeBuilder.function( |
| required: <DartType>[], |
| named: <String, DartType>{'x': intType}, |
| result: intType); |
| DartType rr = TypeBuilder |
| .function(required: <DartType>[intType, intType], result: intType); |
| DartType ro = TypeBuilder.function( |
| required: <DartType>[intType], |
| optional: <DartType>[intType], |
| result: intType); |
| DartType rn = TypeBuilder.function( |
| required: <DartType>[intType], |
| named: <String, DartType>{'x': intType}, |
| result: intType); |
| DartType oo = TypeBuilder.function( |
| required: <DartType>[], |
| optional: <DartType>[intType, intType], |
| result: intType); |
| DartType nn = TypeBuilder.function( |
| required: <DartType>[], |
| named: <String, DartType>{'x': intType, 'y': intType}, |
| result: intType); |
| DartType nnn = TypeBuilder.function( |
| required: <DartType>[], |
| named: <String, DartType>{'x': intType, 'y': intType, 'z': intType}, |
| result: intType); |
| |
| _checkGroups(r, |
| interassignable: [r, o, ro, rn, oo], unrelated: [n, rr, nn, nnn]); |
| _checkGroups(o, |
| interassignable: [o, oo], unrelated: [n, rr, ro, rn, nn, nnn]); |
| _checkGroups(n, |
| interassignable: [n, nn, nnn], unrelated: [r, o, rr, ro, rn, oo]); |
| _checkGroups(rr, |
| interassignable: [rr, ro, oo], unrelated: [r, o, n, rn, nn, nnn]); |
| _checkGroups(ro, interassignable: [ro, oo], unrelated: [o, n, rn, nn, nnn]); |
| _checkGroups(rn, |
| interassignable: [rn], unrelated: [o, n, rr, ro, oo, nn, nnn]); |
| _checkGroups(oo, interassignable: [oo], unrelated: [n, rn, nn, nnn]); |
| _checkGroups(nn, |
| interassignable: [nn, nnn], unrelated: [r, o, rr, ro, rn, oo]); |
| _checkGroups(nnn, |
| interassignable: [nnn], unrelated: [r, o, rr, ro, rn, oo]); |
| } |
| |
| void test_isAssignableTo_num() { |
| DartType interfaceType = ElementFactory.classElement2('A', []).type; |
| List<DartType> interassignable = <DartType>[ |
| dynamicType, |
| objectType, |
| numType, |
| intType, |
| doubleType, |
| bottomType |
| ]; |
| List<DartType> unrelated = <DartType>[ |
| stringType, |
| interfaceType, |
| ]; |
| |
| _checkGroups(numType, |
| interassignable: interassignable, unrelated: unrelated); |
| } |
| |
| void test_isAssignableTo_simple_function() { |
| FunctionType top = |
| TypeBuilder.function(required: <DartType>[intType], result: objectType); |
| FunctionType left = |
| TypeBuilder.function(required: <DartType>[intType], result: intType); |
| FunctionType right = TypeBuilder |
| .function(required: <DartType>[objectType], result: objectType); |
| FunctionType bottom = |
| TypeBuilder.function(required: <DartType>[objectType], result: intType); |
| |
| _checkCrossLattice(top, left, right, bottom); |
| } |
| |
| void test_isAssignableTo_void_functions() { |
| FunctionType top = |
| TypeBuilder.function(required: <DartType>[intType], result: voidType); |
| FunctionType bottom = |
| TypeBuilder.function(required: <DartType>[objectType], result: intType); |
| |
| _checkEquivalent(bottom, top); |
| } |
| |
| void _checkCrossLattice( |
| DartType top, DartType left, DartType right, DartType bottom) { |
| _checkGroups(top, interassignable: [top, left, right, bottom]); |
| _checkGroups(left, |
| interassignable: [top, left, bottom], unrelated: [right]); |
| _checkGroups(right, |
| interassignable: [top, right, bottom], unrelated: [left]); |
| _checkGroups(bottom, interassignable: [top, left, right, bottom]); |
| } |
| |
| void _checkEquivalent(DartType type1, DartType type2) { |
| _checkIsAssignableTo(type1, type2); |
| _checkIsAssignableTo(type2, type1); |
| } |
| |
| void _checkGroups(DartType t1, |
| {List<DartType> interassignable, List<DartType> unrelated}) { |
| if (interassignable != null) { |
| for (DartType t2 in interassignable) { |
| _checkEquivalent(t1, t2); |
| } |
| } |
| if (unrelated != null) { |
| for (DartType t2 in unrelated) { |
| _checkUnrelated(t1, t2); |
| } |
| } |
| } |
| |
| void _checkIsAssignableTo(DartType type1, DartType type2) { |
| expect(typeSystem.isAssignableTo(type1, type2), true); |
| } |
| |
| void _checkIsNotAssignableTo(DartType type1, DartType type2) { |
| expect(typeSystem.isAssignableTo(type1, type2), false); |
| } |
| |
| void _checkIsStrictAssignableTo(DartType type1, DartType type2) { |
| _checkIsAssignableTo(type1, type2); |
| _checkIsNotAssignableTo(type2, type1); |
| } |
| |
| void _checkLattice( |
| DartType top, DartType left, DartType right, DartType bottom) { |
| _checkGroups(top, interassignable: <DartType>[top, left, right, bottom]); |
| _checkGroups(left, |
| interassignable: <DartType>[top, left, bottom], |
| unrelated: <DartType>[right]); |
| _checkGroups(right, |
| interassignable: <DartType>[top, right, bottom], |
| unrelated: <DartType>[left]); |
| _checkGroups(bottom, interassignable: <DartType>[top, left, right, bottom]); |
| } |
| |
| void _checkUnrelated(DartType type1, DartType type2) { |
| _checkIsNotAssignableTo(type1, type2); |
| _checkIsNotAssignableTo(type2, type1); |
| } |
| } |
| |
| @reflectiveTest |
| class StrongGenericFunctionInferenceTest { |
| TypeProvider typeProvider; |
| StrongTypeSystemImpl typeSystem; |
| |
| DartType get bottomType => typeProvider.bottomType; |
| InterfaceType get doubleType => typeProvider.doubleType; |
| DartType get dynamicType => typeProvider.dynamicType; |
| InterfaceType get functionType => typeProvider.functionType; |
| InterfaceType get intType => typeProvider.intType; |
| InterfaceType get iterableType => typeProvider.iterableType; |
| InterfaceType get listType => typeProvider.listType; |
| DartType get nullType => typeProvider.nullType; |
| InterfaceType get numType => typeProvider.numType; |
| InterfaceType get objectType => typeProvider.objectType; |
| InterfaceType get stringType => typeProvider.stringType; |
| DartType get voidType => VoidTypeImpl.instance; |
| |
| void setUp() { |
| typeProvider = new TestTypeProvider(); |
| typeSystem = new StrongTypeSystemImpl(typeProvider); |
| } |
| |
| void test_boundedByAnotherTypeParameter() { |
| // <TFrom, TTo extends Iterable<TFrom>>(TFrom) -> TTo |
| var tFrom = TypeBuilder.variable('TFrom'); |
| var tTo = |
| TypeBuilder.variable('TTo', bound: iterableType.instantiate([tFrom])); |
| var cast = TypeBuilder |
| .function(types: [tFrom, tTo], required: [tFrom], result: tTo); |
| expect(_inferCall(cast, [stringType]), [ |
| stringType, |
| iterableType.instantiate([stringType]) |
| ]); |
| } |
| |
| void test_boundedByOuterClass() { |
| // Regression test for https://github.com/dart-lang/sdk/issues/25740. |
| |
| // class A {} |
| var a = ElementFactory.classElement('A', objectType); |
| |
| // class B extends A {} |
| var b = ElementFactory.classElement('B', a.type); |
| |
| // class C<T extends A> { |
| var c = ElementFactory.classElement('C', objectType, ['T']); |
| (c.typeParameters[0] as TypeParameterElementImpl).bound = a.type; |
| // S m<S extends T>(S); |
| var s = TypeBuilder.variable('S'); |
| (s.element as TypeParameterElementImpl).bound = c.typeParameters[0].type; |
| var m = ElementFactory.methodElement('m', s, [s]); |
| m.typeParameters = [s.element]; |
| c.methods = [m]; |
| // } |
| |
| // C<Object> cOfObject; |
| var cOfObject = c.type.instantiate([objectType]); |
| // C<A> cOfA; |
| var cOfA = c.type.instantiate([a.type]); |
| // C<B> cOfB; |
| var cOfB = c.type.instantiate([b.type]); |
| // B b; |
| // cOfB.m(b); // infer <B> |
| expect(_inferCall(cOfB.getMethod('m').type, [b.type]), [b.type, b.type]); |
| // cOfA.m(b); // infer <B> |
| expect(_inferCall(cOfA.getMethod('m').type, [b.type]), [a.type, b.type]); |
| // cOfObject.m(b); // infer <B> |
| expect(_inferCall(cOfObject.getMethod('m').type, [b.type]), |
| [objectType, b.type]); |
| } |
| |
| void test_boundedByOuterClassSubstituted() { |
| // Regression test for https://github.com/dart-lang/sdk/issues/25740. |
| |
| // class A {} |
| var a = ElementFactory.classElement('A', objectType); |
| |
| // class B extends A {} |
| var b = ElementFactory.classElement('B', a.type); |
| |
| // class C<T extends A> { |
| var c = ElementFactory.classElement('C', objectType, ['T']); |
| (c.typeParameters[0] as TypeParameterElementImpl).bound = a.type; |
| // S m<S extends Iterable<T>>(S); |
| var s = TypeBuilder.variable('S'); |
| var iterableOfT = iterableType.instantiate([c.typeParameters[0].type]); |
| (s.element as TypeParameterElementImpl).bound = iterableOfT; |
| var m = ElementFactory.methodElement('m', s, [s]); |
| m.typeParameters = [s.element]; |
| c.methods = [m]; |
| // } |
| |
| // C<Object> cOfObject; |
| var cOfObject = c.type.instantiate([objectType]); |
| // C<A> cOfA; |
| var cOfA = c.type.instantiate([a.type]); |
| // C<B> cOfB; |
| var cOfB = c.type.instantiate([b.type]); |
| // List<B> b; |
| var listOfB = listType.instantiate([b.type]); |
| // cOfB.m(b); // infer <B> |
| expect(_inferCall(cOfB.getMethod('m').type, [listOfB]), [b.type, listOfB]); |
| // cOfA.m(b); // infer <B> |
| expect(_inferCall(cOfA.getMethod('m').type, [listOfB]), [a.type, listOfB]); |
| // cOfObject.m(b); // infer <B> |
| expect(_inferCall(cOfObject.getMethod('m').type, [listOfB]), |
| [objectType, listOfB]); |
| } |
| |
| void test_boundedRecursively() { |
| // class Clonable<T extends Clonable<T>> |
| ClassElementImpl clonable = |
| ElementFactory.classElement('Clonable', objectType, ['T']); |
| (clonable.typeParameters[0] as TypeParameterElementImpl).bound = |
| clonable.type; |
| // class Foo extends Clonable<Foo> |
| ClassElementImpl foo = ElementFactory.classElement('Foo', null); |
| foo.supertype = clonable.type.instantiate([foo.type]); |
| |
| // <S extends Clonable<S>> |
| var s = TypeBuilder.variable('S'); |
| (s.element as TypeParameterElementImpl).bound = |
| clonable.type.instantiate([s]); |
| // (S, S) -> S |
| var clone = TypeBuilder.function(types: [s], required: [s, s], result: s); |
| expect(_inferCall(clone, [foo.type, foo.type]), [foo.type]); |
| |
| // Something invalid... |
| expect(_inferCall(clone, [stringType, numType], expectError: true), |
| [objectType]); |
| } |
| |
| void test_genericCastFunction() { |
| // <TFrom, TTo>(TFrom) -> TTo |
| var tFrom = TypeBuilder.variable('TFrom'); |
| var tTo = TypeBuilder.variable('TTo'); |
| var cast = TypeBuilder |
| .function(types: [tFrom, tTo], required: [tFrom], result: tTo); |
| expect(_inferCall(cast, [intType]), [intType, dynamicType]); |
| } |
| |
| void test_genericCastFunctionWithUpperBound() { |
| // <TFrom, TTo extends TFrom>(TFrom) -> TTo |
| var tFrom = TypeBuilder.variable('TFrom'); |
| var tTo = TypeBuilder.variable('TTo', bound: tFrom); |
| var cast = TypeBuilder |
| .function(types: [tFrom, tTo], required: [tFrom], result: tTo); |
| expect(_inferCall(cast, [intType]), [intType, intType]); |
| } |
| |
| void test_parametersToFunctionParam() { |
| // <T>(f(T t)) -> T |
| var t = TypeBuilder.variable('T'); |
| var cast = TypeBuilder.function(types: [ |
| t |
| ], required: [ |
| TypeBuilder.function(required: [t], result: dynamicType) |
| ], result: t); |
| expect( |
| _inferCall(cast, [ |
| TypeBuilder.function(required: [numType], result: dynamicType) |
| ]), |
| [numType]); |
| } |
| |
| void test_parametersUseLeastUpperBound() { |
| // <T>(T x, T y) -> T |
| var t = TypeBuilder.variable('T'); |
| var cast = TypeBuilder.function(types: [t], required: [t, t], result: t); |
| expect(_inferCall(cast, [intType, doubleType]), [numType]); |
| } |
| |
| void test_parameterTypeUsesUpperBound() { |
| // <T extends num>(T) -> dynamic |
| var t = TypeBuilder.variable('T', bound: numType); |
| var f = |
| TypeBuilder.function(types: [t], required: [t], result: dynamicType); |
| expect(_inferCall(f, [intType]), [intType]); |
| } |
| |
| void test_returnFunctionWithGenericParameter() { |
| // <T>(T -> T) -> (T -> void) |
| var t = TypeBuilder.variable('T'); |
| var f = TypeBuilder.function(types: [ |
| t |
| ], required: [ |
| TypeBuilder.function(required: [t], result: t) |
| ], result: TypeBuilder.function(required: [t], result: voidType)); |
| expect( |
| _inferCall(f, [ |
| TypeBuilder.function(required: [numType], result: intType) |
| ]), |
| [intType]); |
| } |
| |
| void test_returnFunctionWithGenericParameterAndContext() { |
| // <T>(T -> T) -> (T -> Null) |
| var t = TypeBuilder.variable('T'); |
| var f = TypeBuilder.function(types: [ |
| t |
| ], required: [ |
| TypeBuilder.function(required: [t], result: t) |
| ], result: TypeBuilder.function(required: [t], result: nullType)); |
| expect( |
| _inferCall(f, [], |
| returnType: |
| TypeBuilder.function(required: [numType], result: intType)), |
| [numType]); |
| } |
| |
| void test_returnFunctionWithGenericParameterAndReturn() { |
| // <T>(T -> T) -> (T -> T) |
| var t = TypeBuilder.variable('T'); |
| var f = TypeBuilder.function(types: [ |
| t |
| ], required: [ |
| TypeBuilder.function(required: [t], result: t) |
| ], result: TypeBuilder.function(required: [t], result: t)); |
| expect( |
| _inferCall(f, [ |
| TypeBuilder.function(required: [numType], result: intType) |
| ]), |
| [intType]); |
| } |
| |
| void test_returnFunctionWithGenericReturn() { |
| // <T>(T -> T) -> (() -> T) |
| var t = TypeBuilder.variable('T'); |
| var f = TypeBuilder.function(types: [ |
| t |
| ], required: [ |
| TypeBuilder.function(required: [t], result: t) |
| ], result: TypeBuilder.function(required: [], result: t)); |
| expect( |
| _inferCall(f, [ |
| TypeBuilder.function(required: [numType], result: intType) |
| ]), |
| [intType]); |
| } |
| |
| void test_returnTypeFromContext() { |
| // <T>() -> T |
| var t = TypeBuilder.variable('T'); |
| var f = TypeBuilder.function(types: [t], required: [], result: t); |
| expect(_inferCall(f, [], returnType: stringType), [stringType]); |
| } |
| |
| void test_returnTypeWithBoundFromContext() { |
| // <T extends num>() -> T |
| var t = TypeBuilder.variable('T', bound: numType); |
| var f = TypeBuilder.function(types: [t], required: [], result: t); |
| expect(_inferCall(f, [], returnType: doubleType), [doubleType]); |
| } |
| |
| void test_returnTypeWithBoundFromInvalidContext() { |
| // <T extends num>() -> T |
| var t = TypeBuilder.variable('T', bound: numType); |
| var f = TypeBuilder.function(types: [t], required: [], result: t); |
| expect(_inferCall(f, [], returnType: stringType), [nullType]); |
| } |
| |
| void test_unifyParametersToFunctionParam() { |
| // <T>(f(T t), g(T t)) -> T |
| var t = TypeBuilder.variable('T'); |
| var cast = TypeBuilder.function(types: [ |
| t |
| ], required: [ |
| TypeBuilder.function(required: [t], result: dynamicType), |
| TypeBuilder.function(required: [t], result: dynamicType) |
| ], result: t); |
| expect( |
| _inferCall(cast, [ |
| TypeBuilder.function(required: [intType], result: dynamicType), |
| TypeBuilder.function(required: [doubleType], result: dynamicType) |
| ]), |
| [nullType]); |
| } |
| |
| void test_unusedReturnTypeIsDynamic() { |
| // <T>() -> T |
| var t = TypeBuilder.variable('T'); |
| var f = TypeBuilder.function(types: [t], required: [], result: t); |
| expect(_inferCall(f, []), [dynamicType]); |
| } |
| |
| void test_unusedReturnTypeWithUpperBound() { |
| // <T extends num>() -> T |
| var t = TypeBuilder.variable('T', bound: numType); |
| var f = TypeBuilder.function(types: [t], required: [], result: t); |
| expect(_inferCall(f, []), [numType]); |
| } |
| |
| List<DartType> _inferCall(FunctionTypeImpl ft, List<DartType> arguments, |
| {DartType returnType, bool expectError: false}) { |
| var listener = new RecordingErrorListener(); |
| |
| var reporter = new ErrorReporter( |
| listener, |
| new NonExistingSource( |
| '/test.dart', toUri('/test.dart'), UriKind.FILE_URI)); |
| |
| FunctionType inferred = typeSystem.inferGenericFunctionOrType( |
| ft, ft.parameters, arguments, returnType, |
| errorReporter: reporter, |
| errorNode: astFactory.nullLiteral(new KeywordToken(Keyword.NULL, 0))); |
| |
| if (expectError) { |
| expect(listener.errors.map((e) => e.errorCode).toList(), |
| [StrongModeCode.COULD_NOT_INFER], |
| reason: 'expected exactly 1 could not infer error.'); |
| } else { |
| expect(listener.errors, isEmpty, reason: 'did not expect any errors.'); |
| } |
| return inferred?.typeArguments; |
| } |
| } |
| |
| /** |
| * Tests GLB, which only exists in strong mode. |
| */ |
| @reflectiveTest |
| class StrongGreatestLowerBoundTest extends BoundTestBase { |
| void setUp() { |
| super.setUp(); |
| typeSystem = new StrongTypeSystemImpl(typeProvider); |
| } |
| |
| void test_bottom_function() { |
| _checkGreatestLowerBound(bottomType, simpleFunctionType, bottomType); |
| } |
| |
| void test_bottom_interface() { |
| DartType interfaceType = ElementFactory.classElement2('A', []).type; |
| _checkGreatestLowerBound(bottomType, interfaceType, bottomType); |
| } |
| |
| void test_bottom_typeParam() { |
| DartType typeParam = ElementFactory.typeParameterElement('T').type; |
| _checkGreatestLowerBound(bottomType, typeParam, bottomType); |
| } |
| |
| void test_bounds_of_top_types_complete() { |
| // Test every combination of a subset of Tops programatically. |
| final futureOrDynamicType = futureOrType.instantiate([dynamicType]); |
| final futureOrObjectType = futureOrType.instantiate([objectType]); |
| final futureOrVoidType = futureOrType.instantiate([voidType]); |
| final futureOrFutureOrDynamicType = |
| futureOrType.instantiate([futureOrDynamicType]); |
| final futureOrFutureOrObjectType = |
| futureOrType.instantiate([futureOrObjectType]); |
| final futureOrFutureOrVoidType = |
| futureOrType.instantiate([futureOrVoidType]); |
| |
| final orderedTops = [ |
| // Lower index, so lower Top |
| voidType, |
| dynamicType, |
| objectType, |
| futureOrVoidType, |
| futureOrDynamicType, |
| futureOrObjectType, |
| futureOrFutureOrVoidType, |
| futureOrFutureOrDynamicType, |
| futureOrFutureOrObjectType, |
| // Higher index, higher Top |
| ]; |
| |
| // We could sort and check the sort result is correct in O(n log n), but a |
| // good sorting algorithm would only run n tests here (that each value is |
| // correct relative to its nearest neighbors). But O(n^2) for n=6 is stupid |
| // fast, in this case, so just do the brute force check because we can. |
| for (var i = 0; i < orderedTops.length; ++i) { |
| for (var lower = 0; lower <= i; ++lower) { |
| _checkGreatestLowerBound( |
| orderedTops[i], orderedTops[lower], orderedTops[i]); |
| _checkLeastUpperBound( |
| orderedTops[i], orderedTops[lower], orderedTops[lower]); |
| } |
| for (var greater = i; greater < orderedTops.length; ++greater) { |
| _checkGreatestLowerBound( |
| orderedTops[i], orderedTops[greater], orderedTops[greater]); |
| _checkLeastUpperBound( |
| orderedTops[i], orderedTops[greater], orderedTops[i]); |
| } |
| } |
| } |
| |
| void test_bounds_of_top_types_sanity() { |
| final futureOrDynamicType = futureOrType.instantiate([dynamicType]); |
| final futureOrFutureOrDynamicType = |
| futureOrType.instantiate([futureOrDynamicType]); |
| |
| // Sanity check specific cases of top for GLB/LUB. |
| _checkLeastUpperBound(objectType, dynamicType, dynamicType); |
| _checkGreatestLowerBound(objectType, dynamicType, objectType); |
| _checkLeastUpperBound(objectType, voidType, voidType); |
| _checkLeastUpperBound(futureOrDynamicType, dynamicType, dynamicType); |
| _checkGreatestLowerBound( |
| futureOrDynamicType, objectType, futureOrDynamicType); |
| _checkGreatestLowerBound(futureOrDynamicType, futureOrFutureOrDynamicType, |
| futureOrFutureOrDynamicType); |
| } |
| |
| void test_classAndSuperclass() { |
| // class A |
| // class B extends A |
| // class C extends B |
| ClassElementImpl classA = ElementFactory.classElement2("A"); |
| ClassElementImpl classB = ElementFactory.classElement("B", classA.type); |
| ClassElementImpl classC = ElementFactory.classElement("C", classB.type); |
| _checkGreatestLowerBound(classA.type, classC.type, classC.type); |
| } |
| |
| void test_classAndSuperinterface() { |
| // class A |
| // class B implements A |
| // class C implements B |
| ClassElementImpl classA = ElementFactory.classElement2("A"); |
| ClassElementImpl classB = ElementFactory.classElement2("B"); |
| ClassElementImpl classC = ElementFactory.classElement2("C"); |
| classB.interfaces = <InterfaceType>[classA.type]; |
| classC.interfaces = <InterfaceType>[classB.type]; |
| _checkGreatestLowerBound(classA.type, classC.type, classC.type); |
| } |
| |
| void test_dynamic_bottom() { |
| _checkGreatestLowerBound(dynamicType, bottomType, bottomType); |
| } |
| |
| void test_dynamic_function() { |
| _checkGreatestLowerBound( |
| dynamicType, simpleFunctionType, simpleFunctionType); |
| } |
| |
| void test_dynamic_interface() { |
| DartType interfaceType = ElementFactory.classElement2('A', []).type; |
| _checkGreatestLowerBound(dynamicType, interfaceType, interfaceType); |
| } |
| |
| void test_dynamic_typeParam() { |
| DartType typeParam = ElementFactory.typeParameterElement('T').type; |
| _checkGreatestLowerBound(dynamicType, typeParam, typeParam); |
| } |
| |
| void test_dynamic_void() { |
| // Note: _checkGreatestLowerBound tests `GLB(x, y)` as well as `GLB(y, x)` |
| _checkGreatestLowerBound(dynamicType, voidType, dynamicType); |
| } |
| |
| void test_functionsDifferentNamedTakeUnion() { |
| FunctionType type1 = _functionType([], named: {'a': intType, 'b': intType}); |
| FunctionType type2 = |
| _functionType([], named: {'b': doubleType, 'c': stringType}); |
| FunctionType expected = |
| _functionType([], named: {'a': intType, 'b': numType, 'c': stringType}); |
| _checkGreatestLowerBound(type1, type2, expected); |
| } |
| |
| void test_functionsDifferentOptionalArityTakeMax() { |
| FunctionType type1 = _functionType([], optional: [intType]); |
| FunctionType type2 = |
| _functionType([], optional: [doubleType, stringType, objectType]); |
| FunctionType expected = |
| _functionType([], optional: [numType, stringType, objectType]); |
| _checkGreatestLowerBound(type1, type2, expected); |
| } |
| |
| void test_functionsDifferentRequiredArityBecomeOptional() { |
| FunctionType type1 = _functionType([intType]); |
| FunctionType type2 = _functionType([intType, intType, intType]); |
| FunctionType expected = |
| _functionType([intType], optional: [intType, intType]); |
| _checkGreatestLowerBound(type1, type2, expected); |
| } |
| |
| void test_functionsFromDynamic() { |
| FunctionType type1 = _functionType([dynamicType]); |
| FunctionType type2 = _functionType([intType]); |
| FunctionType expected = _functionType([dynamicType]); |
| _checkGreatestLowerBound(type1, type2, expected); |
| } |
| |
| void test_functionsGlbReturnType() { |
| FunctionType type1 = _functionType([], returns: intType); |
| FunctionType type2 = _functionType([], returns: numType); |
| FunctionType expected = _functionType([], returns: intType); |
| _checkGreatestLowerBound(type1, type2, expected); |
| } |
| |
| void test_functionsLubNamedParams() { |
| FunctionType type1 = |
| _functionType([], named: {'a': stringType, 'b': intType}); |
| FunctionType type2 = _functionType([], named: {'a': intType, 'b': numType}); |
| FunctionType expected = |
| _functionType([], named: {'a': objectType, 'b': numType}); |
| _checkGreatestLowerBound(type1, type2, expected); |
| } |
| |
| void test_functionsLubPositionalParams() { |
| FunctionType type1 = _functionType([], optional: [stringType, intType]); |
| FunctionType type2 = _functionType([], optional: [intType, numType]); |
| FunctionType expected = _functionType([], optional: [objectType, numType]); |
| _checkGreatestLowerBound(type1, type2, expected); |
| } |
| |
| void test_functionsLubRequiredParams() { |
| FunctionType type1 = _functionType([stringType, intType, intType]); |
| FunctionType type2 = _functionType([intType, doubleType, numType]); |
| FunctionType expected = _functionType([objectType, numType, numType]); |
| _checkGreatestLowerBound(type1, type2, expected); |
| } |
| |
| void test_functionsMixedOptionalAndRequiredBecomeOptional() { |
| FunctionType type1 = _functionType([intType, intType], |
| optional: [intType, intType, intType]); |
| FunctionType type2 = _functionType([intType], optional: [intType, intType]); |
| FunctionType expected = _functionType([intType], |
| optional: [intType, intType, intType, intType]); |
| _checkGreatestLowerBound(type1, type2, expected); |
| } |
| |
| void test_functionsReturnBottomIfMixOptionalAndNamed() { |
| // Dart doesn't allow a function to have both optional and named parameters, |
| // so if we would have synthethized that, pick bottom instead. |
| FunctionType type1 = _functionType([intType], named: {'a': intType}); |
| FunctionType type2 = _functionType([], named: {'a': intType}); |
| _checkGreatestLowerBound(type1, type2, bottomType); |
| } |
| |
| void test_functionsSameType() { |
| FunctionType type1 = _functionType([stringType, intType, numType], |
| optional: [doubleType], named: {'n': numType}, returns: intType); |
| FunctionType type2 = _functionType([stringType, intType, numType], |
| optional: [doubleType], named: {'n': numType}, returns: intType); |
| FunctionType expected = _functionType([stringType, intType, numType], |
| optional: [doubleType], named: {'n': numType}, returns: intType); |
| _checkGreatestLowerBound(type1, type2, expected); |
| } |
| |
| void test_interface_function() { |
| DartType interfaceType = ElementFactory.classElement2('A', []).type; |
| _checkGreatestLowerBound(interfaceType, simpleFunctionType, bottomType); |
| } |
| |
| void test_mixin() { |
| // class A |
| // class B |
| // class C |
| // class D extends A with B, C |
| ClassElement classA = ElementFactory.classElement2("A"); |
| ClassElement classB = ElementFactory.classElement2("B"); |
| ClassElement classC = ElementFactory.classElement2("C"); |
| ClassElementImpl classD = ElementFactory.classElement("D", classA.type); |
| classD.mixins = <InterfaceType>[classB.type, classC.type]; |
| _checkGreatestLowerBound(classA.type, classD.type, classD.type); |
| _checkGreatestLowerBound(classB.type, classD.type, classD.type); |
| _checkGreatestLowerBound(classC.type, classD.type, classD.type); |
| } |
| |
| void test_self() { |
| DartType typeParam = ElementFactory.typeParameterElement('T').type; |
| DartType interfaceType = ElementFactory.classElement2('A', []).type; |
| |
| List<DartType> types = [ |
| dynamicType, |
| voidType, |
| bottomType, |
| typeParam, |
| interfaceType, |
| simpleFunctionType |
| ]; |
| |
| for (DartType type in types) { |
| _checkGreatestLowerBound(type, type, type); |
| } |
| } |
| |
| void test_typeParam_function_noBound() { |
| DartType typeParam = ElementFactory.typeParameterElement('T').type; |
| _checkGreatestLowerBound(typeParam, simpleFunctionType, bottomType); |
| } |
| |
| void test_typeParam_interface_bounded() { |
| DartType typeA = ElementFactory.classElement2('A', []).type; |
| DartType typeB = ElementFactory.classElement('B', typeA).type; |
| DartType typeC = ElementFactory.classElement('C', typeB).type; |
| TypeParameterElementImpl typeParam = |
| ElementFactory.typeParameterElement('T'); |
| typeParam.bound = typeB; |
| _checkGreatestLowerBound(typeParam.type, typeC, bottomType); |
| } |
| |
| void test_typeParam_interface_noBound() { |
| // GLB(T, A) = ⊥ |
| DartType typeParam = ElementFactory.typeParameterElement('T').type; |
| DartType interfaceType = ElementFactory.classElement2('A', []).type; |
| _checkGreatestLowerBound(typeParam, interfaceType, bottomType); |
| } |
| |
| void test_typeParameters_different() { |
| // GLB(List<int>, List<double>) = ⊥ |
| InterfaceType listOfIntType = listType.instantiate(<DartType>[intType]); |
| InterfaceType listOfDoubleType = |
| listType.instantiate(<DartType>[doubleType]); |
| // TODO(rnystrom): Can we do something better here? |
| _checkGreatestLowerBound(listOfIntType, listOfDoubleType, bottomType); |
| } |
| |
| void test_typeParameters_same() { |
| // GLB(List<int>, List<int>) = List<int> |
| InterfaceType listOfIntType = listType.instantiate(<DartType>[intType]); |
| _checkGreatestLowerBound(listOfIntType, listOfIntType, listOfIntType); |
| } |
| |
| void test_unrelatedClasses() { |
| // class A |
| // class B |
| // class C |
| ClassElementImpl classA = ElementFactory.classElement2("A"); |
| ClassElementImpl classB = ElementFactory.classElement2("B"); |
| _checkGreatestLowerBound(classA.type, classB.type, bottomType); |
| } |
| |
| void test_void() { |
| List<DartType> types = [ |
| bottomType, |
| simpleFunctionType, |
| ElementFactory.classElement2('A', []).type, |
| ElementFactory.typeParameterElement('T').type |
| ]; |
| for (DartType type in types) { |
| _checkGreatestLowerBound(_functionType([], returns: voidType), |
| _functionType([], returns: type), _functionType([], returns: type)); |
| } |
| } |
| } |
| |
| /** |
| * Tests LUB in strong mode. |
| * |
| * Tests defined in this class are ones whose behavior is spec mode-specific. |
| * In particular, function parameters are compared using LUB in spec mode, but |
| * GLB in strong mode. |
| */ |
| @reflectiveTest |
| class StrongLeastUpperBoundTest extends LeastUpperBoundTestBase { |
| void setUp() { |
| super.setUp(); |
| typeSystem = new StrongTypeSystemImpl(typeProvider); |
| } |
| |
| void test_functionsFuzzyArrows() { |
| FunctionType type1 = _functionType([dynamicType]); |
| FunctionType type2 = _functionType([intType]); |
| FunctionType expected = _functionType([intType]); |
| _checkLeastUpperBound(type1, type2, expected); |
| } |
| |
| void test_functionsGlbNamedParams() { |
| FunctionType type1 = |
| _functionType([], named: {'a': stringType, 'b': intType}); |
| FunctionType type2 = _functionType([], named: {'a': intType, 'b': numType}); |
| FunctionType expected = |
| _functionType([], named: {'a': bottomType, 'b': intType}); |
| _checkLeastUpperBound(type1, type2, expected); |
| } |
| |
| void test_functionsGlbPositionalParams() { |
| FunctionType type1 = _functionType([], optional: [stringType, intType]); |
| FunctionType type2 = _functionType([], optional: [intType, numType]); |
| FunctionType expected = _functionType([], optional: [bottomType, intType]); |
| _checkLeastUpperBound(type1, type2, expected); |
| } |
| |
| void test_functionsGlbRequiredParams() { |
| FunctionType type1 = _functionType([stringType, intType, intType]); |
| FunctionType type2 = _functionType([intType, doubleType, numType]); |
| FunctionType expected = _functionType([bottomType, bottomType, intType]); |
| _checkLeastUpperBound(type1, type2, expected); |
| } |
| |
| void test_nestedNestedFunctionsGlbInnermostParamTypes() { |
| FunctionType type1 = _functionType([ |
| _functionType([ |
| _functionType([stringType, intType, intType]) |
| ]) |
| ]); |
| FunctionType type2 = _functionType([ |
| _functionType([ |
| _functionType([intType, doubleType, numType]) |
| ]) |
| ]); |
| FunctionType expected = _functionType([ |
| _functionType([ |
| _functionType([bottomType, bottomType, intType]) |
| ]) |
| ]); |
| _checkLeastUpperBound(type1, type2, expected); |
| } |
| |
| void test_typeParam_boundedByParam() { |
| TypeParameterElementImpl typeParamElementT = |
| ElementFactory.typeParameterElement('T'); |
| TypeParameterElementImpl typeParamElementS = |
| ElementFactory.typeParameterElement('S'); |
| DartType typeParamT = typeParamElementT.type; |
| DartType typeParamS = typeParamElementS.type; |
| typeParamElementT.bound = typeParamS; |
| _checkLeastUpperBound(typeParamT, typeParamS, typeParamS); |
| } |
| |
| void test_typeParam_class_implements_Function_ignored() { |
| DartType typeA = ElementFactory.classElement('A', functionType).type; |
| TypeParameterElementImpl typeParamElement = |
| ElementFactory.typeParameterElement('T'); |
| typeParamElement.bound = typeA; |
| DartType typeParam = typeParamElement.type; |
| _checkLeastUpperBound(typeParam, simpleFunctionType, objectType); |
| } |
| |
| void test_typeParam_fBounded() { |
| ClassElementImpl AClass = ElementFactory.classElement2('A', ["Q"]); |
| InterfaceType AType = AClass.type; |
| |
| DartType s = TypeBuilder.variable("S"); |
| (s.element as TypeParameterElementImpl).bound = AType.instantiate([s]); |
| DartType u = TypeBuilder.variable("U"); |
| (u.element as TypeParameterElementImpl).bound = AType.instantiate([u]); |
| |
| _checkLeastUpperBound(s, u, AType.instantiate([objectType])); |
| } |
| |
| /// Check least upper bound of the same class with different type parameters. |
| void test_typeParameters_different() { |
| // class List<int> |
| // class List<double> |
| InterfaceType listOfIntType = listType.instantiate(<DartType>[intType]); |
| InterfaceType listOfDoubleType = |
| listType.instantiate(<DartType>[doubleType]); |
| InterfaceType listOfNum = listType.instantiate(<DartType>[numType]); |
| _checkLeastUpperBound(listOfIntType, listOfDoubleType, listOfNum); |
| } |
| |
| /// Check least upper bound of two related classes with different |
| /// type parameters. |
| void test_typeParametersAndClass_different() { |
| // class List<int> |
| // class Iterable<double> |
| InterfaceType listOfIntType = listType.instantiate(<DartType>[intType]); |
| InterfaceType iterableOfDoubleType = |
| iterableType.instantiate(<DartType>[doubleType]); |
| // TODO(leafp): this should be iterableOfNumType |
| _checkLeastUpperBound(listOfIntType, iterableOfDoubleType, objectType); |
| } |
| } |
| |
| @reflectiveTest |
| class StrongSubtypingTest { |
| TypeProvider typeProvider; |
| TypeSystem typeSystem; |
| |
| DartType get bottomType => typeProvider.bottomType; |
| InterfaceType get doubleType => typeProvider.doubleType; |
| DartType get dynamicType => typeProvider.dynamicType; |
| InterfaceType get functionType => typeProvider.functionType; |
| InterfaceType get futureOrType => typeProvider.futureOrType; |
| InterfaceType get intType => typeProvider.intType; |
| InterfaceType get listType => typeProvider.listType; |
| InterfaceType get numType => typeProvider.numType; |
| InterfaceType get objectType => typeProvider.objectType; |
| InterfaceType get stringType => typeProvider.stringType; |
| DartType get voidType => VoidTypeImpl.instance; |
| |
| void setUp() { |
| typeProvider = AnalysisContextFactory.contextWithCore().typeProvider; |
| typeSystem = new StrongTypeSystemImpl(typeProvider); |
| } |
| |
| void test_bottom_isBottom() { |
| DartType interfaceType = ElementFactory.classElement2('A', []).type; |
| List<DartType> equivalents = <DartType>[bottomType]; |
| List<DartType> supertypes = <DartType>[ |
| dynamicType, |
| objectType, |
| intType, |
| doubleType, |
| numType, |
| stringType, |
| functionType, |
| interfaceType |
| ]; |
| _checkGroups(bottomType, equivalents: equivalents, supertypes: supertypes); |
| } |
| |
| void test_call_method() { |
| ClassElementImpl classBottom = ElementFactory.classElement2("Bottom"); |
| MethodElement methodBottom = |
| ElementFactory.methodElement("call", objectType, <DartType>[intType]); |
| classBottom.methods = <MethodElement>[methodBottom]; |
| |
| DartType top = |
| TypeBuilder.function(required: <DartType>[intType], result: objectType); |
| InterfaceType bottom = classBottom.type; |
| |
| _checkIsNotSubtypeOf(bottom, top); |
| } |
| |
| void test_classes() { |
| ClassElement classTop = ElementFactory.classElement2("A"); |
| ClassElement classLeft = ElementFactory.classElement("B", classTop.type); |
| ClassElement classRight = ElementFactory.classElement("C", classTop.type); |
| ClassElement classBottom = ElementFactory.classElement("D", classLeft.type) |
| ..interfaces = <InterfaceType>[classRight.type]; |
| InterfaceType top = classTop.type; |
| InterfaceType left = classLeft.type; |
| InterfaceType right = classRight.type; |
| InterfaceType bottom = classBottom.type; |
| |
| _checkLattice(top, left, right, bottom); |
| } |
| |
| void test_double() { |
| List<DartType> equivalents = <DartType>[doubleType]; |
| List<DartType> supertypes = <DartType>[numType]; |
| List<DartType> unrelated = <DartType>[intType]; |
| _checkGroups(doubleType, |
| equivalents: equivalents, supertypes: supertypes, unrelated: unrelated); |
| } |
| |
| void test_dynamic_isTop() { |
| DartType interfaceType = ElementFactory.classElement2('A', []).type; |
| List<DartType> equivalents = <DartType>[dynamicType, objectType, voidType]; |
| List<DartType> subtypes = <DartType>[ |
| intType, |
| doubleType, |
| numType, |
| stringType, |
| functionType, |
| interfaceType, |
| bottomType |
| ]; |
| _checkGroups(dynamicType, equivalents: equivalents, subtypes: subtypes); |
| } |
| |
| void test_function_subtypes_itself_top_types() { |
| var tops = [dynamicType, objectType, voidType]; |
| // Add FutureOr<T> for T := dynamic, object, void |
| tops.addAll(tops.map((t) => futureOrType.instantiate([t])).toList()); |
| // Add FutureOr<FutureOr<T>> for T := dynamic, object, void |
| tops.addAll( |
| tops.skip(3).map((t) => futureOrType.instantiate([t])).toList()); |
| |
| // Function should subtype all of those top types. |
| _checkGroups(functionType, supertypes: [ |
| dynamicType, |
| objectType, |
| voidType, |
| ]); |
| |
| // Create a non-identical but equal copy of Function, and verify subtyping |
| var copyOfFunction = new InterfaceTypeImpl(functionType.element, null); |
| _checkEquivalent(functionType, copyOfFunction); |
| } |
| |
| void test_genericFunction_generic_monomorphic() { |
| DartType s = TypeBuilder.variable("S"); |
| DartType t = TypeBuilder.variable("T", bound: s); |
| DartType u = TypeBuilder.variable("U", bound: intType); |
| DartType v = TypeBuilder.variable("V", bound: u); |
| |
| DartType a = TypeBuilder.variable("A"); |
| DartType b = TypeBuilder.variable("B", bound: a); |
| DartType c = TypeBuilder.variable("C", bound: intType); |
| DartType d = TypeBuilder.variable("D", bound: c); |
| |
| _checkIsStrictSubtypeOf( |
| TypeBuilder.function(types: [s, t], required: [s], result: t), |
| TypeBuilder.function( |
| types: [a, b], required: [bottomType], result: dynamicType)); |
| |
| _checkIsNotSubtypeOf( |
| TypeBuilder.function(types: [u, v], required: [u], result: v), |
| TypeBuilder.function( |
| types: [c, d], required: [objectType], result: objectType)); |
| |
| _checkIsNotSubtypeOf( |
| TypeBuilder.function(types: [u, v], required: [u], result: v), |
| TypeBuilder |
| .function(types: [c, d], required: [intType], result: intType)); |
| } |
| |
| void test_genericFunction_genericDoesNotSubtypeNonGeneric() { |
| DartType s = TypeBuilder.variable("S"); |
| DartType t = TypeBuilder.variable("T", bound: s); |
| DartType u = TypeBuilder.variable("U", bound: intType); |
| DartType v = TypeBuilder.variable("V", bound: u); |
| |
| _checkIsNotSubtypeOf( |
| TypeBuilder.function(types: [s, t], required: [s], result: t), |
| TypeBuilder.function(required: [dynamicType], result: dynamicType)); |
| |
| _checkIsNotSubtypeOf( |
| TypeBuilder.function(types: [u, v], required: [u], result: v), |
| TypeBuilder.function(required: [objectType], result: objectType)); |
| |
| _checkIsNotSubtypeOf( |
| TypeBuilder.function(types: [u, v], required: [u], result: v), |
| TypeBuilder.function(required: [intType], result: intType)); |
| } |
| |
| void test_genericFunction_simple() { |
| DartType s = TypeBuilder.variable("S"); |
| DartType t = TypeBuilder.variable("T"); |
| |
| _checkEquivalent( |
| TypeBuilder.function(types: [t]), TypeBuilder.function(types: [s])); |
| |
| _checkEquivalent(TypeBuilder.function(types: [t], required: [t], result: t), |
| TypeBuilder.function(types: [s], required: [s], result: s)); |
| } |
| |
| void test_genericFunction_simple_bounded() { |
| DartType s = TypeBuilder.variable("S"); |
| DartType t = TypeBuilder.variable("T", bound: s); |
| DartType u = TypeBuilder.variable("U"); |
| DartType v = TypeBuilder.variable("V", bound: u); |
| |
| _checkEquivalent(TypeBuilder.function(types: [s, t]), |
| TypeBuilder.function(types: [u, v])); |
| |
| _checkEquivalent( |
| TypeBuilder.function(types: [s, t], required: [s], result: t), |
| TypeBuilder.function(types: [u, v], required: [u], result: v)); |
| |
| { |
| DartType top = |
| TypeBuilder.function(types: [s, t], required: [t], result: s); |
| DartType left = |
| TypeBuilder.function(types: [u, v], required: [u], result: u); |
| DartType right = |
| TypeBuilder.function(types: [u, v], required: [v], result: v); |
| DartType bottom = |
| TypeBuilder.function(types: [s, t], required: [s], result: t); |
| _checkLattice(top, left, right, bottom); |
| } |
| } |
| |
| void test_generics() { |
| ClassElementImpl LClass = ElementFactory.classElement2('L', ["T"]); |
| InterfaceType LType = LClass.type; |
| ClassElementImpl MClass = ElementFactory.classElement2('M', ["T"]); |
| DartType typeParam = MClass.typeParameters[0].type; |
| InterfaceType superType = LType.instantiate(<DartType>[typeParam]); |
| MClass.interfaces = <InterfaceType>[superType]; |
| InterfaceType MType = MClass.type; |
| |
| InterfaceType top = LType.instantiate(<DartType>[dynamicType]); |
| InterfaceType left = MType.instantiate(<DartType>[dynamicType]); |
| InterfaceType right = LType.instantiate(<DartType>[intType]); |
| InterfaceType bottom = MType.instantiate(<DartType>[intType]); |
| |
| _checkLattice(top, left, right, bottom); |
| } |
| |
| void test_int() { |
| List<DartType> equivalents = <DartType>[intType]; |
| List<DartType> supertypes = <DartType>[numType]; |
| List<DartType> unrelated = <DartType>[doubleType]; |
| _checkGroups(intType, |
| equivalents: equivalents, supertypes: supertypes, unrelated: unrelated); |
| } |
| |
| void test_named_optional() { |
| DartType r = |
| TypeBuilder.function(required: <DartType>[intType], result: intType); |
| DartType o = TypeBuilder.function( |
| required: <DartType>[], optional: <DartType>[intType], result: intType); |
| DartType n = TypeBuilder.function( |
| required: <DartType>[], |
| named: <String, DartType>{'x': intType}, |
| result: intType); |
| DartType rr = TypeBuilder |
| .function(required: <DartType>[intType, intType], result: intType); |
| DartType ro = TypeBuilder.function( |
| required: <DartType>[intType], |
| optional: <DartType>[intType], |
| result: intType); |
| DartType rn = TypeBuilder.function( |
| required: <DartType>[intType], |
| named: <String, DartType>{'x': intType}, |
| result: intType); |
| DartType oo = TypeBuilder.function( |
| required: <DartType>[], |
| optional: <DartType>[intType, intType], |
| result: intType); |
| DartType nn = TypeBuilder.function( |
| required: <DartType>[], |
| named: <String, DartType>{'x': intType, 'y': intType}, |
| result: intType); |
| DartType nnn = TypeBuilder.function( |
| required: <DartType>[], |
| named: <String, DartType>{'x': intType, 'y': intType, 'z': intType}, |
| result: intType); |
| |
| _checkGroups(r, |
| equivalents: [r], |
| subtypes: [o, ro, rn, oo], |
| unrelated: [n, rr, nn, nnn]); |
| _checkGroups(o, |
| equivalents: [o], subtypes: [oo], unrelated: [n, rr, ro, rn, nn, nnn]); |
| _checkGroups(n, |
| equivalents: [n], |
| subtypes: [nn, nnn], |
| unrelated: [r, o, rr, ro, rn, oo]); |
| _checkGroups(rr, |
| equivalents: [rr], |
| subtypes: [ro, oo], |
| unrelated: [r, o, n, rn, nn, nnn]); |
| _checkGroups(ro, |
| equivalents: [ro], subtypes: [oo], unrelated: [o, n, rn, nn, nnn]); |
| _checkGroups(rn, |
| equivalents: [rn], |
| subtypes: [], |
| unrelated: [o, n, rr, ro, oo, nn, nnn]); |
| _checkGroups(oo, |
| equivalents: [oo], subtypes: [], unrelated: [n, rn, nn, nnn]); |
| _checkGroups(nn, |
| equivalents: [nn], subtypes: [nnn], unrelated: [r, o, rr, ro, rn, oo]); |
| _checkGroups(nnn, |
| equivalents: [nnn], subtypes: [], unrelated: [r, o, rr, ro, rn, oo]); |
| } |
| |
| void test_num() { |
| List<DartType> equivalents = <DartType>[numType]; |
| List<DartType> supertypes = <DartType>[]; |
| List<DartType> unrelated = <DartType>[stringType]; |
| List<DartType> subtypes = <DartType>[intType, doubleType]; |
| _checkGroups(numType, |
| equivalents: equivalents, |
| supertypes: supertypes, |
| unrelated: unrelated, |
| subtypes: subtypes); |
| } |
| |
| void test_simple_function() { |
| FunctionType top = |
| TypeBuilder.function(required: <DartType>[intType], result: objectType); |
| FunctionType left = |
| TypeBuilder.function(required: <DartType>[intType], result: intType); |
| FunctionType right = TypeBuilder |
| .function(required: <DartType>[objectType], result: objectType); |
| FunctionType bottom = |
| TypeBuilder.function(required: <DartType>[objectType], result: intType); |
| |
| _checkLattice(top, left, right, bottom); |
| } |
| |
| /// Regression test for https://github.com/dart-lang/sdk/issues/25069 |
| void test_simple_function_void() { |
| FunctionType functionType = |
| TypeBuilder.function(required: <DartType>[intType], result: objectType); |
| _checkIsNotSubtypeOf(voidType, functionType); |
| } |
| |
| void test_void_functions() { |
| FunctionType top = |
| TypeBuilder.function(required: <DartType>[intType], result: voidType); |
| FunctionType bottom = |
| TypeBuilder.function(required: <DartType>[objectType], result: intType); |
| |
| _checkIsStrictSubtypeOf(bottom, top); |
| } |
| |
| void test_void_isTop() { |
| DartType interfaceType = ElementFactory.classElement2('A', []).type; |
| List<DartType> equivalents = <DartType>[dynamicType, objectType, voidType]; |
| List<DartType> subtypes = <DartType>[ |
| intType, |
| doubleType, |
| numType, |
| stringType, |
| functionType, |
| interfaceType, |
| bottomType |
| ]; |
| _checkGroups(voidType, equivalents: equivalents, subtypes: subtypes); |
| } |
| |
| void _checkEquivalent(DartType type1, DartType type2) { |
| _checkIsSubtypeOf(type1, type2); |
| _checkIsSubtypeOf(type2, type1); |
| } |
| |
| void _checkGroups(DartType t1, |
| {List<DartType> equivalents, |
| List<DartType> unrelated, |
| List<DartType> subtypes, |
| List<DartType> supertypes}) { |
| if (equivalents != null) { |
| for (DartType t2 in equivalents) { |
| _checkEquivalent(t1, t2); |
| } |
| } |
| if (unrelated != null) { |
| for (DartType t2 in unrelated) { |
| _checkUnrelated(t1, t2); |
| } |
| } |
| if (subtypes != null) { |
| for (DartType t2 in subtypes) { |
| _checkIsStrictSubtypeOf(t2, t1); |
| } |
| } |
| if (supertypes != null) { |
| for (DartType t2 in supertypes) { |
| _checkIsStrictSubtypeOf(t1, t2); |
| } |
| } |
| } |
| |
| void _checkIsNotSubtypeOf(DartType type1, DartType type2) { |
| expect(typeSystem.isSubtypeOf(type1, type2), false); |
| } |
| |
| void _checkIsStrictSubtypeOf(DartType type1, DartType type2) { |
| _checkIsSubtypeOf(type1, type2); |
| _checkIsNotSubtypeOf(type2, type1); |
| } |
| |
| void _checkIsSubtypeOf(DartType type1, DartType type2) { |
| expect(typeSystem.isSubtypeOf(type1, type2), true); |
| } |
| |
| void _checkLattice( |
| DartType top, DartType left, DartType right, DartType bottom) { |
| _checkGroups(top, |
| equivalents: <DartType>[top], |
| subtypes: <DartType>[left, right, bottom]); |
| _checkGroups(left, |
| equivalents: <DartType>[left], |
| subtypes: <DartType>[bottom], |
| unrelated: <DartType>[right], |
| supertypes: <DartType>[top]); |
| _checkGroups(right, |
| equivalents: <DartType>[right], |
| subtypes: <DartType>[bottom], |
| unrelated: <DartType>[left], |
| supertypes: <DartType>[top]); |
| _checkGroups(bottom, |
| equivalents: <DartType>[bottom], |
| supertypes: <DartType>[top, left, right]); |
| } |
| |
| void _checkUnrelated(DartType type1, DartType type2) { |
| _checkIsNotSubtypeOf(type1, type2); |
| _checkIsNotSubtypeOf(type2, type1); |
| } |
| } |
| |
| class TypeBuilder { |
| static FunctionTypeImpl function( |
| {List<DartType> types, |
| List<DartType> required, |
| List<DartType> optional, |
| Map<String, DartType> named, |
| DartType result}) { |
| result = result ?? VoidTypeImpl.instance; |
| required = required ?? []; |
| FunctionElementImpl f = ElementFactory.functionElement8(required, result, |
| optional: optional, named: named); |
| if (types != null) { |
| f.typeParameters = |
| new List<TypeParameterElement>.from(types.map((t) => t.element)); |
| } |
| return f.type = new FunctionTypeImpl(f); |
| } |
| |
| static TypeParameterType variable(String name, {DartType bound}) => |
| ElementFactory.typeParameterWithType(name, bound).type; |
| } |