blob: ebdb227d69cdd94ec5a8e5d9b8564b1319fd746d [file] [log] [blame]
// 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.
import 'package:front_end/src/fasta/type_inference/type_constraint_gatherer.dart';
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/mock_sdk_component.dart';
import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
main() {
defineReflectiveSuite(() {
defineReflectiveTests(TypeConstraintGathererTest);
});
}
@reflectiveTest
class TypeConstraintGathererTest {
static const UnknownType unknownType = const UnknownType();
static const DynamicType dynamicType = const DynamicType();
static const VoidType voidType = const VoidType();
final testLib =
new Library(Uri.parse('org-dartlang:///test.dart'), name: 'lib');
Component component;
CoreTypes coreTypes;
TypeParameterType T1;
TypeParameterType T2;
Class classP;
Class classQ;
TypeConstraintGathererTest() {
component = createMockSdkComponent();
component.libraries.add(testLib..parent = component);
coreTypes = new CoreTypes(component);
T1 = new TypeParameterType(
new TypeParameter('T1', objectType), Nullability.legacy);
T2 = new TypeParameterType(
new TypeParameter('T2', objectType), Nullability.legacy);
classP = _addClass(_class('P'));
classQ = _addClass(_class('Q'));
}
Class get functionClass => coreTypes.functionClass;
InterfaceType get functionType => coreTypes.functionLegacyRawType;
Class get iterableClass => coreTypes.iterableClass;
Class get listClass => coreTypes.listClass;
Class get mapClass => coreTypes.mapClass;
Class get objectClass => coreTypes.objectClass;
InterfaceType get objectType => coreTypes.objectLegacyRawType;
InterfaceType get P => coreTypes.legacyRawType(classP);
InterfaceType get Q => coreTypes.legacyRawType(classQ);
void test_any_subtype_parameter() {
_checkConstraintsLower(T1, Q, testLib, ['lib::Q* <: T1']);
}
void test_any_subtype_top() {
_checkConstraintsUpper(P, dynamicType, testLib, []);
_checkConstraintsUpper(P, objectType, testLib, []);
_checkConstraintsUpper(P, voidType, testLib, []);
}
void test_any_subtype_unknown() {
_checkConstraintsUpper(P, unknownType, testLib, []);
_checkConstraintsUpper(T1, unknownType, testLib, []);
}
void test_different_classes() {
_checkConstraintsUpper(_list(T1), _iterable(Q), testLib, ['T1 <: lib::Q*']);
_checkConstraintsUpper(_iterable(T1), _list(Q), testLib, null);
}
void test_equal_types() {
_checkConstraintsUpper(P, P, testLib, []);
}
void test_function_generic() {
var T = new TypeParameterType(
new TypeParameter('T', objectType), Nullability.legacy);
var U = new TypeParameterType(
new TypeParameter('U', objectType), Nullability.legacy);
// <T>() -> dynamic <: () -> dynamic, never
_checkConstraintsUpper(
new FunctionType([], dynamicType, Nullability.legacy,
typeParameters: [T.parameter]),
new FunctionType([], dynamicType, Nullability.legacy),
testLib,
null);
// () -> dynamic <: <T>() -> dynamic, never
_checkConstraintsUpper(
new FunctionType([], dynamicType, Nullability.legacy),
new FunctionType([], dynamicType, Nullability.legacy,
typeParameters: [T.parameter]),
testLib,
null);
// <T>(T) -> T <: <U>(U) -> U, always
_checkConstraintsUpper(
new FunctionType([T], T, Nullability.legacy,
typeParameters: [T.parameter]),
new FunctionType([U], U, Nullability.legacy,
typeParameters: [U.parameter]),
testLib,
[]);
}
void test_function_parameter_mismatch() {
// (P) -> dynamic <: () -> dynamic, never
_checkConstraintsUpper(
new FunctionType([P], dynamicType, Nullability.legacy),
new FunctionType([], dynamicType, Nullability.legacy),
testLib,
null);
// () -> dynamic <: (P) -> dynamic, never
_checkConstraintsUpper(
new FunctionType([], dynamicType, Nullability.legacy),
new FunctionType([P], dynamicType, Nullability.legacy),
testLib,
null);
// ([P]) -> dynamic <: () -> dynamic, always
_checkConstraintsUpper(
new FunctionType([P], dynamicType, Nullability.legacy,
requiredParameterCount: 0),
new FunctionType([], dynamicType, Nullability.legacy),
testLib,
[]);
// () -> dynamic <: ([P]) -> dynamic, never
_checkConstraintsUpper(
new FunctionType([], dynamicType, Nullability.legacy),
new FunctionType([P], dynamicType, Nullability.legacy,
requiredParameterCount: 0),
testLib,
null);
// ({x: P}) -> dynamic <: () -> dynamic, always
_checkConstraintsUpper(
new FunctionType([], dynamicType, Nullability.legacy,
namedParameters: [new NamedType('x', P)]),
new FunctionType([], dynamicType, Nullability.legacy),
testLib,
[]);
// () -> dynamic !<: ({x: P}) -> dynamic, never
_checkConstraintsUpper(
new FunctionType([], dynamicType, Nullability.legacy),
new FunctionType([], dynamicType, Nullability.legacy,
namedParameters: [new NamedType('x', P)]),
testLib,
null);
}
void test_function_parameter_types() {
// (T1) -> dynamic <: (Q) -> dynamic, under constraint Q <: T1
_checkConstraintsUpper(
new FunctionType([T1], dynamicType, Nullability.legacy),
new FunctionType([Q], dynamicType, Nullability.legacy),
testLib,
['lib::Q* <: T1']);
// ({x: T1}) -> dynamic <: ({x: Q}) -> dynamic, under constraint Q <: T1
_checkConstraintsUpper(
new FunctionType([], dynamicType, Nullability.legacy,
namedParameters: [new NamedType('x', T1)]),
new FunctionType([], dynamicType, Nullability.legacy,
namedParameters: [new NamedType('x', Q)]),
testLib,
['lib::Q* <: T1']);
}
void test_function_return_type() {
// () -> T1 <: () -> Q, under constraint T1 <: Q
_checkConstraintsUpper(
new FunctionType([], T1, Nullability.legacy),
new FunctionType([], Q, Nullability.legacy),
testLib,
['T1 <: lib::Q*']);
// () -> P <: () -> void, always
_checkConstraintsUpper(new FunctionType([], P, Nullability.legacy),
new FunctionType([], voidType, Nullability.legacy), testLib, []);
// () -> void <: () -> P, never
_checkConstraintsUpper(new FunctionType([], voidType, Nullability.legacy),
new FunctionType([], P, Nullability.legacy), testLib, null);
}
void test_function_trivial_cases() {
var F = new FunctionType([], dynamicType, Nullability.legacy);
// () -> dynamic <: dynamic, always
_checkConstraintsUpper(F, dynamicType, testLib, []);
// () -> dynamic <: Function, always
_checkConstraintsUpper(F, functionType, testLib, []);
// () -> dynamic <: Object, always
_checkConstraintsUpper(F, objectType, testLib, []);
}
void test_nonInferredParameter_subtype_any() {
var U = new TypeParameterType(
new TypeParameter('U', _list(P)), Nullability.legacy);
_checkConstraintsLower(_list(T1), U, testLib, ['lib::P* <: T1']);
}
void test_null_subtype_any() {
_checkConstraintsLower(T1, new NullType(), testLib, ['Null <: T1']);
_checkConstraintsUpper(new NullType(), Q, testLib, []);
}
void test_parameter_subtype_any() {
_checkConstraintsUpper(T1, Q, testLib, ['T1 <: lib::Q*']);
}
void test_same_classes() {
_checkConstraintsUpper(_list(T1), _list(Q), testLib, ['T1 <: lib::Q*']);
}
void test_typeParameters() {
_checkConstraintsUpper(
_map(T1, T2), _map(P, Q), testLib, ['T1 <: lib::P*', 'T2 <: lib::Q*']);
}
void test_unknown_subtype_any() {
_checkConstraintsUpper(Q, unknownType, testLib, []);
_checkConstraintsUpper(T1, unknownType, testLib, []);
}
Class _addClass(Class c) {
testLib.addClass(c);
return c;
}
void _checkConstraintsLower(DartType type, DartType bound,
Library clientLibrary, List<String> expectedConstraints) {
_checkConstraintsHelper(type, bound, clientLibrary, expectedConstraints,
(gatherer, type, bound) => gatherer.tryConstrainLower(type, bound));
}
void _checkConstraintsUpper(DartType type, DartType bound,
Library clientLibrary, List<String> expectedConstraints) {
_checkConstraintsHelper(type, bound, clientLibrary, expectedConstraints,
(gatherer, type, bound) => gatherer.tryConstrainUpper(type, bound));
}
void _checkConstraintsHelper(
DartType a,
DartType b,
Library clientLibrary,
List<String> expectedConstraints,
bool Function(TypeConstraintGatherer, DartType, DartType) tryConstrain) {
var typeSchemaEnvironment = new TypeSchemaEnvironment(
coreTypes, new ClassHierarchy(component, coreTypes));
var typeConstraintGatherer = new TypeConstraintGatherer(
typeSchemaEnvironment, [T1.parameter, T2.parameter], testLib);
var constraints = tryConstrain(typeConstraintGatherer, a, b)
? typeConstraintGatherer.computeConstraints(clientLibrary)
: null;
if (expectedConstraints == null) {
expect(constraints, isNull);
return;
}
expect(constraints, isNotNull);
var constraintStrings = <String>[];
constraints.forEach((t, constraint) {
if (constraint.lower is! UnknownType ||
constraint.upper is! UnknownType) {
var s = t.name;
if (constraint.lower is! UnknownType) {
s = '${typeSchemaToString(constraint.lower)} <: $s';
}
if (constraint.upper is! UnknownType) {
s = '$s <: ${typeSchemaToString(constraint.upper)}';
}
constraintStrings.add(s);
}
});
expect(constraintStrings, unorderedEquals(expectedConstraints));
}
Class _class(String name,
{Supertype supertype,
List<TypeParameter> typeParameters,
List<Supertype> implementedTypes}) {
return new Class(
name: name,
supertype: supertype ?? objectClass.asThisSupertype,
typeParameters: typeParameters,
implementedTypes: implementedTypes);
}
DartType _iterable(DartType element) =>
new InterfaceType(iterableClass, Nullability.legacy, [element]);
DartType _list(DartType element) =>
new InterfaceType(listClass, Nullability.legacy, [element]);
DartType _map(DartType key, DartType value) =>
new InterfaceType(mapClass, Nullability.legacy, [key, value]);
}