blob: b988507249b9b23570d9c180e70493e18f41f8cf [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:kernel/kernel.dart';
import 'type_parser.dart';
import 'package:test/test.dart';
final List<TestCase> testCases = <TestCase>[
successCase('List<T>', 'List<String>', {'T': 'String'}),
successCase('List<String>', 'List<T>', {'T': 'String'}),
successCase('List<T>', 'List<T>', {'T': null}),
successCase('List<S>', 'List<T>', {'S': 'T'}),
successCase('List<S>', 'List<T>', {'T': 'S'}),
successCase(
'List<S>', 'List<T>', {'S': 'T', 'T': null}), // Require left bias.
failureCase('List<S>', 'List<T>', []),
failureCase('List<T>', 'T', ['T']),
failureCase('List<List<T>>', 'List<T>', ['T']),
failureCase('Map<S, T>', 'Map<List<T>, List<S>>', ['T', 'S']),
failureCase('Map<Map<S,String>, Map<int,S>>',
'Map<Map<int, S>, Map<S, String>>', ['S']),
successCase('Map<Map<S, int>, Map<int, S>>', 'Map<Map<int, S>, Map<S, int>>',
{'S': 'int'}),
successCase('Map<Map<S, String>, Map<int, T>>',
'Map<Map<int, T>, Map<S, String>>', {'S': 'int', 'T': 'String'}),
successCase('Map<S, List<T>>', 'Map<T, List<S>>', {'S': 'T'}),
successCase('Map<S, T>', 'Map<S, List<S>>', {'T': 'List<S>'}),
successCase('Map<S, T>', 'Map<S, List<S>>', {'T': 'List<S>', 'S': null}),
successCase('Map<List<S>, T>', 'Map<T, List<S>>', {'T': 'List<S>'}),
successCase(
'Map<List<S>, T>', 'Map<T, List<S>>', {'T': 'List<S>', 'S': null}),
successCase('<E>(E) => E', '<T>(T) => T', {}),
successCase('<E>(E, S) => E', '<T>(T, int) => T', {'S': 'int'}),
failureCase('<E>(E, S) => E', '<T>(T, T) => T', ['S']),
successCase(
'<E>(E) => <T>(T) => Map<E,T>', '<E>(E) => <T>(T) => Map<E,T>', {}),
successCase('<E>(E,_) => E', '<T>(T,_) => T', {}),
successCase('(x:int,y:String) => int', '(y:String,x:int) => int', {}),
successCase('<S,T>(x:S,y:T) => S', '<S,T>(y:T,x:S) => S', {}),
successCase('(x:<T>(T)=>T,y:<S>(S)=>S) => int',
'(y:<S>(S)=>S,x:<T>(T)=>T) => int', {}),
successCase('(x:<T>(T)=>T,y:<S>(S,S,S)=>S) => int',
'(y:<S>(S,S,S)=>S,x:<T>(T)=>T) => int', {}),
];
class TestCase {
String type1;
String type2;
Iterable<String> quantifiedVariables;
Map<String, String> expectedSubstitution; // Null if unification should fail.
TestCase.success(this.type1, this.type2, this.expectedSubstitution) {
quantifiedVariables = expectedSubstitution.keys;
}
TestCase.fail(this.type1, this.type2, this.quantifiedVariables);
bool get shouldSucceed => expectedSubstitution != null;
String toString() => '∃ ${quantifiedVariables.join(',')}. $type1 = $type2';
}
TestCase successCase(String type1, String type2, Map<String, String> expected,
{bool debug: false}) {
return new TestCase.success(type1, type2, expected);
}
TestCase failureCase(
String type1, String type2, List<String> quantifiedVariables,
{bool debug: false}) {
return new TestCase.fail(type1, type2, quantifiedVariables);
}
void checkHashCodeEquality(DartType type1, DartType type2) {
if (type1 == type2 && type1.hashCode != type2.hashCode) {
fail('Equal types with different hash codes: $type1 and $type2');
}
}
const int MinimumSmi = -(1 << 30);
const int MaximumSmi = (1 << 30) - 1;
bool isSmallInteger(int hash) {
return MinimumSmi <= hash && hash <= MaximumSmi;
}
void checkHashCodeRange(DartType type) {
int hash = type.hashCode;
if (!isSmallInteger(hash)) {
fail('Hash code for $type is not a SMI: $hash');
}
}
void main() {
for (var testCase in testCases) {
test('$testCase', () {
var env = new LazyTypeEnvironment();
var type1 = env.parse(testCase.type1);
var type2 = env.parse(testCase.type2);
checkHashCodeEquality(type1, type2);
checkHashCodeRange(type1);
checkHashCodeRange(type2);
});
}
}