Implement Dart 1.0 LUB algorithm (for interface types) in kernel.

Note: I intend to implement the full LUB algorithm (including strong
mode behaviors) in front_end, however this piece of the algorithm
makes sense to be in kernel so that it can take advantage of
_ClassInfo.

R=ahe@google.com, johnniwinther@google.com

Review-Url: https://codereview.chromium.org/2848083002 .
This commit is contained in:
Paul Berry 2017-05-01 11:25:45 -07:00
parent c305d183c3
commit 29570728a3
5 changed files with 586 additions and 5 deletions

View file

@ -6,6 +6,7 @@ library kernel.class_hierarchy;
import 'ast.dart';
import 'dart:math';
import 'dart:typed_data';
import 'src/heap.dart';
import 'type_algebra.dart';
/// Data structure for answering various subclassing queries.
@ -62,6 +63,155 @@ class ClassHierarchy {
return _infoFor[class_].directImplementers.isNotEmpty;
}
/// Returns the number of steps in the longest inheritance path from [class_]
/// to [rootClass].
int getClassDepth(Class class_) => _infoFor[class_].depth;
/// Returns a list of classes appropriate for use in calculating a least upper
/// bound.
///
/// The returned list is a list of all classes that [class_] is a subtype of
/// (including itself), sorted first by depth (deepest first) and then by
/// class index.
List<Class> getRankedSuperclasses(Class class_) {
return _getRankedSuperclassInfos(_infoFor[class_])
.map((info) => info.classNode)
.toList();
}
List<_ClassInfo> _getRankedSuperclassInfos(_ClassInfo info) {
if (info.leastUpperBoundInfos != null) return info.leastUpperBoundInfos;
var heap = new _LubHeap()..add(info);
var chain = <_ClassInfo>[];
info.leastUpperBoundInfos = chain;
_ClassInfo lastInfo = null;
while (heap.isNotEmpty) {
var nextInfo = heap.remove();
if (identical(nextInfo, lastInfo)) continue;
chain.add(nextInfo);
lastInfo = nextInfo;
var classNode = nextInfo.classNode;
void addToHeap(Supertype supertype) {
heap.add(_infoFor[supertype.classNode]);
}
if (classNode.supertype != null) addToHeap(classNode.supertype);
if (classNode.mixedInType != null) addToHeap(classNode.mixedInType);
classNode.implementedTypes.forEach(addToHeap);
}
return chain;
}
/// Returns the least upper bound of two interface types, as defined by Dart
/// 1.0.
///
/// Given two interfaces I and J, let S_I be the set of superinterfaces of I,
/// let S_J be the set of superinterfaces of J, and let
/// S = (I union S_I) intersect (J union S_J). Furthermore, we define
/// S_n = {T | T in S and depth(T) = n} for any finite n where depth(T) is
/// the number of steps in the longest inheritance path from T to Object. Let
/// q be the largest number such that S_q has cardinality one. The least
/// upper bound of I and J is the sole element of S_q.
///
/// This is called the "classic" least upper bound to distinguish it from the
/// strong mode least upper bound, which has special behaviors in the case
/// where one type is a subtype of the other, or where both types are based on
/// the same class.
InterfaceType getClassicLeastUpperBound(
InterfaceType type1, InterfaceType type2) {
// The algorithm is: first we compute a list of superclasses for both types,
// ordered from greatest to least depth, and ordered by topological sort
// index within each depth. Due to the sort order, we can find the
// intersection of these lists by a simple walk.
//
// Then, for each class in the intersection, determine the exact type that
// is implemented by type1 and type2. If the types match, that type is a
// candidate (it's a member of S_n). As soon as we find a candidate which
// is unique for its depth, we return it.
//
// As an optimization, if the class for I is a subtype of the class for J,
// then we know that the list of superclasses of J is a subset of the list
// of superclasses for I; therefore it is sufficient to compute just the
// list of superclasses for J. To avoid complicating the code below (which
// intersects the two lists), we set both lists equal to the list of
// superclasses for J. And vice versa with the role of I and J swapped.
// Compute the list of superclasses for both types, with the above
// optimization.
_ClassInfo info1 = _infoFor[type1.classNode];
_ClassInfo info2 = _infoFor[type2.classNode];
List<_ClassInfo> classes1;
List<_ClassInfo> classes2;
if (identical(info1, info2) || info1.isSubtypeOf(info2)) {
classes1 = classes2 = _getRankedSuperclassInfos(info2);
} else if (info2.isSubtypeOf(info1)) {
classes1 = classes2 = _getRankedSuperclassInfos(info1);
} else {
classes1 = _getRankedSuperclassInfos(info1);
classes2 = _getRankedSuperclassInfos(info2);
}
// Walk the lists finding their intersection, looking for a depth that has a
// single candidate.
int i1 = 0;
int i2 = 0;
InterfaceType candidate = null;
int currentDepth = -1;
int numCandidatesAtThisDepth = 0;
while (true) {
_ClassInfo next = classes1[i1];
_ClassInfo next2 = classes2[i2];
if (!identical(next, next2)) {
if (_LubHeap.sortsBeforeStatic(next, next2)) {
++i1;
} else {
++i2;
}
continue;
}
++i2;
++i1;
if (next.depth != currentDepth) {
if (numCandidatesAtThisDepth == 1) return candidate;
currentDepth = next.depth;
numCandidatesAtThisDepth = 0;
candidate = null;
} else if (numCandidatesAtThisDepth > 1) {
continue;
}
// For each class in the intersection, find the exact type that is
// implemented by type1 and type2. If they match, it's a candidate.
//
// Two additional optimizations:
//
// - If this class lacks type parameters, we know there is a match without
// needing to substitute.
//
// - If the depth is 0, we have reached Object, so we can return it
// immediately. Since all interface types are subtypes of Object, this
// ensures the loop terminates.
if (next.classNode.typeParameters.isEmpty) {
candidate = next.classNode.rawType;
if (currentDepth == 0) return candidate;
++numCandidatesAtThisDepth;
} else {
var superType1 = identical(info1, next)
? type1
: Substitution.fromInterfaceType(type1).substituteType(
info1.genericSuperTypes[next.classNode].asInterfaceType);
var superType2 = identical(info2, next)
? type2
: Substitution.fromInterfaceType(type2).substituteType(
info2.genericSuperTypes[next.classNode].asInterfaceType);
if (superType1 == superType2) {
candidate = superType1;
++numCandidatesAtThisDepth;
}
}
}
}
/// Returns the instantiation of [superclass] that is implemented by [class_],
/// or `null` if [class_] does not implement [superclass] at all.
Supertype getClassAsInstanceOf(Class class_, Class superclass) {
@ -286,27 +436,33 @@ class ClassHierarchy {
/// Upwards traversal of the class hierarchy that orders classes so super
/// types before their subtypes.
///
/// Returns the depth of the visited class (the number of steps in the longest
/// inheritance path to the root class).
int _topSortIndex = 0;
void _topologicalSortVisit(Class classNode) {
int _topologicalSortVisit(Class classNode) {
var info = _infoFor[classNode];
if (info != null) {
if (info.isBeingVisited) {
throw 'Cyclic inheritance involving ${info.classNode.name}';
}
return; // Already built.
return info.depth; // Already built.
}
int superDepth = -1;
_infoFor[classNode] = info = new _ClassInfo(classNode);
info.isBeingVisited = true;
if (classNode.supertype != null) {
_topologicalSortVisit(classNode.supertype.classNode);
superDepth =
max(superDepth, _topologicalSortVisit(classNode.supertype.classNode));
_recordSuperTypes(info, classNode.supertype);
}
if (classNode.mixedInType != null) {
_topologicalSortVisit(classNode.mixedInType.classNode);
superDepth = max(
superDepth, _topologicalSortVisit(classNode.mixedInType.classNode));
_recordSuperTypes(info, classNode.mixedInType);
}
for (var supertype in classNode.implementedTypes) {
_topologicalSortVisit(supertype.classNode);
superDepth = max(superDepth, _topologicalSortVisit(supertype.classNode));
_recordSuperTypes(info, supertype);
}
_buildDeclaredMembers(classNode, info);
@ -315,6 +471,7 @@ class ClassHierarchy {
info.topologicalIndex = id;
classes[id] = info.classNode;
info.isBeingVisited = false;
return info.depth = superDepth + 1;
}
void _buildDeclaredMembers(Class classNode, _ClassInfo info) {
@ -791,6 +948,7 @@ class _ClassInfo {
int topologicalIndex = 0;
int topDownIndex = -1;
bool isBeingVisited = false;
int depth = 0;
// Super types must always occur before subtypes in these lists.
// For example:
@ -810,6 +968,8 @@ class _ClassInfo {
Uint32List submixtureIntervalList;
Uint32List subtypeIntervalList;
List<_ClassInfo> leastUpperBoundInfos;
bool isSubclassOf(_ClassInfo other) {
return _intervalListContains(other.subclassIntervalList, topDownIndex);
}
@ -900,3 +1060,19 @@ class ClassSet {
return new ClassSet(_hierarchy, builder.buildIntervalList());
}
}
/// Heap for use in computing least upper bounds.
///
/// The heap is sorted such that classes that are deepest in the hierarchy
/// are removed first; in the case of ties, classes with lower topological sort
/// index are removed first.
class _LubHeap extends Heap<_ClassInfo> {
@override
bool sortsBefore(_ClassInfo a, _ClassInfo b) => sortsBeforeStatic(a, b);
static bool sortsBeforeStatic(_ClassInfo a, _ClassInfo b) {
if (a.depth > b.depth) return true;
if (a.depth < b.depth) return false;
return a.topologicalIndex < b.topologicalIndex;
}
}

View file

@ -0,0 +1,62 @@
// 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.
/// Basic implementation of a heap, with O(log n) insertion and removal.
abstract class Heap<T> {
final _items = <T>[];
bool get isEmpty => _items.isEmpty;
bool get isNotEmpty => _items.isNotEmpty;
void add(T item) {
int index = _items.length;
_items.length += 1;
while (index > 0) {
T parent = _items[_parentIndex(index)];
if (sortsBefore(parent, item)) break;
_items[index] = parent;
index = _parentIndex(index);
}
_items[index] = item;
}
T remove() {
T removed = _items[0];
T orphan = _items.removeLast();
if (_items.isNotEmpty) _reInsert(orphan);
return removed;
}
/// Client should use a derived class to specify the sort order.
bool sortsBefore(T a, T b);
int _firstChildIndex(int index) {
return (index << 1) + 1;
}
int _parentIndex(int index) {
return (index - 1) >> 1;
}
void _reInsert(T item) {
int index = 0;
while (true) {
int childIndex = _firstChildIndex(index);
if (childIndex >= _items.length) break;
T child = _items[childIndex];
if (childIndex + 1 < _items.length) {
T nextChild = _items[childIndex + 1];
if (sortsBefore(nextChild, child)) {
child = nextChild;
childIndex++;
}
}
if (sortsBefore(item, child)) break;
_items[index] = _items[childIndex];
index = childIndex;
}
_items[index] = item;
}
}

View file

@ -0,0 +1,66 @@
// 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:kernel/ast.dart';
/// Returns a [Program] object containing empty definitions of core SDK classes.
Program createMockSdkProgram() {
var coreLib = new Library(Uri.parse('dart:core'));
var asyncLib = new Library(Uri.parse('dart:async'));
var internalLib = new Library(Uri.parse('dart:_internal'));
Class addClass(Library lib, Class c) {
lib.addClass(c);
return c;
}
var objectClass = addClass(coreLib, new Class(name: 'Object'));
var objectType = objectClass.rawType;
TypeParameter typeParam(String name, [DartType bound]) {
return new TypeParameter(name, bound ?? objectType);
}
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);
}
addClass(coreLib, class_('Null'));
addClass(coreLib, class_('bool'));
var num = addClass(coreLib, class_('num'));
addClass(coreLib, class_('String'));
var iterable =
addClass(coreLib, class_('Iterable', typeParameters: [typeParam('T')]));
{
var T = typeParam('T');
addClass(
coreLib,
class_('List', typeParameters: [
T
], implementedTypes: [
new Supertype(iterable, [new TypeParameterType(T)])
]));
}
addClass(
coreLib, class_('Map', typeParameters: [typeParam('K'), typeParam('V')]));
addClass(coreLib, class_('int', supertype: num.asThisSupertype));
addClass(coreLib, class_('double', supertype: num.asThisSupertype));
addClass(coreLib, class_('Iterator', typeParameters: [typeParam('T')]));
addClass(coreLib, class_('Symbol'));
addClass(coreLib, class_('Type'));
addClass(coreLib, class_('Function'));
addClass(coreLib, class_('Invocation'));
addClass(asyncLib, class_('Future', typeParameters: [typeParam('T')]));
addClass(asyncLib, class_('Stream', typeParameters: [typeParam('T')]));
addClass(internalLib, class_('Symbol'));
return new Program([coreLib, asyncLib, internalLib]);
}

View file

@ -0,0 +1,38 @@
// 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:kernel/src/heap.dart';
import 'package:test/test.dart';
main() {
check_sort(Iterable<int> initialOrder) {
var values = initialOrder.toList();
var heap = new _intHeap();
values.forEach(heap.add);
values.sort();
var result = <int>[];
while (heap.isNotEmpty) {
expect(heap.isEmpty, isFalse);
result.add(heap.remove());
}
expect(heap.isEmpty, isTrue);
expect(result, values);
}
test('sort', () {
check_sort(<int>[3, 1, 4, 1, 5, 9, 2, 6]);
});
test('sort_already_sorted', () {
check_sort(<int>[1, 1, 2, 3, 4, 5, 6, 9]);
});
test('sort_reverse_sorted', () {
check_sort(<int>[9, 6, 5, 4, 3, 2, 1, 1]);
});
}
class _intHeap extends Heap<int> {
bool sortsBefore(int a, int b) => a < b;
}

View file

@ -0,0 +1,239 @@
// 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:kernel/ast.dart';
import 'package:kernel/class_hierarchy.dart';
import 'package:kernel/core_types.dart';
import 'package:kernel/testing/mock_sdk_program.dart';
import 'package:test/test.dart';
main() {
Library makeTestLibrary(Program program) {
var library = new Library(Uri.parse('org-dartlang:///test.dart'))
..parent = program;
program.libraries.add(library);
return library;
}
test('depth', () {
var program = createMockSdkProgram();
var coreTypes = new CoreTypes(program);
var defaultSuper = coreTypes.objectClass.asThisSupertype;
var library = makeTestLibrary(program);
Class addClass(Class c) {
library.addClass(c);
return c;
}
var base = addClass(new Class(name: 'base', supertype: defaultSuper));
var extends_ =
addClass(new Class(name: 'extends_', supertype: base.asThisSupertype));
var with_ = addClass(new Class(
name: 'with_',
supertype: defaultSuper,
mixedInType: base.asThisSupertype));
var implements_ = addClass(new Class(
name: 'implements_',
supertype: defaultSuper,
implementedTypes: [base.asThisSupertype]));
var hierarchy = new ClassHierarchy(program);
expect(hierarchy.getClassDepth(coreTypes.objectClass), 0);
expect(hierarchy.getClassDepth(base), 1);
expect(hierarchy.getClassDepth(extends_), 2);
expect(hierarchy.getClassDepth(with_), 2);
expect(hierarchy.getClassDepth(implements_), 2);
});
test('ranked_superclasses', () {
var program = createMockSdkProgram();
var coreTypes = new CoreTypes(program);
var defaultSuper = coreTypes.objectClass.asThisSupertype;
var library = makeTestLibrary(program);
Class addClass(String name, List<Class> implements_) {
var c = new Class(
name: name,
supertype: defaultSuper,
implementedTypes: implements_.map((c) => c.asThisSupertype).toList());
library.addClass(c);
return c;
}
// Create the class hierarchy:
//
// Object
// |
// A
// / \
// B C
// | |
// | D
// \ /
// E
var a = addClass('A', []);
var b = addClass('B', [a]);
var c = addClass('C', [a]);
var d = addClass('D', [c]);
var e = addClass('E', [b, d]);
var hierarchy = new ClassHierarchy(program);
expect(hierarchy.getRankedSuperclasses(a), [a, coreTypes.objectClass]);
expect(hierarchy.getRankedSuperclasses(b), [b, a, coreTypes.objectClass]);
expect(hierarchy.getRankedSuperclasses(c), [c, a, coreTypes.objectClass]);
expect(
hierarchy.getRankedSuperclasses(d), [d, c, a, coreTypes.objectClass]);
if (hierarchy.getClassIndex(b) < hierarchy.getClassIndex(c)) {
expect(hierarchy.getRankedSuperclasses(e),
[e, d, b, c, a, coreTypes.objectClass]);
} else {
expect(hierarchy.getRankedSuperclasses(e),
[e, d, c, b, a, coreTypes.objectClass]);
}
});
test('least_upper_bound_non_generic', () {
var program = createMockSdkProgram();
var coreTypes = new CoreTypes(program);
var defaultSuper = coreTypes.objectClass.asThisSupertype;
var library = makeTestLibrary(program);
Class addClass(String name, List<Class> implements_) {
var c = new Class(
name: name,
supertype: defaultSuper,
implementedTypes: implements_.map((c) => c.asThisSupertype).toList());
library.addClass(c);
return c;
}
// Create the class hierarchy:
//
// Object
// / \
// A B
// /|\
// C D E
// |X|/
// FG HI
//
// (F and G both implement (C, D); H and I both implement (C, D, E).
var a = addClass('A', []);
var b = addClass('B', []);
var c = addClass('C', [a]);
var d = addClass('D', [a]);
var e = addClass('E', [a]);
var f = addClass('F', [c, d]);
var g = addClass('G', [c, d]);
var h = addClass('H', [c, d, e]);
var i = addClass('I', [c, d, e]);
var hierarchy = new ClassHierarchy(program);
expect(hierarchy.getClassicLeastUpperBound(a.rawType, b.rawType),
coreTypes.objectClass.rawType);
expect(
hierarchy.getClassicLeastUpperBound(
a.rawType, coreTypes.objectClass.rawType),
coreTypes.objectClass.rawType);
expect(
hierarchy.getClassicLeastUpperBound(
coreTypes.objectClass.rawType, b.rawType),
coreTypes.objectClass.rawType);
expect(
hierarchy.getClassicLeastUpperBound(c.rawType, d.rawType), a.rawType);
expect(
hierarchy.getClassicLeastUpperBound(c.rawType, a.rawType), a.rawType);
expect(
hierarchy.getClassicLeastUpperBound(a.rawType, d.rawType), a.rawType);
expect(
hierarchy.getClassicLeastUpperBound(f.rawType, g.rawType), a.rawType);
expect(
hierarchy.getClassicLeastUpperBound(h.rawType, i.rawType), a.rawType);
});
test('least_upper_bound_non_generic', () {
var program = createMockSdkProgram();
var coreTypes = new CoreTypes(program);
var defaultSuper = coreTypes.objectClass.asThisSupertype;
var library = makeTestLibrary(program);
var int = coreTypes.intClass.rawType;
var double = coreTypes.doubleClass.rawType;
var bool = coreTypes.boolClass.rawType;
Class addClass(String name, List<String> typeParameterNames,
List<Supertype> implements_(List<DartType> typeParameterTypes)) {
var typeParameters = typeParameterNames
.map((name) => new TypeParameter(name, coreTypes.objectClass.rawType))
.toList();
var typeParameterTypes = typeParameters
.map((parameter) => new TypeParameterType(parameter))
.toList();
var c = new Class(
name: name,
typeParameters: typeParameters,
supertype: defaultSuper,
implementedTypes: implements_(typeParameterTypes));
library.addClass(c);
return c;
}
// Create the class hierarchy:
//
// Object
// |
// A
// / \
// B<T> C<U>
// \ /
// D<T,U>
// / \
// E F
//
// Where E implements D<int, double> and F implements D<int, bool>.
var a = addClass('A', [], (_) => []);
var b = addClass('B', ['T'], (_) => [a.asThisSupertype]);
var c = addClass('C', ['U'], (_) => [a.asThisSupertype]);
var d = addClass('D', ['T', 'U'], (typeParameterTypes) {
var t = typeParameterTypes[0];
var u = typeParameterTypes[1];
return [
new Supertype(b, [t]),
new Supertype(c, [u])
];
});
var e = addClass(
'E',
[],
(_) => [
new Supertype(d, [int, double])
]);
var f = addClass(
'F',
[],
(_) => [
new Supertype(d, [int, bool])
]);
var hierarchy = new ClassHierarchy(program);
expect(
hierarchy.getClassicLeastUpperBound(new InterfaceType(d, [int, double]),
new InterfaceType(d, [int, double])),
new InterfaceType(d, [int, double]));
expect(
hierarchy.getClassicLeastUpperBound(new InterfaceType(d, [int, double]),
new InterfaceType(d, [int, bool])),
new InterfaceType(b, [int]));
expect(
hierarchy.getClassicLeastUpperBound(new InterfaceType(d, [int, double]),
new InterfaceType(d, [bool, double])),
new InterfaceType(c, [double]));
expect(
hierarchy.getClassicLeastUpperBound(new InterfaceType(d, [int, double]),
new InterfaceType(d, [bool, int])),
a.rawType);
expect(hierarchy.getClassicLeastUpperBound(e.rawType, f.rawType),
new InterfaceType(b, [int]));
});
}