| // Copyright (c) 2020, 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/ast/standard_ast_factory.dart'; |
| import 'package:analyzer/dart/ast/token.dart'; |
| import 'package:analyzer/dart/element/element.dart'; |
| import 'package:analyzer/dart/element/type.dart'; |
| import 'package:analyzer/error/listener.dart'; |
| import 'package:analyzer/src/dart/ast/token.dart'; |
| import 'package:analyzer/src/dart/element/generic_inferrer.dart'; |
| import 'package:analyzer/src/dart/element/type.dart'; |
| import 'package:analyzer/src/dart/resolver/variance.dart'; |
| import 'package:analyzer/src/error/codes.dart'; |
| import 'package:analyzer/src/generated/source.dart'; |
| import 'package:meta/meta.dart'; |
| import 'package:path/path.dart' show toUri; |
| import 'package:test/test.dart'; |
| import 'package:test_reflective_loader/test_reflective_loader.dart'; |
| |
| import '../../../generated/type_system_test.dart'; |
| |
| main() { |
| defineReflectiveSuite(() { |
| defineReflectiveTests(ConstraintMatchingTest); |
| defineReflectiveTests(GenericFunctionInferenceTest); |
| }); |
| } |
| |
| @reflectiveTest |
| class ConstraintMatchingTest extends AbstractTypeSystemNullSafetyTest { |
| TypeParameterElement T; |
| TypeParameterType T_none; |
| TypeParameterType T_question; |
| |
| @override |
| void setUp() { |
| super.setUp(); |
| T = typeParameter('T'); |
| T_none = typeParameterTypeNone(T); |
| T_question = typeParameterTypeQuestion(T); |
| } |
| |
| void test_function_coreFunction() { |
| _checkOrdinarySubtypeMatch( |
| functionTypeNone( |
| returnType: voidNone, |
| ), |
| typeProvider.functionType, |
| [T], |
| covariant: true, |
| ); |
| } |
| |
| void test_function_parameters_requiredPositional() { |
| _checkIsSubtypeMatchOf( |
| functionTypeNone( |
| parameters: [ |
| requiredParameter(type: T_none), |
| ], |
| returnType: intNone, |
| ), |
| functionTypeNone( |
| parameters: [ |
| requiredParameter(type: stringNone), |
| ], |
| returnType: intNone, |
| ), |
| [T], |
| ['String <: T'], |
| covariant: true, |
| ); |
| } |
| |
| void test_function_returnType() { |
| _checkIsSubtypeMatchOf( |
| functionTypeNone( |
| parameters: [ |
| requiredParameter(type: intNone), |
| ], |
| returnType: T_none, |
| ), |
| functionTypeNone( |
| parameters: [ |
| requiredParameter(type: intNone), |
| ], |
| returnType: stringNone, |
| ), |
| [T], |
| ['T <: String'], |
| covariant: true, |
| ); |
| } |
| |
| /// Left FutureOr: if `T0` is `FutureOr<S0>` then: |
| /// * `T0 <: T1` iff `Future<S0> <: T1` and `S0 <: T1` |
| void test_futureOr_left() { |
| _checkIsSubtypeMatchOf( |
| futureOrNone(T_none), |
| futureOrNone(stringNone), |
| [T], |
| ['T <: String'], |
| covariant: true, |
| ); |
| |
| _checkIsSubtypeMatchOf( |
| futureOrNone(T_none), |
| futureNone(stringNone), |
| [T], |
| ['T <: String', 'T <: Future<String>'], |
| covariant: true, |
| ); |
| |
| // Future<List<T>> <: List<String> can't be satisfied |
| _checkIsNotSubtypeMatchOf( |
| futureOrNone(listNone(T_none)), |
| listNone(stringNone), |
| [T], |
| covariant: true, |
| ); |
| |
| // List<T> <: Future<List<String>> can't be satisfied |
| _checkIsNotSubtypeMatchOf( |
| futureOrNone(listNone(T_none)), |
| futureNone(listNone(stringNone)), |
| [T], |
| covariant: true, |
| ); |
| } |
| |
| void test_futureOr_right_both_noConstraints() { |
| // `Future<String> <: Future<Object>` can be satisfied: |
| // * no constraints |
| // |
| // `Future<String> <: Object` can be satisfied: |
| // * no constraints |
| _checkIsSubtypeMatchOf( |
| futureNone(stringNone), |
| futureOrNone(objectNone), |
| [T], |
| [], |
| covariant: true, |
| ); |
| } |
| |
| void test_futureOr_right_both_prefer_future() { |
| // `Future<String> <: Future<T>` can be satisfied: |
| // * `String <: T` |
| // |
| // `Future<String> <: T` can be satisfied: |
| // * `Future<String> <: T` |
| // |
| // We prefer `String <: T`. |
| _checkIsSubtypeMatchOf( |
| futureNone(stringNone), |
| futureOrNone(T_none), |
| [T], |
| ['String <: T'], |
| covariant: false, |
| ); |
| |
| // `Future<T> <: Future<Object>` can be satisfied: |
| // * `T <: Object` |
| // |
| // `Future<T> <: Object` can be satisfied: |
| // * no constraints |
| // |
| // We prefer `T <: Object`. |
| _checkIsSubtypeMatchOf( |
| futureNone(T_none), |
| futureOrNone(objectNone), |
| [T], |
| ['T <: Object'], |
| covariant: true, |
| ); |
| } |
| |
| /// Right FutureOr: if `T1` is `FutureOr<S1>` then: |
| /// `T0 <: T1` iff any of the following hold: |
| /// * either `T0 <: Future<S1>` |
| /// * or `T0 <: S1` |
| void test_futureOr_right_none() { |
| // `List<T> <: Future<String>` cannot be satisfied. |
| // `List<T> <: int` cannot be satisfied. |
| _checkIsNotSubtypeMatchOf( |
| listNone(T_none), |
| futureOrNone(stringNone), |
| [T], |
| covariant: true, |
| ); |
| } |
| |
| void test_futureOr_right_single_future() { |
| // `Future<T> <: Future<String>` can be satisfied: |
| // * `T <: String` |
| // |
| // `Future<T> <: String` cannot be satisfied. |
| _checkIsSubtypeMatchOf( |
| futureNone(T_none), |
| futureOrNone(stringNone), |
| [T], |
| ['T <: String'], |
| covariant: true, |
| ); |
| } |
| |
| void test_futureOr_right_single_nonFuture() { |
| // `List<T> <: Future<List<String>>` cannot be satisfied. |
| // |
| // `List<T> <: List<String>` can be satisfied: |
| // * `T <: String` |
| _checkIsSubtypeMatchOf( |
| listNone(T_none), |
| futureOrNone(listNone(stringNone)), |
| [T], |
| ['T <: String'], |
| covariant: true, |
| ); |
| } |
| |
| void test_futureOr_right_single_nonFuture_null() { |
| // `Null <: Future<T>` can be satisfied: |
| // * no constraints |
| // |
| // `Null <: T` can be satisfied: |
| // * `Null <: T` |
| // |
| // We prefer `Null <: T`. |
| _checkIsSubtypeMatchOf( |
| nullNone, |
| futureOrNone(T_none), |
| [T], |
| ['Null <: T'], |
| covariant: false, |
| ); |
| } |
| |
| void test_interfaceType_interface_left() { |
| _checkIsNotSubtypeMatchOf( |
| iterableNone(T_none), |
| listNone(stringNone), |
| [T], |
| covariant: true, |
| ); |
| } |
| |
| void test_interfaceType_interface_right() { |
| _checkIsSubtypeMatchOf( |
| listNone(T_none), |
| iterableNone(stringNone), |
| [T], |
| ['T <: String'], |
| covariant: true, |
| ); |
| } |
| |
| void test_interfaceType_sameClass() { |
| _checkIsSubtypeMatchOf( |
| listNone(T_none), |
| listNone(stringNone), |
| [T], |
| ['T <: String'], |
| covariant: true, |
| ); |
| } |
| |
| void test_interfaceType_topMerge() { |
| var testClassIndex = 0; |
| |
| void check1( |
| DartType extendsTypeArgument, |
| DartType implementsTypeArgument, |
| String expectedConstraint, |
| ) { |
| var library = library_( |
| uriStr: 'package:test/test.dart', |
| analysisSession: analysisContext.analysisSession, |
| typeSystem: typeSystem, |
| ); |
| |
| // class A<T> {} |
| var A = class_(name: 'A', typeParameters: [ |
| typeParameter('T'), |
| ]); |
| A.enclosingElement = library.definingCompilationUnit; |
| |
| // class B<T> extends A<T> {} |
| var B_T = typeParameter('T'); |
| var B_T_none = typeParameterTypeNone(B_T); |
| var B = class_( |
| name: 'B', |
| typeParameters: [B_T], |
| superType: interfaceTypeNone(A, typeArguments: [B_T_none]), |
| ); |
| B.enclosingElement = library.definingCompilationUnit; |
| |
| // class Cx extends A<> implements B<> {} |
| var C = class_( |
| name: 'C${testClassIndex++}', |
| superType: interfaceTypeNone( |
| A, |
| typeArguments: [extendsTypeArgument], |
| ), |
| interfaces: [ |
| interfaceTypeNone( |
| B, |
| typeArguments: [implementsTypeArgument], |
| ) |
| ], |
| ); |
| C.enclosingElement = library.definingCompilationUnit; |
| |
| _checkIsSubtypeMatchOf( |
| interfaceTypeNone(C), |
| interfaceTypeNone(A, typeArguments: [T_none]), |
| [T], |
| [expectedConstraint], |
| covariant: false, |
| ); |
| } |
| |
| void check( |
| DartType typeArgument1, |
| DartType typeArgument2, |
| String expectedConstraint, |
| ) { |
| check1(typeArgument1, typeArgument2, expectedConstraint); |
| check1(typeArgument2, typeArgument1, expectedConstraint); |
| } |
| |
| check(objectQuestion, dynamicNone, 'Object? <: T'); |
| check(objectStar, dynamicNone, 'Object? <: T'); |
| check(voidNone, objectQuestion, 'Object? <: T'); |
| check(voidNone, objectStar, 'Object? <: T'); |
| } |
| |
| void test_left_null() { |
| // Null <: T is trivially satisfied by the constraint Null <: T. |
| _checkIsSubtypeMatchOf(nullNone, T_none, [T], ['Null <: T'], |
| covariant: false); |
| |
| // For any other type X, Null <: X is satisfied without the need for any |
| // constraints. |
| _checkOrdinarySubtypeMatch(nullNone, listQuestion(T_none), [T], |
| covariant: false); |
| _checkOrdinarySubtypeMatch(nullNone, stringQuestion, [T], covariant: false); |
| _checkOrdinarySubtypeMatch(nullNone, voidNone, [T], covariant: false); |
| _checkOrdinarySubtypeMatch(nullNone, dynamicNone, [T], covariant: false); |
| _checkOrdinarySubtypeMatch(nullNone, objectQuestion, [T], covariant: false); |
| _checkOrdinarySubtypeMatch(nullNone, nullNone, [T], covariant: false); |
| _checkOrdinarySubtypeMatch( |
| nullNone, |
| functionTypeQuestion(returnType: voidNone), |
| [T], |
| covariant: false, |
| ); |
| } |
| |
| void test_right_dynamic() { |
| // T <: dynamic is trivially satisfied by the constraint T <: dynamic. |
| _checkIsSubtypeMatchOf(T_none, dynamicNone, [T], ['T <: dynamic'], |
| covariant: true); |
| |
| // For any other type X, X <: dynamic is satisfied without the need for any |
| // constraints. |
| _checkOrdinarySubtypeMatch(listNone(T_none), dynamicNone, [T], |
| covariant: true); |
| _checkOrdinarySubtypeMatch(stringNone, dynamicNone, [T], covariant: true); |
| _checkOrdinarySubtypeMatch(voidNone, dynamicNone, [T], covariant: true); |
| _checkOrdinarySubtypeMatch(dynamicNone, dynamicNone, [T], covariant: true); |
| _checkOrdinarySubtypeMatch(objectNone, dynamicNone, [T], covariant: true); |
| _checkOrdinarySubtypeMatch(nullNone, dynamicNone, [T], covariant: true); |
| _checkOrdinarySubtypeMatch( |
| functionTypeNone( |
| parameters: [ |
| requiredParameter(type: intNone), |
| ], |
| returnType: stringNone, |
| ), |
| dynamicNone, |
| [T], |
| covariant: true, |
| ); |
| } |
| |
| void test_right_object() { |
| // T <: Object is trivially satisfied by the constraint T <: Object. |
| _checkIsSubtypeMatchOf(T_none, objectNone, [T], ['T <: Object'], |
| covariant: true); |
| |
| // For any other type X, X <: Object is satisfied without the need for any |
| // constraints. |
| _checkOrdinarySubtypeMatch(listNone(T_none), objectNone, [T], |
| covariant: true); |
| _checkOrdinarySubtypeMatch(stringNone, objectNone, [T], covariant: true); |
| _checkOrdinarySubtypeMatch(voidNone, objectNone, [T], covariant: true); |
| _checkOrdinarySubtypeMatch(dynamicNone, objectNone, [T], covariant: true); |
| _checkOrdinarySubtypeMatch(objectNone, objectNone, [T], covariant: true); |
| _checkOrdinarySubtypeMatch(nullNone, objectNone, [T], covariant: true); |
| _checkOrdinarySubtypeMatch( |
| functionTypeNone( |
| parameters: [ |
| requiredParameter(type: intNone), |
| ], |
| returnType: stringNone, |
| ), |
| objectNone, |
| [T], |
| covariant: true, |
| ); |
| } |
| |
| void test_right_void() { |
| // T <: void is trivially satisfied by the constraint T <: void. |
| _checkIsSubtypeMatchOf(T_none, voidNone, [T], ['T <: void'], |
| covariant: true); |
| |
| // For any other type X, X <: void is satisfied without the need for any |
| // constraints. |
| _checkOrdinarySubtypeMatch(listNone(T_none), voidNone, [T], |
| covariant: true); |
| _checkOrdinarySubtypeMatch(stringNone, voidNone, [T], covariant: true); |
| _checkOrdinarySubtypeMatch(voidNone, voidNone, [T], covariant: true); |
| _checkOrdinarySubtypeMatch(dynamicNone, voidNone, [T], covariant: true); |
| _checkOrdinarySubtypeMatch(objectNone, voidNone, [T], covariant: true); |
| _checkOrdinarySubtypeMatch(nullNone, voidNone, [T], covariant: true); |
| _checkOrdinarySubtypeMatch( |
| functionTypeNone( |
| parameters: [ |
| requiredParameter(type: intNone), |
| ], |
| returnType: stringNone, |
| ), |
| voidNone, |
| [T], |
| covariant: true, |
| ); |
| } |
| |
| void test_typeParameter_left_covariant() { |
| // When doing a covariant match, the type parameters we're trying to find |
| // types for are on the left hand side. |
| _checkIsSubtypeMatchOf(T_none, stringNone, [T], ['T <: String'], |
| covariant: true); |
| } |
| |
| void test_typeParameter_left_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 = typeParameter('S'); |
| var S_none = typeParameterTypeNone(S); |
| _checkIsSubtypeMatchOf(T_none, S_none, [T], ['T <: S'], covariant: true); |
| } |
| |
| void test_typeParameter_left_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 = typeParameter('S'); |
| var S_none = typeParameterTypeNone(S); |
| _checkIsNotSubtypeMatchOf(listNone(T_none), S_none, [T], covariant: true); |
| } |
| |
| void test_typeParameter_nullable() { |
| _checkIsSubtypeMatchOf(T_question, stringQuestion, [T], ['T <: String'], |
| covariant: true); |
| |
| _checkIsSubtypeMatchOf(stringQuestion, T_question, [T], ['String <: T'], |
| covariant: false); |
| } |
| |
| void test_typeParameter_right_contravariant() { |
| // When doing a contravariant match, the type parameters we're trying to |
| // find types for are on the right hand side. |
| _checkIsSubtypeMatchOf(stringNone, T_none, [T], ['String <: T'], |
| covariant: false); |
| } |
| |
| void test_typeParameter_right_contravariant_direct() { |
| // When doing a contravariant match, the type parameters we're trying to |
| // find types for are on the right hand side. If 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 = typeParameter('S'); |
| var S_none = typeParameterTypeNone(S); |
| _checkIsSubtypeMatchOf(listNone(S_none), listNone(T_none), [T], ['S <: T'], |
| covariant: false); |
| } |
| |
| void test_typeParameter_right_contravariant_via_bound() { |
| // When doing a contravariant match, the type parameters we're trying to |
| // find types for are on the right hand side. If 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 = typeParameter('S', bound: listNone(stringNone)); |
| var S_none = typeParameterTypeNone(S); |
| _checkIsSubtypeMatchOf(S_none, listNone(T_none), [T], ['String <: T'], |
| covariant: false); |
| } |
| |
| void test_variance_contravariant() { |
| // class A<in T> |
| var T = typeParameter('T', variance: Variance.contravariant); |
| var T_none = typeParameterTypeNone(T); |
| var A = class_(name: 'A', typeParameters: [T]); |
| |
| _checkIsSubtypeMatchOf( |
| interfaceTypeNone(A, typeArguments: [T_none]), |
| interfaceTypeNone(A, typeArguments: [numNone]), |
| [T], |
| ['num <: in T'], |
| covariant: true, |
| ); |
| } |
| |
| void test_variance_covariant() { |
| // class A<out T> |
| var T = typeParameter('T', variance: Variance.covariant); |
| var T_none = typeParameterTypeNone(T); |
| var A = class_(name: 'A', typeParameters: [T]); |
| |
| _checkIsSubtypeMatchOf( |
| interfaceTypeNone(A, typeArguments: [T_none]), |
| interfaceTypeNone(A, typeArguments: [numNone]), |
| [T], |
| ['out T <: num'], |
| covariant: true, |
| ); |
| } |
| |
| void test_variance_invariant() { |
| // class A<inout T> |
| var T = typeParameter('T', variance: Variance.invariant); |
| var T_none = typeParameterTypeNone(T); |
| var A = class_(name: 'A', typeParameters: [T]); |
| |
| _checkIsSubtypeMatchOf( |
| interfaceTypeNone(A, typeArguments: [T_none]), |
| interfaceTypeNone(A, typeArguments: [numNone]), |
| [T], |
| ['inout T <: num', 'num <: inout T'], |
| covariant: true, |
| ); |
| } |
| |
| void _checkIsNotSubtypeMatchOf( |
| DartType subtype, |
| DartType supertype, |
| List<TypeParameterElement> typeFormals, { |
| @required bool covariant, |
| }) { |
| var inferrer = GenericInferrer(typeSystem, typeFormals); |
| |
| var success = inferrer.tryMatchSubtypeOf(subtype, supertype, null, |
| covariant: covariant); |
| expect(success, isFalse); |
| |
| inferrer.constraints.forEach((typeParameter, constraints) { |
| expect(constraints, isEmpty); |
| }); |
| } |
| |
| void _checkIsSubtypeMatchOf( |
| DartType subtype, |
| DartType supertype, |
| List<TypeParameterElement> typeFormals, |
| Iterable<String> expectedConstraints, { |
| @required bool covariant, |
| }) { |
| var inferrer = GenericInferrer(typeSystem, typeFormals); |
| |
| var success = inferrer.tryMatchSubtypeOf(subtype, supertype, null, |
| covariant: covariant); |
| expect(success, isTrue); |
| |
| var formattedConstraints = <String>[]; |
| inferrer.constraints.forEach((typeParameter, constraints) { |
| for (var constraint in constraints) { |
| formattedConstraints.add( |
| constraint.format( |
| typeParameter.getDisplayString(withNullability: true), |
| withNullability: true, |
| ), |
| ); |
| } |
| }); |
| expect(formattedConstraints, unorderedEquals(expectedConstraints)); |
| } |
| |
| void _checkOrdinarySubtypeMatch( |
| DartType subtype, |
| DartType supertype, |
| List<TypeParameterElement> typeFormals, { |
| @required bool covariant, |
| }) { |
| var expectSuccess = typeSystem.isSubtypeOf(subtype, supertype); |
| if (expectSuccess) { |
| _checkIsSubtypeMatchOf(subtype, supertype, typeFormals, [], |
| covariant: covariant); |
| } else { |
| _checkIsNotSubtypeMatchOf(subtype, supertype, typeFormals, |
| covariant: covariant); |
| } |
| } |
| } |
| |
| @reflectiveTest |
| class GenericFunctionInferenceTest extends AbstractTypeSystemNullSafetyTest { |
| void test_boundedByAnotherTypeParameter() { |
| // <TFrom, TTo extends Iterable<TFrom>>(TFrom) -> TTo |
| var tFrom = typeParameter('TFrom'); |
| var tTo = |
| typeParameter('TTo', bound: iterableNone(typeParameterTypeNone(tFrom))); |
| var cast = functionTypeNone( |
| typeFormals: [tFrom, tTo], |
| parameters: [ |
| requiredParameter( |
| type: typeParameterTypeNone(tFrom), |
| ), |
| ], |
| returnType: typeParameterTypeNone(tTo), |
| ); |
| expect(_inferCall(cast, [stringNone]), |
| [stringNone, (iterableNone(stringNone))]); |
| } |
| |
| void test_boundedByOuterClass() { |
| // Regression test for https://github.com/dart-lang/sdk/issues/25740. |
| |
| // class A {} |
| var A = class_(name: 'A', superType: objectNone); |
| var typeA = interfaceTypeNone(A); |
| |
| // class B extends A {} |
| var B = class_(name: 'B', superType: typeA); |
| var typeB = interfaceTypeNone(B); |
| |
| // class C<T extends A> { |
| var CT = typeParameter('T', bound: typeA); |
| var C = class_( |
| name: 'C', |
| superType: objectNone, |
| typeParameters: [CT], |
| ); |
| // S m<S extends T>(S); |
| var S = typeParameter('S', bound: typeParameterTypeNone(CT)); |
| var m = method( |
| 'm', |
| typeParameterTypeNone(S), |
| typeFormals: [S], |
| parameters: [ |
| requiredParameter( |
| name: '_', |
| type: typeParameterTypeNone(S), |
| ), |
| ], |
| ); |
| C.methods = [m]; |
| // } |
| |
| // C<Object> cOfObject; |
| var cOfObject = interfaceTypeNone(C, typeArguments: [objectNone]); |
| // C<A> cOfA; |
| var cOfA = interfaceTypeNone(C, typeArguments: [typeA]); |
| // C<B> cOfB; |
| var cOfB = interfaceTypeNone(C, typeArguments: [typeB]); |
| // B b; |
| // cOfB.m(b); // infer <B> |
| _assertType( |
| _inferCall2(cOfB.getMethod('m').type, [typeB]), 'B Function(B)'); |
| // cOfA.m(b); // infer <B> |
| _assertType( |
| _inferCall2(cOfA.getMethod('m').type, [typeB]), 'B Function(B)'); |
| // cOfObject.m(b); // infer <B> |
| _assertType( |
| _inferCall2(cOfObject.getMethod('m').type, [typeB]), 'B Function(B)'); |
| } |
| |
| void test_boundedByOuterClassSubstituted() { |
| // Regression test for https://github.com/dart-lang/sdk/issues/25740. |
| |
| // class A {} |
| var A = class_(name: 'A', superType: objectNone); |
| var typeA = interfaceTypeNone(A); |
| |
| // class B extends A {} |
| var B = class_(name: 'B', superType: typeA); |
| var typeB = interfaceTypeNone(B); |
| |
| // class C<T extends A> { |
| var CT = typeParameter('T', bound: typeA); |
| var C = class_( |
| name: 'C', |
| superType: objectNone, |
| typeParameters: [CT], |
| ); |
| // S m<S extends Iterable<T>>(S); |
| var iterableOfT = iterableNone(typeParameterTypeNone(CT)); |
| var S = typeParameter('S', bound: iterableOfT); |
| var m = method( |
| 'm', |
| typeParameterTypeNone(S), |
| typeFormals: [S], |
| parameters: [ |
| requiredParameter( |
| name: '_', |
| type: typeParameterTypeNone(S), |
| ), |
| ], |
| ); |
| C.methods = [m]; |
| // } |
| |
| // C<Object> cOfObject; |
| var cOfObject = interfaceTypeNone(C, typeArguments: [objectNone]); |
| // C<A> cOfA; |
| var cOfA = interfaceTypeNone(C, typeArguments: [typeA]); |
| // C<B> cOfB; |
| var cOfB = interfaceTypeNone(C, typeArguments: [typeB]); |
| // List<B> b; |
| var listOfB = listNone(typeB); |
| // cOfB.m(b); // infer <B> |
| _assertType(_inferCall2(cOfB.getMethod('m').type, [listOfB]), |
| 'List<B> Function(List<B>)'); |
| // cOfA.m(b); // infer <B> |
| _assertType(_inferCall2(cOfA.getMethod('m').type, [listOfB]), |
| 'List<B> Function(List<B>)'); |
| // cOfObject.m(b); // infer <B> |
| _assertType(_inferCall2(cOfObject.getMethod('m').type, [listOfB]), |
| 'List<B> Function(List<B>)'); |
| } |
| |
| void test_boundedRecursively() { |
| // class A<T extends A<T>> |
| var T = typeParameter('T'); |
| var A = class_( |
| name: 'Cloneable', |
| superType: objectNone, |
| typeParameters: [T], |
| ); |
| T.bound = interfaceTypeNone( |
| A, |
| typeArguments: [typeParameterTypeNone(T)], |
| ); |
| |
| // class B extends A<B> {} |
| var B = class_(name: 'B', superType: null); |
| B.supertype = interfaceTypeNone(A, typeArguments: [interfaceTypeNone(B)]); |
| var typeB = interfaceTypeNone(B); |
| |
| // <S extends A<S>> |
| var S = typeParameter('S'); |
| var typeS = typeParameterTypeNone(S); |
| S.bound = interfaceTypeNone(A, typeArguments: [typeS]); |
| |
| // (S, S) -> S |
| var clone = functionTypeNone( |
| typeFormals: [S], |
| parameters: [ |
| requiredParameter(type: typeS), |
| requiredParameter(type: typeS), |
| ], |
| returnType: typeS, |
| ); |
| expect(_inferCall(clone, [typeB, typeB]), [typeB]); |
| |
| // Something invalid... |
| expect( |
| _inferCall(clone, [stringNone, numNone], expectError: true), |
| [objectNone], |
| ); |
| } |
| |
| void test_genericCastFunction() { |
| // <TFrom, TTo>(TFrom) -> TTo |
| var tFrom = typeParameter('TFrom'); |
| var tTo = typeParameter('TTo'); |
| var cast = functionTypeNone( |
| typeFormals: [tFrom, tTo], |
| parameters: [ |
| requiredParameter( |
| type: typeParameterTypeNone(tFrom), |
| ), |
| ], |
| returnType: typeParameterTypeNone(tTo), |
| ); |
| expect(_inferCall(cast, [intNone]), [intNone, dynamicNone]); |
| } |
| |
| void test_genericCastFunctionWithUpperBound() { |
| // <TFrom, TTo extends TFrom>(TFrom) -> TTo |
| var tFrom = typeParameter('TFrom'); |
| var tTo = typeParameter( |
| 'TTo', |
| bound: typeParameterTypeNone(tFrom), |
| ); |
| var cast = functionTypeNone( |
| typeFormals: [tFrom, tTo], |
| parameters: [ |
| requiredParameter( |
| type: typeParameterTypeNone(tFrom), |
| ), |
| ], |
| returnType: typeParameterTypeNone(tTo), |
| ); |
| expect(_inferCall(cast, [intNone]), [intNone, intNone]); |
| } |
| |
| void test_parameter_contravariantUseUpperBound() { |
| // <T>(T x, void Function(T) y) -> T |
| // Generates constraints int <: T <: num. |
| // Since T is contravariant, choose num. |
| var T = typeParameter('T', variance: Variance.contravariant); |
| var tFunction = functionTypeNone( |
| parameters: [requiredParameter(type: typeParameterTypeNone(T))], |
| returnType: voidNone); |
| var numFunction = functionTypeNone( |
| parameters: [requiredParameter(type: numNone)], returnType: voidNone); |
| var function = functionTypeNone( |
| typeFormals: [T], |
| parameters: [ |
| requiredParameter(type: typeParameterTypeNone(T)), |
| requiredParameter(type: tFunction) |
| ], |
| returnType: typeParameterTypeNone(T), |
| ); |
| |
| expect(_inferCall(function, [intNone, numFunction]), [numNone]); |
| } |
| |
| void test_parameter_covariantUseLowerBound() { |
| // <T>(T x, void Function(T) y) -> T |
| // Generates constraints int <: T <: num. |
| // Since T is covariant, choose int. |
| var T = typeParameter('T', variance: Variance.covariant); |
| var tFunction = functionTypeNone( |
| parameters: [requiredParameter(type: typeParameterTypeNone(T))], |
| returnType: voidNone); |
| var numFunction = functionTypeNone( |
| parameters: [requiredParameter(type: numNone)], returnType: voidNone); |
| var function = functionTypeNone( |
| typeFormals: [T], |
| parameters: [ |
| requiredParameter(type: typeParameterTypeNone(T)), |
| requiredParameter(type: tFunction) |
| ], |
| returnType: typeParameterTypeNone(T), |
| ); |
| |
| expect(_inferCall(function, [intNone, numFunction]), [intNone]); |
| } |
| |
| void test_parametersToFunctionParam() { |
| // <T>(f(T t)) -> T |
| var T = typeParameter('T'); |
| var cast = functionTypeNone( |
| typeFormals: [T], |
| parameters: [ |
| requiredParameter( |
| type: functionTypeNone( |
| parameters: [ |
| requiredParameter( |
| type: typeParameterTypeNone(T), |
| ), |
| ], |
| returnType: dynamicNone, |
| ), |
| ), |
| ], |
| returnType: typeParameterTypeNone(T), |
| ); |
| expect( |
| _inferCall(cast, [ |
| functionTypeNone( |
| parameters: [ |
| requiredParameter(type: numNone), |
| ], |
| returnType: dynamicNone, |
| ) |
| ]), |
| [numNone], |
| ); |
| } |
| |
| void test_parametersUseLeastUpperBound() { |
| // <T>(T x, T y) -> T |
| var T = typeParameter('T'); |
| var cast = functionTypeNone( |
| typeFormals: [T], |
| parameters: [ |
| requiredParameter(type: typeParameterTypeNone(T)), |
| requiredParameter(type: typeParameterTypeNone(T)), |
| ], |
| returnType: typeParameterTypeNone(T), |
| ); |
| expect(_inferCall(cast, [intNone, doubleNone]), [numNone]); |
| } |
| |
| void test_parameterTypeUsesUpperBound() { |
| // <T extends num>(T) -> dynamic |
| var T = typeParameter('T', bound: numNone); |
| var f = functionTypeNone( |
| typeFormals: [T], |
| parameters: [ |
| requiredParameter(type: typeParameterTypeNone(T)), |
| ], |
| returnType: dynamicNone, |
| ); |
| expect(_inferCall(f, [intNone]), [intNone]); |
| } |
| |
| void test_returnFunctionWithGenericParameter() { |
| // <T>(T -> T) -> (T -> void) |
| var T = typeParameter('T'); |
| var f = functionTypeNone( |
| typeFormals: [T], |
| parameters: [ |
| requiredParameter( |
| type: functionTypeNone( |
| parameters: [ |
| requiredParameter(type: typeParameterTypeNone(T)), |
| ], |
| returnType: typeParameterTypeNone(T), |
| ), |
| ), |
| ], |
| returnType: functionTypeNone( |
| parameters: [ |
| requiredParameter(type: typeParameterTypeNone(T)), |
| ], |
| returnType: voidNone, |
| ), |
| ); |
| expect( |
| _inferCall(f, [ |
| functionTypeNone( |
| parameters: [ |
| requiredParameter(type: numNone), |
| ], |
| returnType: intNone, |
| ), |
| ]), |
| [intNone], |
| ); |
| } |
| |
| void test_returnFunctionWithGenericParameterAndContext() { |
| // <T>(T -> T) -> (T -> Null) |
| var T = typeParameter('T'); |
| var f = functionTypeNone( |
| typeFormals: [T], |
| parameters: [ |
| requiredParameter( |
| type: functionTypeNone( |
| parameters: [ |
| requiredParameter(type: typeParameterTypeNone(T)), |
| ], |
| returnType: typeParameterTypeNone(T), |
| ), |
| ), |
| ], |
| returnType: functionTypeNone( |
| parameters: [ |
| requiredParameter(type: typeParameterTypeNone(T)), |
| ], |
| returnType: nullNone, |
| ), |
| ); |
| expect( |
| _inferCall( |
| f, |
| [], |
| returnType: functionTypeNone( |
| parameters: [ |
| requiredParameter(type: numNone), |
| ], |
| returnType: intNone, |
| ), |
| ), |
| [numNone], |
| ); |
| } |
| |
| void test_returnFunctionWithGenericParameterAndReturn() { |
| // <T>(T -> T) -> (T -> T) |
| var T = typeParameter('T'); |
| var f = functionTypeNone( |
| typeFormals: [T], |
| parameters: [ |
| requiredParameter( |
| type: functionTypeNone( |
| parameters: [ |
| requiredParameter(type: typeParameterTypeNone(T)), |
| ], |
| returnType: typeParameterTypeNone(T), |
| ), |
| ), |
| ], |
| returnType: functionTypeNone( |
| parameters: [ |
| requiredParameter(type: typeParameterTypeNone(T)), |
| ], |
| returnType: typeParameterTypeNone(T), |
| ), |
| ); |
| expect( |
| _inferCall(f, [ |
| functionTypeNone( |
| parameters: [ |
| requiredParameter(type: numNone), |
| ], |
| returnType: intNone, |
| ) |
| ]), |
| [intNone], |
| ); |
| } |
| |
| void test_returnFunctionWithGenericReturn() { |
| // <T>(T -> T) -> (() -> T) |
| var T = typeParameter('T'); |
| var f = functionTypeNone( |
| typeFormals: [T], |
| parameters: [ |
| requiredParameter( |
| type: functionTypeNone( |
| parameters: [ |
| requiredParameter(type: typeParameterTypeNone(T)), |
| ], |
| returnType: typeParameterTypeNone(T), |
| ), |
| ), |
| ], |
| returnType: functionTypeNone( |
| returnType: typeParameterTypeNone(T), |
| ), |
| ); |
| expect( |
| _inferCall(f, [ |
| functionTypeNone( |
| parameters: [ |
| requiredParameter(type: numNone), |
| ], |
| returnType: intNone, |
| ) |
| ]), |
| [intNone], |
| ); |
| } |
| |
| void test_returnTypeFromContext() { |
| // <T>() -> T |
| var T = typeParameter('T'); |
| var f = functionTypeNone( |
| typeFormals: [T], |
| returnType: typeParameterTypeNone(T), |
| ); |
| expect(_inferCall(f, [], returnType: stringNone), [stringNone]); |
| } |
| |
| void test_returnTypeWithBoundFromContext() { |
| // <T extends num>() -> T |
| var T = typeParameter('T', bound: numNone); |
| var f = functionTypeNone( |
| typeFormals: [T], |
| returnType: typeParameterTypeNone(T), |
| ); |
| expect(_inferCall(f, [], returnType: doubleNone), [doubleNone]); |
| } |
| |
| void test_returnTypeWithBoundFromInvalidContext() { |
| // <T extends num>() -> T |
| var T = typeParameter('T', bound: numNone); |
| var f = functionTypeNone( |
| typeFormals: [T], |
| returnType: typeParameterTypeNone(T), |
| ); |
| expect(_inferCall(f, [], returnType: stringNone), [neverNone]); |
| } |
| |
| void test_unifyParametersToFunctionParam() { |
| // <T>(f(T t), g(T t)) -> T |
| var T = typeParameter('T'); |
| var cast = functionTypeNone( |
| typeFormals: [T], |
| parameters: [ |
| requiredParameter( |
| type: functionTypeNone( |
| parameters: [ |
| requiredParameter( |
| type: typeParameterTypeNone(T), |
| ), |
| ], |
| returnType: dynamicNone, |
| ), |
| ), |
| requiredParameter( |
| type: functionTypeNone( |
| parameters: [ |
| requiredParameter( |
| type: typeParameterTypeNone(T), |
| ), |
| ], |
| returnType: dynamicNone, |
| ), |
| ), |
| ], |
| returnType: typeParameterTypeNone(T), |
| ); |
| expect( |
| _inferCall(cast, [ |
| functionTypeNone( |
| parameters: [ |
| requiredParameter(type: intNone), |
| ], |
| returnType: dynamicNone, |
| ), |
| functionTypeNone( |
| parameters: [ |
| requiredParameter(type: doubleNone), |
| ], |
| returnType: dynamicNone, |
| ) |
| ]), |
| [neverNone], |
| ); |
| } |
| |
| void test_unusedReturnTypeIsDynamic() { |
| // <T>() -> T |
| var T = typeParameter('T'); |
| var f = functionTypeNone( |
| typeFormals: [T], |
| returnType: typeParameterTypeNone(T), |
| ); |
| expect(_inferCall(f, []), [dynamicNone]); |
| } |
| |
| void test_unusedReturnTypeWithUpperBound() { |
| // <T extends num>() -> T |
| var T = typeParameter('T', bound: numNone); |
| var f = functionTypeNone( |
| typeFormals: [T], |
| returnType: typeParameterTypeNone(T), |
| ); |
| expect(_inferCall(f, []), [numNone]); |
| } |
| |
| void _assertType(DartType type, String expected) { |
| var typeStr = type.getDisplayString(withNullability: false); |
| expect(typeStr, expected); |
| } |
| |
| List<DartType> _inferCall(FunctionTypeImpl ft, List<DartType> arguments, |
| {DartType returnType, bool expectError = false}) { |
| var listener = RecordingErrorListener(); |
| |
| var reporter = ErrorReporter( |
| listener, |
| NonExistingSource('/test.dart', toUri('/test.dart'), UriKind.FILE_URI), |
| isNonNullableByDefault: false, |
| ); |
| |
| var typeArguments = typeSystem.inferGenericFunctionOrType( |
| typeParameters: ft.typeFormals, |
| parameters: ft.parameters, |
| declaredReturnType: ft.returnType, |
| argumentTypes: arguments, |
| contextReturnType: returnType, |
| errorReporter: reporter, |
| errorNode: astFactory.nullLiteral(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 typeArguments; |
| } |
| |
| FunctionType _inferCall2(FunctionTypeImpl ft, List<DartType> arguments, |
| {DartType returnType, bool expectError = false}) { |
| var typeArguments = _inferCall( |
| ft, |
| arguments, |
| returnType: returnType, |
| expectError: expectError, |
| ); |
| return ft.instantiate(typeArguments); |
| } |
| } |