| // Copyright (c) 2017, 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. |
| |
| // @dart = 2.9 |
| |
| import 'package:front_end/src/fasta/type_inference/type_schema.dart'; |
| import 'package:front_end/src/fasta/type_inference/type_schema_environment.dart'; |
| import 'package:kernel/ast.dart'; |
| import 'package:kernel/core_types.dart'; |
| import 'package:kernel/class_hierarchy.dart'; |
| import 'package:kernel/testing/type_parser_environment.dart'; |
| import 'package:test/test.dart'; |
| import 'package:test_reflective_loader/test_reflective_loader.dart'; |
| |
| main() { |
| defineReflectiveSuite(() { |
| defineReflectiveTests(TypeSchemaEnvironmentTest); |
| }); |
| } |
| |
| @reflectiveTest |
| class TypeSchemaEnvironmentTest { |
| Env typeParserEnvironment; |
| TypeSchemaEnvironment typeSchemaEnvironment; |
| |
| final Map<String, DartType Function()> additionalTypes = { |
| "UNKNOWN": () => new UnknownType(), |
| }; |
| |
| Library _coreLibrary; |
| Library _testLibrary; |
| |
| Library get coreLibrary => _coreLibrary; |
| Library get testLibrary => _testLibrary; |
| |
| Component get component => typeParserEnvironment.component; |
| CoreTypes get coreTypes => typeParserEnvironment.coreTypes; |
| |
| void parseTestLibrary(String testLibraryText) { |
| typeParserEnvironment = |
| new Env(testLibraryText, isNonNullableByDefault: false); |
| typeSchemaEnvironment = new TypeSchemaEnvironment( |
| coreTypes, new ClassHierarchy(component, coreTypes)); |
| assert( |
| typeParserEnvironment.component.libraries.length == 2, |
| "The tests are supposed to have exactly two libraries: " |
| "the core library and the test library."); |
| Library firstLibrary = typeParserEnvironment.component.libraries.first; |
| Library secondLibrary = typeParserEnvironment.component.libraries.last; |
| if (firstLibrary.importUri.scheme == "dart" && |
| firstLibrary.importUri.path == "core") { |
| _coreLibrary = firstLibrary; |
| _testLibrary = secondLibrary; |
| } else { |
| assert( |
| secondLibrary.importUri.scheme == "dart" && |
| secondLibrary.importUri.path == "core", |
| "One of the libraries is expected to be 'dart:core'."); |
| _coreLibrary == secondLibrary; |
| _testLibrary = firstLibrary; |
| } |
| } |
| |
| void test_addLowerBound() { |
| parseTestLibrary("class A; class B extends A; class C extends A;"); |
| checkConstraintLowerBound(constraint: "", bound: "UNKNOWN"); |
| checkConstraintLowerBound(constraint: ":> B*", bound: "B*"); |
| checkConstraintLowerBound(constraint: ":> B* :> C*", bound: "A*"); |
| } |
| |
| void test_addUpperBound() { |
| parseTestLibrary("class A; class B extends A; class C extends A;"); |
| checkConstraintUpperBound(constraint: "", bound: "UNKNOWN"); |
| checkConstraintUpperBound(constraint: "<: A*", bound: "A*"); |
| checkConstraintUpperBound(constraint: "<: A* <: B*", bound: "B*"); |
| checkConstraintUpperBound(constraint: "<: A* <: B* <: C*", bound: "Never*"); |
| } |
| |
| void test_glb_bottom() { |
| parseTestLibrary("class A;"); |
| checkLowerBound(type1: "Null", type2: "A*", lowerBound: "Null"); |
| checkLowerBound(type1: "A*", type2: "Null", lowerBound: "Null"); |
| } |
| |
| void test_glb_function() { |
| parseTestLibrary("class A; class B extends A;"); |
| |
| // GLB(() -> A, () -> B) = () -> B |
| checkLowerBound( |
| type1: "() ->* A*", type2: "() ->* B*", lowerBound: "() ->* B*"); |
| |
| // GLB(() -> void, (A, B) -> void) = ([A, B]) -> void |
| checkLowerBound( |
| type1: "() ->* void", |
| type2: "(A*, B*) ->* void", |
| lowerBound: "([A*, B*]) ->* void"); |
| checkLowerBound( |
| type1: "(A*, B*) ->* void", |
| type2: "() ->* void", |
| lowerBound: "([A*, B*]) ->* void"); |
| |
| // GLB((A) -> void, (B) -> void) = (A) -> void |
| checkLowerBound( |
| type1: "(A*) ->* void", |
| type2: "(B*) ->* void", |
| lowerBound: "(A*) ->* void"); |
| checkLowerBound( |
| type1: "(B*) ->* void", |
| type2: "(A*) ->* void", |
| lowerBound: "(A*) ->* void"); |
| |
| // GLB(({a: A}) -> void, ({b: B}) -> void) = ({a: A, b: B}) -> void |
| checkLowerBound( |
| type1: "({A* a}) ->* void", |
| type2: "({B* b}) ->* void", |
| lowerBound: "({A* a, B* b}) ->* void"); |
| checkLowerBound( |
| type1: "({B* b}) ->* void", |
| type2: "({A* a}) ->* void", |
| lowerBound: "({A* a, B* b}) ->* void"); |
| |
| // GLB(({a: A, c: A}) -> void, ({b: B, d: B}) -> void) |
| // = ({a: A, b: B, c: A, d: B}) -> void |
| checkLowerBound( |
| type1: "({A* a, A* c}) ->* void", |
| type2: "({B* b, B* d}) ->* void", |
| lowerBound: "({A* a, B* b, A* c, B* d}) ->* void"); |
| |
| // GLB(({a: A, b: B}) -> void, ({a: B, b: A}) -> void) |
| // = ({a: A, b: A}) -> void |
| checkLowerBound( |
| type1: "({A* a, B* b}) ->* void", |
| type2: "({B* a, A* b}) ->* void", |
| lowerBound: "({A* a, A* b}) ->* void"); |
| checkLowerBound( |
| type1: "({B* a, A* b}) ->* void", |
| type2: "({A* a, B* b}) ->* void", |
| lowerBound: "({A* a, A* b}) ->* void"); |
| |
| // GLB((B, {a: A}) -> void, (B) -> void) = (B, {a: A}) -> void |
| checkLowerBound( |
| type1: "(B*, {A* a}) ->* void", |
| type2: "(B*) ->* void", |
| lowerBound: "(B*, {A* a}) ->* void"); |
| |
| // GLB(({a: A}) -> void, (B) -> void) = bottom |
| checkLowerBound( |
| type1: "({A* a}) ->* void", |
| type2: "(B*) ->* void", |
| lowerBound: "Never*"); |
| |
| // GLB(({a: A}) -> void, ([B]) -> void) = bottom |
| checkLowerBound( |
| type1: "({A* a}) ->* void", |
| type2: "([B*]) ->* void", |
| lowerBound: "Never*"); |
| } |
| |
| void test_glb_identical() { |
| parseTestLibrary("class A;"); |
| checkLowerBound(type1: "A*", type2: "A*", lowerBound: "A*"); |
| } |
| |
| void test_glb_subtype() { |
| parseTestLibrary("class A; class B extends A;"); |
| |
| checkLowerBound(type1: "A*", type2: "B*", lowerBound: "B*"); |
| checkLowerBound(type1: "B*", type2: "A*", lowerBound: "B*"); |
| } |
| |
| void test_glb_top() { |
| parseTestLibrary("class A;"); |
| checkLowerBound(type1: "dynamic", type2: "A*", lowerBound: "A*"); |
| checkLowerBound(type1: "A*", type2: "dynamic", lowerBound: "A*"); |
| checkLowerBound(type1: "Object*", type2: "A*", lowerBound: "A*"); |
| checkLowerBound(type1: "A*", type2: "Object*", lowerBound: "A*"); |
| checkLowerBound(type1: "void", type2: "A*", lowerBound: "A*"); |
| checkLowerBound(type1: "A*", type2: "void", lowerBound: "A*"); |
| } |
| |
| void test_glb_unknown() { |
| parseTestLibrary("class A;"); |
| checkLowerBound(type1: "A*", type2: "UNKNOWN", lowerBound: "A*"); |
| checkLowerBound(type1: "UNKNOWN", type2: "A*", lowerBound: "A*"); |
| } |
| |
| void test_glb_unrelated() { |
| parseTestLibrary("class A; class B;"); |
| checkLowerBound(type1: "A*", type2: "B*", lowerBound: "Never*"); |
| } |
| |
| void test_inferGenericFunctionOrType() { |
| parseTestLibrary(""); |
| |
| // Test an instantiation of [1, 2.0] with no context. This should infer |
| // as List<?> during downwards inference. |
| checkInference( |
| typeParametersToInfer: "T extends Object*", |
| functionType: "() ->* List<T*>*", |
| actualParameterTypes: null, |
| returnContextType: null, |
| expectedTypes: "UNKNOWN"); |
| // And upwards inference should refine it to List<num>. |
| checkInference( |
| typeParametersToInfer: "T extends Object*", |
| functionType: "(T*, T*) ->* List<T*>*", |
| actualParameterTypes: "int*, double*", |
| returnContextType: null, |
| inferredTypesFromDownwardPhase: "UNKNOWN", |
| expectedTypes: "num*"); |
| |
| // Test an instantiation of [1, 2.0] with a context of List<Object>. This |
| // should infer as List<Object> during downwards inference. |
| checkInference( |
| typeParametersToInfer: "T extends Object*", |
| functionType: "() ->* List<T*>*", |
| actualParameterTypes: null, |
| returnContextType: "List<Object*>*", |
| expectedTypes: "Object*"); |
| // And upwards inference should preserve the type. |
| checkInference( |
| typeParametersToInfer: "T extends Object*", |
| functionType: "(T*, T*) ->* List<T>", |
| actualParameterTypes: "int*, double*", |
| returnContextType: "List<Object*>*", |
| inferredTypesFromDownwardPhase: "Object*", |
| expectedTypes: "Object*"); |
| } |
| |
| void test_inferTypeFromConstraints_applyBound() { |
| parseTestLibrary(""); |
| |
| // With no constraints: |
| // Downward inference should infer '?' |
| checkInferenceFromConstraints( |
| typeParameter: "T extends num*", |
| constraints: "", |
| downwardsInferPhase: true, |
| expected: "UNKNOWN"); |
| // Upward inference should infer num |
| checkInferenceFromConstraints( |
| typeParameter: "T extends num*", |
| constraints: "", |
| downwardsInferPhase: false, |
| inferredTypeFromDownwardPhase: "UNKNOWN", |
| expected: "num*"); |
| |
| // With an upper bound of Object: |
| // Downward inference should infer num. |
| checkInferenceFromConstraints( |
| typeParameter: "T extends num*", |
| constraints: "<: Object*", |
| downwardsInferPhase: true, |
| expected: "num*"); |
| // Upward inference should infer num. |
| checkInferenceFromConstraints( |
| typeParameter: "T extends num*", |
| constraints: "<: Object*", |
| downwardsInferPhase: false, |
| inferredTypeFromDownwardPhase: "num*", |
| expected: "num*"); |
| // Upward inference should still infer num even if there are more |
| // constraints now, because num was finalized during downward inference. |
| checkInferenceFromConstraints( |
| typeParameter: "T extends num*", |
| constraints: ":> int* <: int*", |
| downwardsInferPhase: false, |
| inferredTypeFromDownwardPhase: "num*", |
| expected: "num*"); |
| } |
| |
| void test_inferTypeFromConstraints_simple() { |
| parseTestLibrary(""); |
| |
| // With an upper bound of List<?>: |
| // Downwards inference should infer List<List<?>> |
| checkInferenceFromConstraints( |
| typeParameter: "T extends Object*", |
| constraints: "<: List<UNKNOWN>*", |
| downwardsInferPhase: true, |
| expected: "List<UNKNOWN>*"); |
| // Upwards inference should refine that to List<List<dynamic>> |
| checkInferenceFromConstraints( |
| typeParameter: "T extends Object*", |
| constraints: "<: List<UNKNOWN>*", |
| downwardsInferPhase: false, |
| inferredTypeFromDownwardPhase: "List<UNKNOWN>*", |
| expected: "List<dynamic>*"); |
| } |
| |
| void test_lub_classic() { |
| // Make the class hierarchy: |
| // |
| // Object |
| // | |
| // A |
| // /| |
| // B C |
| // |X| |
| // D E |
| parseTestLibrary(""" |
| class A; |
| class B extends A; |
| class C extends A; |
| class D implements B, C; |
| class E implements B, C; |
| """); |
| |
| checkUpperBound(type1: "D*", type2: "E*", upperBound: "A*"); |
| } |
| |
| void test_lub_commonClass() { |
| parseTestLibrary(""); |
| checkUpperBound( |
| type1: "List<int*>*", |
| type2: "List<double*>*", |
| upperBound: "List<num*>*"); |
| } |
| |
| void test_lub_function() { |
| parseTestLibrary("class A; class B extends A;"); |
| |
| // LUB(() -> A, () -> B) = () -> A |
| checkUpperBound( |
| type1: "() ->* A*", type2: "() ->* B*", upperBound: "() ->* A*"); |
| |
| // LUB(([A]) -> void, (A) -> void) = Function |
| checkUpperBound( |
| type1: "([A*]) ->* void", |
| type2: "(A*) ->* void", |
| upperBound: "Function*"); |
| |
| // LUB(() -> void, (A, B) -> void) = Function |
| checkUpperBound( |
| type1: "() ->* void", |
| type2: "(A*, B*) ->* void", |
| upperBound: "Function*"); |
| checkUpperBound( |
| type1: "(A*, B*) ->* void", |
| type2: "() ->* void", |
| upperBound: "Function*"); |
| |
| // LUB((A) -> void, (B) -> void) = (B) -> void |
| checkUpperBound( |
| type1: "(A*) ->* void", |
| type2: "(B*) ->* void", |
| upperBound: "(B*) ->* void"); |
| checkUpperBound( |
| type1: "(B*) ->* void", |
| type2: "(A*) ->* void", |
| upperBound: "(B*) ->* void"); |
| |
| // LUB(({a: A}) -> void, ({b: B}) -> void) = () -> void |
| checkUpperBound( |
| type1: "({A* a}) ->* void", |
| type2: "({B* b}) ->* void", |
| upperBound: "() ->* void"); |
| checkUpperBound( |
| type1: "({B* b}) ->* void", |
| type2: "({A* a}) ->* void", |
| upperBound: "() ->* void"); |
| |
| // LUB(({a: A, c: A}) -> void, ({b: B, d: B}) -> void) = () -> void |
| checkUpperBound( |
| type1: "({A* a, A* c}) ->* void", |
| type2: "({B* b, B* d}) ->* void", |
| upperBound: "() ->* void"); |
| |
| // LUB(({a: A, b: B}) -> void, ({a: B, b: A}) -> void) |
| // = ({a: B, b: B}) -> void |
| checkUpperBound( |
| type1: "({A* a, B* b}) ->* void", |
| type2: "({B* a, A* b}) ->* void", |
| upperBound: "({B* a, B* b}) ->* void"); |
| checkUpperBound( |
| type1: "({B* a, A* b}) ->* void", |
| type2: "({A* a, B* b}) ->* void", |
| upperBound: "({B* a, B* b}) ->* void"); |
| |
| // LUB((B, {a: A}) -> void, (B) -> void) = (B) -> void |
| checkUpperBound( |
| type1: "(B*, {A* a}) ->* void", |
| type2: "(B*) ->* void", |
| upperBound: "(B*) ->* void"); |
| |
| // LUB(({a: A}) -> void, (B) -> void) = Function |
| checkUpperBound( |
| type1: "({A* a}) ->* void", |
| type2: "(B*) ->* void", |
| upperBound: "Function*"); |
| |
| // GLB(({a: A}) -> void, ([B]) -> void) = () -> void |
| checkUpperBound( |
| type1: "({A* a}) ->* void", |
| type2: "([B*]) ->* void", |
| upperBound: "() ->* void"); |
| } |
| |
| void test_lub_identical() { |
| parseTestLibrary("class A;"); |
| checkUpperBound(type1: "A*", type2: "A*", upperBound: "A*"); |
| } |
| |
| void test_lub_sameClass() { |
| parseTestLibrary("class A; class B extends A; class Map<X, Y>;"); |
| checkUpperBound( |
| type1: "Map<A*, B*>*", |
| type2: "Map<B*, A*>*", |
| upperBound: "Map<A*, A*>*"); |
| } |
| |
| void test_lub_subtype() { |
| parseTestLibrary(""); |
| checkUpperBound( |
| type1: "List<int*>*", |
| type2: "Iterable<num*>*", |
| upperBound: "Iterable<num*>*"); |
| checkUpperBound( |
| type1: "Iterable<num*>*", |
| type2: "List<int*>*", |
| upperBound: "Iterable<num*>*"); |
| } |
| |
| void test_lub_top() { |
| parseTestLibrary("class A;"); |
| |
| checkUpperBound(type1: "dynamic", type2: "A*", upperBound: "dynamic"); |
| checkUpperBound(type1: "A*", type2: "dynamic", upperBound: "dynamic"); |
| checkUpperBound(type1: "Object*", type2: "A*", upperBound: "Object*"); |
| checkUpperBound(type1: "A*", type2: "Object*", upperBound: "Object*"); |
| checkUpperBound(type1: "void", type2: "A*", upperBound: "void"); |
| checkUpperBound(type1: "A*", type2: "void", upperBound: "void"); |
| checkUpperBound(type1: "dynamic", type2: "Object*", upperBound: "dynamic"); |
| checkUpperBound(type1: "Object*", type2: "dynamic", upperBound: "dynamic"); |
| checkUpperBound(type1: "dynamic", type2: "void", upperBound: "void"); |
| checkUpperBound(type1: "void", type2: "dynamic", upperBound: "void"); |
| checkUpperBound(type1: "Object*", type2: "void", upperBound: "void"); |
| checkUpperBound(type1: "void", type2: "Object*", upperBound: "void"); |
| } |
| |
| void test_lub_typeParameter() { |
| parseTestLibrary(""); |
| |
| // LUB(T, T) = T |
| checkUpperBound( |
| type1: "T*", |
| type2: "T*", |
| upperBound: "T*", |
| typeParameters: "T extends List<T*>*"); |
| |
| // LUB(T, List<Bottom>) = LUB(List<Object>, List<Bottom>) = List<Object> |
| checkUpperBound( |
| type1: "T*", |
| type2: "List<Null>*", |
| upperBound: "List<Object*>*", |
| typeParameters: "T extends List<T*>*"); |
| checkUpperBound( |
| type1: "List<Null>*", |
| type2: "T*", |
| upperBound: "List<Object*>*", |
| typeParameters: "T extends List<T*>*"); |
| |
| // LUB(T, U) = LUB(List<Object>, U) = LUB(List<Object>, List<Bottom>) |
| // = List<Object> |
| checkUpperBound( |
| type1: "T*", |
| type2: "U*", |
| upperBound: "List<Object*>*", |
| typeParameters: "T extends List<T*>*, U extends List<Null>*"); |
| checkUpperBound( |
| type1: "U*", |
| type2: "T*", |
| upperBound: "List<Object*>*", |
| typeParameters: "T extends List<T*>*, U extends List<Null>*"); |
| } |
| |
| void test_lub_unknown() { |
| parseTestLibrary("class A;"); |
| checkUpperBound(type1: "A*", type2: "UNKNOWN", upperBound: "A*"); |
| checkUpperBound(type1: "UNKNOWN", type2: "A*", upperBound: "A*"); |
| } |
| |
| void test_solveTypeConstraint() { |
| parseTestLibrary(""" |
| class A; |
| class B extends A; |
| |
| class C<T extends Object*>; |
| class D<T extends Object*> extends C<T*>; |
| """); |
| |
| // Solve(? <: T <: ?) => ? |
| checkConstraintSolving("", "UNKNOWN", grounded: false); |
| |
| // Solve(? <: T <: ?, grounded) => ? |
| // Fully unconstrained variables are inferred via instantiate-to-bounds |
| // rather than constraint solving. |
| checkConstraintSolving("", "UNKNOWN", grounded: true); |
| |
| // Solve(A <: T <: ?) => A |
| checkConstraintSolving(":> A*", "A*", grounded: false); |
| |
| // Solve(A <: T <: ?, grounded) => A |
| checkConstraintSolving(":> A*", "A*", grounded: true); |
| |
| // Solve(A<?> <: T <: ?) => A<?> |
| checkConstraintSolving(":> C<UNKNOWN>*", "C<UNKNOWN>*", grounded: false); |
| |
| // Solve(A<?> <: T <: ?, grounded) => A<Null> |
| checkConstraintSolving(":> C<UNKNOWN>*", "C<Null>*", grounded: true); |
| |
| // Solve(? <: T <: A) => A |
| checkConstraintSolving("<: A*", "A*", grounded: false); |
| |
| // Solve(? <: T <: A, grounded) => A |
| checkConstraintSolving("<: A*", "A*", grounded: true); |
| |
| // Solve(? <: T <: A<?>) => A<?> |
| checkConstraintSolving("<: C<UNKNOWN>*", "C<UNKNOWN>*", grounded: false); |
| |
| // Solve(? <: T <: A<?>, grounded) => A<dynamic> |
| checkConstraintSolving("<: C<UNKNOWN>*", "C<dynamic>*", grounded: true); |
| |
| // Solve(B <: T <: A) => B |
| checkConstraintSolving(":> B* <: A*", "B*", grounded: false); |
| |
| // Solve(B <: T <: A, grounded) => B |
| checkConstraintSolving(":> B* <: A*", "B*", grounded: true); |
| |
| // Solve(B<?> <: T <: A) => A |
| checkConstraintSolving(":> D<UNKNOWN>* <: C<dynamic>*", "C<dynamic>*", |
| grounded: false); |
| |
| // Solve(B<?> <: T <: A, grounded) => A |
| checkConstraintSolving(":> D<UNKNOWN>* <: C<dynamic>*", "C<dynamic>*", |
| grounded: true); |
| |
| // Solve(B <: T <: A<?>) => B |
| checkConstraintSolving(":> D<Null>* <: C<UNKNOWN>*", "D<Null>*", |
| grounded: false); |
| |
| // Solve(B <: T <: A<?>, grounded) => B |
| checkConstraintSolving(":> D<Null>* <: C<UNKNOWN>*", "D<Null>*", |
| grounded: true); |
| |
| // Solve(B<?> <: T <: A<?>) => B<?> |
| checkConstraintSolving(":> D<UNKNOWN>* <: C<UNKNOWN>*", "D<UNKNOWN>*", |
| grounded: false); |
| |
| // Solve(B<?> <: T <: A<?>) => B<Null> |
| checkConstraintSolving(":> D<UNKNOWN>* <: C<UNKNOWN>*", "D<Null>*", |
| grounded: true); |
| } |
| |
| void test_typeConstraint_default() { |
| parseTestLibrary(""); |
| checkConstraintUpperBound(constraint: "", bound: "UNKNOWN"); |
| checkConstraintLowerBound(constraint: "", bound: "UNKNOWN"); |
| } |
| |
| void test_typeSatisfiesConstraint() { |
| parseTestLibrary(""" |
| class A; |
| class B extends A; |
| class C extends B; |
| class D extends C; |
| class E extends D; |
| """); |
| |
| checkTypeDoesntSatisfyConstraint("A*", ":> D* <: B*"); |
| checkTypeSatisfiesConstraint("B*", ":> D* <: B*"); |
| checkTypeSatisfiesConstraint("C*", ":> D* <: B*"); |
| checkTypeSatisfiesConstraint("D*", ":> D* <: B*"); |
| checkTypeDoesntSatisfyConstraint("E*", ":> D* <: B*"); |
| } |
| |
| void test_unknown_at_bottom() { |
| parseTestLibrary("class A;"); |
| checkIsLegacySubtype("UNKNOWN", "A*"); |
| } |
| |
| void test_unknown_at_top() { |
| parseTestLibrary("class A; class Map<X, Y>;"); |
| checkIsLegacySubtype("A*", "UNKNOWN"); |
| checkIsLegacySubtype("Map<A*, A*>*", "Map<UNKNOWN, UNKNOWN>*"); |
| } |
| |
| void checkConstraintSolving(String constraint, String expected, |
| {bool grounded}) { |
| assert(grounded != null); |
| expect( |
| typeSchemaEnvironment.solveTypeConstraint( |
| parseConstraint(constraint), new DynamicType(), new NullType(), |
| grounded: grounded), |
| parseType(expected)); |
| } |
| |
| void checkConstraintUpperBound({String constraint, String bound}) { |
| assert(constraint != null); |
| assert(bound != null); |
| |
| expect(parseConstraint(constraint).upper, parseType(bound)); |
| } |
| |
| void checkConstraintLowerBound({String constraint, String bound}) { |
| assert(constraint != null); |
| assert(bound != null); |
| |
| expect(parseConstraint(constraint).lower, parseType(bound)); |
| } |
| |
| void checkTypeSatisfiesConstraint(String type, String constraint) { |
| expect( |
| typeSchemaEnvironment.typeSatisfiesConstraint( |
| parseType(type), parseConstraint(constraint)), |
| isTrue); |
| } |
| |
| void checkTypeDoesntSatisfyConstraint(String type, String constraint) { |
| expect( |
| typeSchemaEnvironment.typeSatisfiesConstraint( |
| parseType(type), parseConstraint(constraint)), |
| isFalse); |
| } |
| |
| void checkIsSubtype(String subtype, String supertype) { |
| expect( |
| typeSchemaEnvironment |
| .performNullabilityAwareSubtypeCheck( |
| parseType(subtype), parseType(supertype)) |
| .isSubtypeWhenUsingNullabilities(), |
| isTrue); |
| } |
| |
| void checkIsLegacySubtype(String subtype, String supertype) { |
| expect( |
| typeSchemaEnvironment |
| .performNullabilityAwareSubtypeCheck( |
| parseType(subtype), parseType(supertype)) |
| .isSubtypeWhenIgnoringNullabilities(), |
| isTrue); |
| } |
| |
| void checkIsNotSubtype(String subtype, String supertype) { |
| expect( |
| typeSchemaEnvironment |
| .performNullabilityAwareSubtypeCheck( |
| parseType(subtype), parseType(supertype)) |
| .isSubtypeWhenIgnoringNullabilities(), |
| isFalse); |
| } |
| |
| void checkUpperBound( |
| {String type1, String type2, String upperBound, String typeParameters}) { |
| assert(type1 != null); |
| assert(type2 != null); |
| assert(upperBound != null); |
| |
| typeParserEnvironment.withTypeParameters(typeParameters, |
| (List<TypeParameter> typeParameterNodes) { |
| expect( |
| typeSchemaEnvironment.getStandardUpperBound( |
| parseType(type1), parseType(type2), testLibrary), |
| parseType(upperBound)); |
| }); |
| } |
| |
| void checkLowerBound( |
| {String type1, String type2, String lowerBound, String typeParameters}) { |
| assert(type1 != null); |
| assert(type2 != null); |
| assert(lowerBound != null); |
| |
| typeParserEnvironment.withTypeParameters(typeParameters, |
| (List<TypeParameter> typeParameterNodes) { |
| expect( |
| typeSchemaEnvironment.getStandardLowerBound( |
| parseType(type1), parseType(type2), testLibrary), |
| parseType(lowerBound)); |
| }); |
| } |
| |
| void checkInference( |
| {String typeParametersToInfer, |
| String functionType, |
| String actualParameterTypes, |
| String returnContextType, |
| String inferredTypesFromDownwardPhase, |
| String expectedTypes}) { |
| assert(typeParametersToInfer != null); |
| assert(functionType != null); |
| assert(expectedTypes != null); |
| |
| typeParserEnvironment.withTypeParameters(typeParametersToInfer, |
| (List<TypeParameter> typeParameterNodesToInfer) { |
| FunctionType functionTypeNode = parseType(functionType); |
| DartType returnContextTypeNode = |
| returnContextType == null ? null : parseType(returnContextType); |
| List<DartType> actualTypeNodes = actualParameterTypes == null |
| ? null |
| : parseTypes(actualParameterTypes); |
| List<DartType> expectedTypeNodes = |
| expectedTypes == null ? null : parseTypes(expectedTypes); |
| DartType declaredReturnTypeNode = functionTypeNode.returnType; |
| List<DartType> formalTypeNodes = actualParameterTypes == null |
| ? null |
| : functionTypeNode.positionalParameters; |
| |
| List<DartType> inferredTypeNodes; |
| if (inferredTypesFromDownwardPhase == null) { |
| inferredTypeNodes = new List<DartType>.generate( |
| typeParameterNodesToInfer.length, (_) => new UnknownType()); |
| } else { |
| inferredTypeNodes = parseTypes(inferredTypesFromDownwardPhase); |
| } |
| |
| typeSchemaEnvironment.inferGenericFunctionOrType( |
| declaredReturnTypeNode, |
| typeParameterNodesToInfer, |
| formalTypeNodes, |
| actualTypeNodes, |
| returnContextTypeNode, |
| inferredTypeNodes, |
| testLibrary); |
| |
| assert( |
| inferredTypeNodes.length == expectedTypeNodes.length, |
| "The numbers of expected types and type parameters to infer " |
| "mismatch."); |
| for (int i = 0; i < inferredTypeNodes.length; ++i) { |
| expect(inferredTypeNodes[i], expectedTypeNodes[i]); |
| } |
| }); |
| } |
| |
| void checkInferenceFromConstraints( |
| {String typeParameter, |
| String constraints, |
| String inferredTypeFromDownwardPhase, |
| bool downwardsInferPhase, |
| String expected}) { |
| assert(typeParameter != null); |
| assert(expected != null); |
| assert(downwardsInferPhase != null); |
| assert(inferredTypeFromDownwardPhase == null || !downwardsInferPhase); |
| |
| typeParserEnvironment.withTypeParameters(typeParameter, |
| (List<TypeParameter> typeParameterNodes) { |
| assert(typeParameterNodes.length == 1); |
| |
| TypeConstraint typeConstraint = parseConstraint(constraints); |
| DartType expectedTypeNode = parseType(expected); |
| TypeParameter typeParameterNode = typeParameterNodes.single; |
| List<DartType> inferredTypeNodes = <DartType>[ |
| inferredTypeFromDownwardPhase == null |
| ? new UnknownType() |
| : parseType(inferredTypeFromDownwardPhase) |
| ]; |
| |
| typeSchemaEnvironment.inferTypeFromConstraints( |
| {typeParameterNode: typeConstraint}, |
| [typeParameterNode], |
| inferredTypeNodes, |
| testLibrary, |
| downwardsInferPhase: downwardsInferPhase); |
| |
| expect(inferredTypeNodes.single, expectedTypeNode); |
| }); |
| } |
| |
| /// Parses a string like "<: T <: S >: R" into a [TypeConstraint]. |
| /// |
| /// The [constraint] string is assumed to be a sequence of bounds added to the |
| /// constraint. Each element of the sequence is either "<: T" or ":> T", |
| /// where the former adds an upper bound and the latter adds a lower bound. |
| /// The bounds are added to the constraint in the order they are mentioned in |
| /// the [constraint] string, from left to right. |
| TypeConstraint parseConstraint(String constraint) { |
| TypeConstraint result = new TypeConstraint(); |
| List<String> upperBoundSegments = constraint.split("<:"); |
| bool firstUpperBoundSegment = true; |
| for (String upperBoundSegment in upperBoundSegments) { |
| if (firstUpperBoundSegment) { |
| firstUpperBoundSegment = false; |
| if (upperBoundSegment.isEmpty) { |
| continue; |
| } |
| } |
| List<String> lowerBoundSegments = upperBoundSegment.split(":>"); |
| bool firstLowerBoundSegment = true; |
| for (String segment in lowerBoundSegments) { |
| if (firstLowerBoundSegment) { |
| firstLowerBoundSegment = false; |
| if (segment.isNotEmpty) { |
| typeSchemaEnvironment.addUpperBound( |
| result, parseType(segment), testLibrary); |
| } |
| } else { |
| typeSchemaEnvironment.addLowerBound( |
| result, parseType(segment), testLibrary); |
| } |
| } |
| } |
| return result; |
| } |
| |
| DartType parseType(String type) { |
| return typeParserEnvironment.parseType(type, |
| additionalTypes: additionalTypes); |
| } |
| |
| List<DartType> parseTypes(String types) { |
| return typeParserEnvironment.parseTypes(types, |
| additionalTypes: additionalTypes); |
| } |
| } |