blob: 56b23398f8e032395ed7fb5c52a2fc3b3b5fbeed [file] [log] [blame]
// Copyright (c) 2016, 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:test/test.dart';
import 'package:kernel/ast.dart';
import 'package:kernel/class_hierarchy.dart';
import 'package:kernel/type_environment.dart';
import 'type_parser.dart';
/// Description of a small class hierarchy for use in subtype tests.
var classEnvironment = <String, List<String>>{
'Comparable<T>': ['Object'],
'num': ['Object', 'Comparable<num>'],
'int': ['num'],
'double': ['num'],
'Iterable<T>': ['Object'],
'List<T>': ['Iterable<T>'],
};
List<TestCase> testCases = <TestCase>[
subtype('int', 'num'),
subtype('int', 'Comparable<num>'),
subtype('int', 'Comparable<Object>'),
subtype('int', 'Object'),
subtype('double', 'num'),
notSubtype('int', 'double'),
notSubtype('int', 'Comparable<int>'),
notSubtype('int', 'Iterable<int>'),
notSubtype('Comparable<int>', 'Iterable<int>'),
subtype('List<int>', 'List<int>'),
subtype('List<int>', 'Iterable<int>'),
subtype('List<int>', 'List<num>'),
subtype('List<int>', 'Iterable<num>'),
subtype('List<int>', 'List<Object>'),
subtype('List<int>', 'Iterable<Object>'),
subtype('List<int>', 'Object'),
subtype('List<int>', 'List<Comparable<Object>>'),
subtype('List<int>', 'List<Comparable<num>>'),
subtype('List<int>', 'List<Comparable<Comparable<num>>>'),
notSubtype('List<int>', 'List<double>'),
notSubtype('List<int>', 'Iterable<double>'),
notSubtype('List<int>', 'Comparable<int>'),
notSubtype('List<int>', 'List<Comparable<int>>'),
notSubtype('List<int>', 'List<Comparable<Comparable<int>>>'),
subtype('(num) => num', '(int) => num'),
subtype('(num) => int', '(num) => num'),
subtype('(num) => int', '(int) => num'),
notSubtype('(int) => int', '(num) => num'),
subtype('(num) => (num) => num', '(num) => (int) => num'),
notSubtype('(num) => (int) => int', '(num) => (num) => num'),
subtype('(x:num) => num', '(x:int) => num'), // named parameters
subtype('(num,x:num) => num', '(int,x:int) => num'),
subtype('(x:num) => int', '(x:num) => num'),
notSubtype('(x:int) => int', '(x:num) => num'),
subtype('<E>(E) => int', '<E>(E) => num'), // type parameters
subtype('<E>(num) => E', '<E>(int) => E'),
subtype('<E>(E,num) => E', '<E>(E,int) => E'),
notSubtype('<E>(E,num) => E', '<E>(E,E) => E'),
subtype('<E>(E) => (E) => E', '<F>(F) => (F) => F'),
subtype('<E>(E, (int,E) => E) => E', '<E>(E, (int,E) => E) => E'),
subtype('<E>(E, (int,E) => E) => E', '<E>(E, (num,E) => E) => E'),
notSubtype('<E,F>(E) => (F) => E', '<E>(E) => <F>(F) => E'),
notSubtype('<E,F>(E) => (F) => E', '<F,E>(E) => (F) => E'),
subtype('<E>(E,num) => E', '<E:num>(E,E) => E'),
subtype('<E:num>(E) => int', '<E:int>(E) => int'),
subtype('<E:num>(E) => E', '<E:int>(E) => E'),
subtype('<E:num>(int) => E', '<E:int>(int) => E'),
notSubtype('<E>(int) => int', '(int) => int'),
notSubtype('<E,F>(int) => int', '<E>(int) => int'),
subtype('<E:List<E>>(E) => E', '<F:List<F>>(F) => F'),
subtype('<E:Iterable<E>>(E) => E', '<F:List<F>>(F) => F'),
subtype('<E>(E,List<Object>) => E', '<F:List<F>>(F,F) => F'),
notSubtype('<E>(E,List<Object>) => List<E>', '<F:List<F>>(F,F) => F'),
notSubtype('<E>(E,List<Object>) => int', '<F:List<F>>(F,F) => F'),
subtype('<E>(E,List<Object>) => E', '<F:List<F>>(F,F) => void'),
];
/// Assert that [subtype] is a subtype of [supertype], and that [supertype]
/// is not a subtype of [subtype] (unless the two strings are equal).
TestCase subtype(String subtype_, String supertype) {
return new TestCase(subtype_, supertype, isSubtype: true);
}
/// Assert that neither type is a subtype of the other.
TestCase notSubtype(String subtype_, String supertype) {
return new TestCase(subtype_, supertype, isSubtype: false);
}
class TestCase {
String subtype;
String supertype;
bool isSubtype;
TestCase(this.subtype, this.supertype, {this.isSubtype});
String toString() =>
isSubtype ? '$subtype <: $supertype' : '$subtype </: $supertype';
}
class MockSubtypeTester extends SubtypeTester {
ClassHierarchy hierarchy;
InterfaceType objectType;
InterfaceType rawFunctionType;
LazyTypeEnvironment environment;
MockSubtypeTester(
this.hierarchy, this.objectType, this.rawFunctionType, this.environment);
}
MockSubtypeTester makeSubtypeTester(Map<String, List<String>> testcase) {
LazyTypeEnvironment environment = new LazyTypeEnvironment();
Class objectClass = environment.lookup('Object');
Class functionClass = environment.lookup('Function');
functionClass.supertype = objectClass.asRawSupertype;
for (var typeString in testcase.keys) {
InterfaceType type = environment.parseFresh(typeString);
Class class_ = type.classNode;
for (TypeParameterType typeArg in type.typeArguments) {
class_.typeParameters.add(typeArg.parameter);
}
for (var supertypeString in testcase[typeString]) {
if (class_.supertype == null) {
class_.supertype = environment.parseSuper(supertypeString);
} else {
class_.implementedTypes.add(environment.parseSuper(supertypeString));
}
}
}
var program = new Program([environment.dummyLibrary]);
var hierarchy = new ClassHierarchy(program);
return new MockSubtypeTester(
hierarchy, objectClass.rawType, functionClass.rawType, environment);
}
main() {
var tester = makeSubtypeTester(classEnvironment);
var environment = tester.environment;
for (var testCase in testCases) {
test('$testCase', () {
var subtype = environment.parseFresh(testCase.subtype);
var supertype = environment.parseFresh(testCase.supertype);
if (tester.isSubtypeOf(subtype, supertype) != testCase.isSubtype) {
fail('isSubtypeOf(${testCase.subtype}, ${testCase.supertype}) returned '
'${!testCase.isSubtype} but should return ${testCase.isSubtype}');
}
if (subtype != supertype && tester.isSubtypeOf(supertype, subtype)) {
fail('isSubtypeOf(${testCase.supertype}, ${testCase.subtype}) returned '
'true but should return false');
}
});
}
}