mirror of
https://github.com/dart-lang/sdk
synced 2024-09-18 22:11:19 +00:00
ClassHierarchy refactorings
This CL: * Removes unused functions in the ClassHierarchy * Turns the data structures used in the ClassHierarchy upside down (nodes have a lists of supers instead of lists of subs) * Factors some things that needs the original list-of-subs into another class that the user can ask the ClassHierarchy to compute if needed. It appears that it isn't generally. This is step #1 in turning the ClassHierarchy into an incremental ClassHierarchy. Change-Id: I48c5731c01c1b0e8bf1fcd4ddba7f2bf7ce3b9c9 Reviewed-on: https://dart-review.googlesource.com/53662 Commit-Queue: Jens Johansen <jensj@google.com> Reviewed-by: Aske Simon Christensen <askesc@google.com>
This commit is contained in:
parent
94a6a48edd
commit
7b3ba3f41f
|
@ -3,10 +3,11 @@
|
|||
// BSD-style license that can be found in the LICENSE file.
|
||||
library kernel.class_hierarchy;
|
||||
|
||||
import 'ast.dart';
|
||||
import 'dart:collection' show IterableBase;
|
||||
import 'dart:collection';
|
||||
import 'dart:math';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'ast.dart';
|
||||
import 'src/heap.dart';
|
||||
import 'type_algebra.dart';
|
||||
|
||||
|
@ -34,8 +35,8 @@ abstract class ClassHierarchy {
|
|||
}
|
||||
};
|
||||
return new ClosedWorldClassHierarchy._internal(
|
||||
component, numberOfClasses, onAmbiguousSupertypes)
|
||||
.._initialize(mixinInferrer);
|
||||
numberOfClasses, onAmbiguousSupertypes, mixinInferrer)
|
||||
.._initialize(component.libraries);
|
||||
}
|
||||
|
||||
/// Given the [unordered] classes, return them in such order that classes
|
||||
|
@ -43,24 +44,9 @@ abstract class ClassHierarchy {
|
|||
/// [unordered], they are not included.
|
||||
Iterable<Class> getOrderedClasses(Iterable<Class> unordered);
|
||||
|
||||
/// Returns the unique index of the [class_].
|
||||
int getClassIndex(Class class_);
|
||||
|
||||
/// True if the component contains another class that is a subtype of given one.
|
||||
bool hasProperSubtypes(Class class_);
|
||||
|
||||
/// Returns the number of steps in the longest inheritance path from [class_]
|
||||
/// to [Object].
|
||||
int getClassDepth(Class class_);
|
||||
|
||||
/// 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_);
|
||||
|
||||
/// Returns the least upper bound of two interface types, as defined by Dart
|
||||
/// 1.0.
|
||||
///
|
||||
|
@ -119,12 +105,6 @@ abstract class ClassHierarchy {
|
|||
/// The returned list should not be modified.
|
||||
List<Member> getDispatchTargets(Class class_, {bool setters: false});
|
||||
|
||||
/// Returns the single concrete target for invocation of the given interface
|
||||
/// target, or `null` if it could not be resolved or there are multiple
|
||||
/// possible targets.
|
||||
Member getSingleTargetForInterfaceInvocation(Member interfaceTarget,
|
||||
{bool setter: false});
|
||||
|
||||
/// Returns the possibly abstract interface member of [class_] with the given
|
||||
/// [name].
|
||||
///
|
||||
|
@ -153,20 +133,10 @@ abstract class ClassHierarchy {
|
|||
/// classes.
|
||||
List<Member> getDeclaredMembers(Class class_, {bool setters: false});
|
||||
|
||||
/// Returns the subclasses of [class_] as an interval list.
|
||||
ClassSet getSubclassesOf(Class class_);
|
||||
|
||||
/// Returns the subtypes of [class_] as an interval list.
|
||||
ClassSet getSubtypesOf(Class class_);
|
||||
|
||||
/// True if [subclass] inherits from [superclass] though zero or more
|
||||
/// `extends` relationships.
|
||||
bool isSubclassOf(Class subclass, Class superclass);
|
||||
|
||||
/// True if [submixture] inherits from [superclass] though zero or more
|
||||
/// `extends` and `with` relationships.
|
||||
bool isSubmixtureOf(Class submixture, Class superclass);
|
||||
|
||||
/// True if [subtype] inherits from [superclass] though zero or more
|
||||
/// `extends`, `with`, and `implements` relationships.
|
||||
bool isSubtypeOf(Class subtype, Class superclass);
|
||||
|
@ -175,12 +145,6 @@ abstract class ClassHierarchy {
|
|||
/// mixin application (i.e. [Class.mixedInType]).
|
||||
bool isUsedAsMixin(Class class_);
|
||||
|
||||
/// True if the given class is the direct super class of another class.
|
||||
bool isUsedAsSuperClass(Class class_);
|
||||
|
||||
/// True if the given class is used in an `implements` clause.
|
||||
bool isUsedAsSuperInterface(Class class_);
|
||||
|
||||
/// Invokes [callback] for every member declared in or inherited by [class_]
|
||||
/// that overrides or implements a member in a supertype of [class_]
|
||||
/// (or in rare cases, overrides a member declared in [class_]).
|
||||
|
@ -211,11 +175,16 @@ abstract class ClassHierarchy {
|
|||
void forEachOverridePair(Class class_,
|
||||
callback(Member declaredMember, Member interfaceMember, bool isSetter));
|
||||
|
||||
/// This method is invoked by the client after it changed the [classes], and
|
||||
/// some of the information that this hierarchy might have cached, is not
|
||||
/// valid anymore. The hierarchy may perform required updates and return the
|
||||
/// same instance, or return a new instance.
|
||||
ClassHierarchy applyChanges(Iterable<Class> classes);
|
||||
/// This method is invoked by the client after a change: removal, adding, or
|
||||
/// modified classes. For modified classes specify a class as both removed and
|
||||
/// added: Some of the information that this hierarchy might have cached,
|
||||
/// is not valid anymore.
|
||||
/// Note, that if the changes includes changes to the relationship between
|
||||
/// classes, it is the clients responsibility to mark all subclasses as
|
||||
/// changed too.
|
||||
///
|
||||
ClassHierarchy applyChanges(
|
||||
Iterable<Class> removedClasses, Iterable<Class> addedClasses);
|
||||
|
||||
/// Merges two sorted lists.
|
||||
///
|
||||
|
@ -328,35 +297,137 @@ abstract class ClassHierarchy {
|
|||
}
|
||||
}
|
||||
|
||||
abstract class ClassHierarchySubtypes {
|
||||
/// Returns the subtypes of [class_] as an interval list.
|
||||
ClassSet getSubtypesOf(Class class_);
|
||||
|
||||
/// Returns the single concrete target for invocation of the given interface
|
||||
/// target, or `null` if it could not be resolved or there are multiple
|
||||
/// possible targets.
|
||||
Member getSingleTargetForInterfaceInvocation(Member interfaceTarget,
|
||||
{bool setter: false});
|
||||
}
|
||||
|
||||
class _ClassInfoSubtype {
|
||||
final _ClassInfo classInfo;
|
||||
int topDownIndex = -1;
|
||||
|
||||
/// Top-down indices of all subclasses of this class, represented as
|
||||
/// interleaved begin/end interval end points.
|
||||
Uint32List subtypeIntervalList;
|
||||
|
||||
_ClassInfoSubtype(this.classInfo);
|
||||
}
|
||||
|
||||
class _ClosedWorldClassHierarchySubtypes implements ClassHierarchySubtypes {
|
||||
final ClosedWorldClassHierarchy hierarchy;
|
||||
final List<Class> _classesByTopDownIndex;
|
||||
final Map<Class, _ClassInfoSubtype> _infoFor = <Class, _ClassInfoSubtype>{};
|
||||
bool invalidated = false;
|
||||
|
||||
_ClosedWorldClassHierarchySubtypes(this.hierarchy)
|
||||
: _classesByTopDownIndex = new List<Class>(hierarchy._infoFor.length) {
|
||||
if (hierarchy._infoFor.isNotEmpty) {
|
||||
for (Class class_ in hierarchy._infoFor.keys) {
|
||||
_infoFor[class_] = new _ClassInfoSubtype(hierarchy._infoFor[class_]);
|
||||
}
|
||||
|
||||
_topDownSortVisit(_infoFor[hierarchy._infoFor.keys.first]);
|
||||
}
|
||||
}
|
||||
|
||||
/// Downwards traversal of the class hierarchy that orders classes so local
|
||||
/// hierarchies have contiguous indices.
|
||||
int _topDownSortIndex = 0;
|
||||
void _topDownSortVisit(_ClassInfoSubtype subInfo) {
|
||||
if (subInfo.topDownIndex != -1) return;
|
||||
int index = _topDownSortIndex++;
|
||||
subInfo.topDownIndex = index;
|
||||
_classesByTopDownIndex[index] = subInfo.classInfo.classNode;
|
||||
var subtypeSetBuilder = new _IntervalListBuilder()..addSingleton(index);
|
||||
for (_ClassInfo subtype in subInfo.classInfo.directExtenders) {
|
||||
_ClassInfoSubtype subtypeInfo = _infoFor[subtype.classNode];
|
||||
_topDownSortVisit(subtypeInfo);
|
||||
subtypeSetBuilder.addIntervalList(subtypeInfo.subtypeIntervalList);
|
||||
}
|
||||
for (_ClassInfo subtype in subInfo.classInfo.directMixers) {
|
||||
_ClassInfoSubtype subtypeInfo = _infoFor[subtype.classNode];
|
||||
_topDownSortVisit(subtypeInfo);
|
||||
subtypeSetBuilder.addIntervalList(subtypeInfo.subtypeIntervalList);
|
||||
}
|
||||
for (_ClassInfo subtype in subInfo.classInfo.directImplementers) {
|
||||
_ClassInfoSubtype subtypeInfo = _infoFor[subtype.classNode];
|
||||
_topDownSortVisit(subtypeInfo);
|
||||
subtypeSetBuilder.addIntervalList(subtypeInfo.subtypeIntervalList);
|
||||
}
|
||||
subInfo.subtypeIntervalList = subtypeSetBuilder.buildIntervalList();
|
||||
}
|
||||
|
||||
@override
|
||||
Member getSingleTargetForInterfaceInvocation(Member interfaceTarget,
|
||||
{bool setter: false}) {
|
||||
if (invalidated) throw "This datastructure has been invalidated";
|
||||
Name name = interfaceTarget.name;
|
||||
Member target = null;
|
||||
ClassSet subtypes = getSubtypesOf(interfaceTarget.enclosingClass);
|
||||
for (Class c in subtypes) {
|
||||
if (!c.isAbstract) {
|
||||
Member candidate = hierarchy.getDispatchTarget(c, name, setter: setter);
|
||||
if ((candidate != null) && !candidate.isAbstract) {
|
||||
if (target == null) {
|
||||
target = candidate;
|
||||
} else if (target != candidate) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return target;
|
||||
}
|
||||
|
||||
@override
|
||||
ClassSet getSubtypesOf(Class class_) {
|
||||
if (invalidated) throw "This datastructure has been invalidated";
|
||||
Set<Class> result = new Set<Class>();
|
||||
Uint32List list = _infoFor[class_].subtypeIntervalList;
|
||||
for (int i = 0; i < list.length; i += 2) {
|
||||
int from = list[i];
|
||||
int to = list[i + 1];
|
||||
for (int j = from; j < to; j++) {
|
||||
result.add(_classesByTopDownIndex[j]);
|
||||
}
|
||||
}
|
||||
return new ClassSet(result);
|
||||
}
|
||||
}
|
||||
|
||||
/// Implementation of [ClassHierarchy] for closed world.
|
||||
class ClosedWorldClassHierarchy implements ClassHierarchy {
|
||||
final HandleAmbiguousSupertypes _onAmbiguousSupertypes;
|
||||
MixinInferrer mixinInferrer;
|
||||
|
||||
/// The [Component] that this class hierarchy represents.
|
||||
final Component _component;
|
||||
/// The insert order is important.
|
||||
final Map<Class, _ClassInfo> _infoFor =
|
||||
new LinkedHashMap<Class, _ClassInfo>();
|
||||
|
||||
/// All classes in the component.
|
||||
///
|
||||
/// The list is ordered so that classes occur after their super classes.
|
||||
final List<Class> classes;
|
||||
Iterable<Class> get classes => _infoFor.keys;
|
||||
int get numberOfClasses => _infoFor.length;
|
||||
|
||||
final Map<Class, _ClassInfo> _infoFor = <Class, _ClassInfo>{};
|
||||
|
||||
/// All classes ordered by [_ClassInfo.topDownIndex].
|
||||
final List<Class> _classesByTopDownIndex;
|
||||
_ClosedWorldClassHierarchySubtypes _cachedClassHierarchySubtypes;
|
||||
|
||||
ClosedWorldClassHierarchy._internal(
|
||||
this._component, int numberOfClasses, this._onAmbiguousSupertypes)
|
||||
: classes = new List<Class>(numberOfClasses),
|
||||
_classesByTopDownIndex = new List<Class>(numberOfClasses);
|
||||
int numberOfClasses, this._onAmbiguousSupertypes, this.mixinInferrer);
|
||||
|
||||
@override
|
||||
int getClassIndex(Class class_) => _infoFor[class_].topologicalIndex;
|
||||
ClassHierarchySubtypes computeSubtypesInformation() {
|
||||
_cachedClassHierarchySubtypes ??=
|
||||
new _ClosedWorldClassHierarchySubtypes(this);
|
||||
return _cachedClassHierarchySubtypes;
|
||||
}
|
||||
|
||||
@override
|
||||
Iterable<Class> getOrderedClasses(Iterable<Class> unordered) {
|
||||
var unorderedSet = unordered.toSet();
|
||||
return classes.where(unorderedSet.contains);
|
||||
return _infoFor.keys.where(unorderedSet.contains);
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -365,43 +436,17 @@ class ClosedWorldClassHierarchy implements ClassHierarchy {
|
|||
return _infoFor[subclass].isSubclassOf(_infoFor[superclass]);
|
||||
}
|
||||
|
||||
@override
|
||||
bool isSubmixtureOf(Class submixture, Class superclass) {
|
||||
if (identical(submixture, superclass)) return true;
|
||||
return _infoFor[submixture].isSubmixtureOf(_infoFor[superclass]);
|
||||
}
|
||||
|
||||
@override
|
||||
bool isSubtypeOf(Class subtype, Class superclass) {
|
||||
if (identical(subtype, superclass)) return true;
|
||||
return _infoFor[subtype].isSubtypeOf(_infoFor[superclass]);
|
||||
}
|
||||
|
||||
@override
|
||||
bool isUsedAsSuperClass(Class class_) {
|
||||
return _infoFor[class_].directExtenders.isNotEmpty;
|
||||
}
|
||||
|
||||
@override
|
||||
bool isUsedAsMixin(Class class_) {
|
||||
return _infoFor[class_].directMixers.isNotEmpty;
|
||||
}
|
||||
|
||||
@override
|
||||
bool isUsedAsSuperInterface(Class class_) {
|
||||
return _infoFor[class_].directImplementers.isNotEmpty;
|
||||
}
|
||||
|
||||
@override
|
||||
int getClassDepth(Class class_) => _infoFor[class_].depth;
|
||||
|
||||
@override
|
||||
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);
|
||||
|
@ -560,27 +605,6 @@ class ClosedWorldClassHierarchy implements ClassHierarchy {
|
|||
return setters ? info.implementedSetters : info.implementedGettersAndCalls;
|
||||
}
|
||||
|
||||
@override
|
||||
Member getSingleTargetForInterfaceInvocation(Member interfaceTarget,
|
||||
{bool setter: false}) {
|
||||
Name name = interfaceTarget.name;
|
||||
Member target = null;
|
||||
ClassSet subtypes = getSubtypesOf(interfaceTarget.enclosingClass);
|
||||
for (Class c in subtypes) {
|
||||
if (!c.isAbstract) {
|
||||
Member candidate = getDispatchTarget(c, name, setter: setter);
|
||||
if ((candidate != null) && !candidate.isAbstract) {
|
||||
if (target == null) {
|
||||
target = candidate;
|
||||
} else if (target != candidate) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return target;
|
||||
}
|
||||
|
||||
@override
|
||||
Member getInterfaceMember(Class class_, Name name, {bool setter: false}) {
|
||||
List<Member> list = getInterfaceMembers(class_, setters: setter);
|
||||
|
@ -663,25 +687,49 @@ class ClosedWorldClassHierarchy implements ClassHierarchy {
|
|||
|
||||
@override
|
||||
bool hasProperSubtypes(Class class_) {
|
||||
// If there are no subtypes then the subtype set contains the class itself.
|
||||
return !getSubtypesOf(class_).isSingleton;
|
||||
_ClassInfo info = _infoFor[class_];
|
||||
return info.directExtenders.isNotEmpty ||
|
||||
info.directImplementers.isNotEmpty ||
|
||||
info.directMixers.isNotEmpty;
|
||||
}
|
||||
|
||||
@override
|
||||
ClassSet getSubtypesOf(Class class_) {
|
||||
return new ClassSet(this, _infoFor[class_].subtypeIntervalList);
|
||||
}
|
||||
ClassHierarchy applyChanges(
|
||||
Iterable<Class> removedClasses, Iterable<Class> addedClasses) {
|
||||
if (removedClasses.isEmpty && addedClasses.isEmpty) return this;
|
||||
|
||||
@override
|
||||
ClassSet getSubclassesOf(Class class_) {
|
||||
return new ClassSet(this, _infoFor[class_].subclassIntervalList);
|
||||
}
|
||||
// Remove all references to the removed classes.
|
||||
for (Class class_ in removedClasses) {
|
||||
_ClassInfo info = _infoFor[class_];
|
||||
if (class_.supertype != null) {
|
||||
_infoFor[class_.supertype.classNode]?.directExtenders?.remove(info);
|
||||
}
|
||||
if (class_.mixedInType != null) {
|
||||
_infoFor[class_.mixedInType.classNode]?.directMixers?.remove(info);
|
||||
}
|
||||
for (var supertype in class_.implementedTypes) {
|
||||
_infoFor[supertype.classNode]?.directImplementers?.remove(info);
|
||||
}
|
||||
|
||||
@override
|
||||
ClassHierarchy applyChanges(Iterable<Class> classes) {
|
||||
if (classes.isEmpty) return this;
|
||||
return new ClassHierarchy(_component,
|
||||
onAmbiguousSupertypes: _onAmbiguousSupertypes);
|
||||
_infoFor.remove(class_);
|
||||
}
|
||||
|
||||
// If we have a cached computation of subtypes, invalidate it and stop
|
||||
// caching it.
|
||||
if (_cachedClassHierarchySubtypes != null) {
|
||||
_cachedClassHierarchySubtypes.invalidated = true;
|
||||
}
|
||||
|
||||
// Add the new classes
|
||||
List<Class> addedClassesSorted = new List<Class>();
|
||||
int expectedStartIndex = _topSortIndex;
|
||||
for (Class class_ in addedClasses) {
|
||||
_topologicalSortVisit(class_, new Set<Class>(),
|
||||
orderedList: addedClassesSorted);
|
||||
}
|
||||
_initialize2(addedClassesSorted, expectedStartIndex);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -698,19 +746,29 @@ class ClosedWorldClassHierarchy implements ClassHierarchy {
|
|||
return map == null ? null : map[superclass]?.first;
|
||||
}
|
||||
|
||||
void _initialize(MixinInferrer mixinInferrer) {
|
||||
void _initialize(List<Library> libraries) {
|
||||
// Build the class ordering based on a topological sort.
|
||||
for (var library in _component.libraries) {
|
||||
for (var library in libraries) {
|
||||
for (var classNode in library.classes) {
|
||||
_topologicalSortVisit(classNode);
|
||||
_topologicalSortVisit(classNode, new Set<Class>());
|
||||
}
|
||||
}
|
||||
|
||||
// Build index of direct children. Do this after the topological sort so
|
||||
// that super types always occur before subtypes.
|
||||
for (int i = 0; i < classes.length; ++i) {
|
||||
var class_ = classes[i];
|
||||
var info = _infoFor[class_];
|
||||
_initialize2(_infoFor.keys, 0);
|
||||
}
|
||||
|
||||
/// - Build index of direct children.
|
||||
/// - Build list of super classes and super types.
|
||||
/// - Infer and record supertypes for the classes.
|
||||
/// - Record interface members.
|
||||
/// - Perform some sanity checking.
|
||||
/// Do this after the topological sort so that super types always occur
|
||||
/// before subtypes.
|
||||
void _initialize2(
|
||||
Iterable<Class> classes, int expectedStartingTopologicalIndex) {
|
||||
int i = expectedStartingTopologicalIndex;
|
||||
for (Class class_ in classes) {
|
||||
_ClassInfo info = _infoFor[class_];
|
||||
if (class_.supertype != null) {
|
||||
_infoFor[class_.supertype.classNode].directExtenders.add(info);
|
||||
}
|
||||
|
@ -720,53 +778,30 @@ class ClosedWorldClassHierarchy implements ClassHierarchy {
|
|||
for (var supertype in class_.implementedTypes) {
|
||||
_infoFor[supertype.classNode].directImplementers.add(info);
|
||||
}
|
||||
}
|
||||
_topDownVisitClass(class_);
|
||||
|
||||
// Run a downward traversal from the root, compute preorder numbers for
|
||||
// each class, and build their subtype sets as interval lists.
|
||||
if (classes.isNotEmpty) {
|
||||
_topDownSortVisit(_infoFor[classes[0]]);
|
||||
}
|
||||
|
||||
// Now that the intervals for subclass, mixer, and implementer queries are
|
||||
// built, we may infer and record supertypes for the classes.
|
||||
for (int i = 0; i < classes.length; ++i) {
|
||||
Class classNode = classes[i];
|
||||
_ClassInfo info = _infoFor[classNode];
|
||||
if (classNode.supertype != null) {
|
||||
_recordSuperTypes(info, classNode.supertype);
|
||||
if (class_.supertype != null) {
|
||||
_recordSuperTypes(info, class_.supertype);
|
||||
}
|
||||
if (classNode.mixedInType != null) {
|
||||
mixinInferrer?.infer(this, classNode);
|
||||
_recordSuperTypes(info, classNode.mixedInType);
|
||||
if (class_.mixedInType != null) {
|
||||
mixinInferrer?.infer(this, class_);
|
||||
_recordSuperTypes(info, class_.mixedInType);
|
||||
}
|
||||
for (Supertype supertype in classNode.implementedTypes) {
|
||||
for (Supertype supertype in class_.implementedTypes) {
|
||||
_recordSuperTypes(info, supertype);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < classes.length; ++i) {
|
||||
var class_ = classes[i];
|
||||
_buildInterfaceMembers(class_, _infoFor[class_], setters: true);
|
||||
_buildInterfaceMembers(class_, _infoFor[class_], setters: false);
|
||||
}
|
||||
_buildInterfaceMembers(class_, info, setters: true);
|
||||
_buildInterfaceMembers(class_, info, setters: false);
|
||||
|
||||
for (int i = 0; i < classes.length; ++i) {
|
||||
Class cls = classes[i];
|
||||
if (cls == null) {
|
||||
throw "No class at index $i.";
|
||||
}
|
||||
_ClassInfo info = _infoFor[cls];
|
||||
if (info == null) {
|
||||
throw "No info for ${cls.name} from ${cls.fileUri}.";
|
||||
throw "No info for ${class_.name} from ${class_.fileUri}.";
|
||||
}
|
||||
if (info.topologicalIndex != i) {
|
||||
throw "Unexpected topologicalIndex (${info.topologicalIndex} != $i) "
|
||||
"for ${cls.name} from ${cls.fileUri}.";
|
||||
}
|
||||
if (info.subtypeIntervalList == null) {
|
||||
throw "No subtypeIntervalList for ${cls.name} from ${cls.fileUri}.";
|
||||
"for ${class_.name} from ${class_.fileUri}.";
|
||||
}
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -776,34 +811,45 @@ class ClosedWorldClassHierarchy implements ClassHierarchy {
|
|||
/// Returns the depth of the visited class (the number of steps in the longest
|
||||
/// inheritance path to the root class).
|
||||
int _topSortIndex = 0;
|
||||
int _topologicalSortVisit(Class classNode) {
|
||||
int _topologicalSortVisit(Class classNode, Set<Class> beingVisited,
|
||||
{List<Class> orderedList}) {
|
||||
var info = _infoFor[classNode];
|
||||
if (info != null) {
|
||||
if (info.isBeingVisited) {
|
||||
throw 'Cyclic inheritance involving ${info.classNode.name}';
|
||||
}
|
||||
return info.depth; // Already built.
|
||||
return info.depth;
|
||||
}
|
||||
|
||||
if (!beingVisited.add(classNode)) {
|
||||
throw 'Cyclic inheritance involving ${classNode.name}';
|
||||
}
|
||||
|
||||
info = new _ClassInfo(classNode);
|
||||
|
||||
int superDepth = -1;
|
||||
_infoFor[classNode] = info = new _ClassInfo(classNode);
|
||||
info.isBeingVisited = true;
|
||||
if (classNode.supertype != null) {
|
||||
superDepth =
|
||||
max(superDepth, _topologicalSortVisit(classNode.supertype.classNode));
|
||||
superDepth = max(
|
||||
superDepth,
|
||||
_topologicalSortVisit(classNode.supertype.classNode, beingVisited,
|
||||
orderedList: orderedList));
|
||||
}
|
||||
if (classNode.mixedInType != null) {
|
||||
superDepth = max(
|
||||
superDepth, _topologicalSortVisit(classNode.mixedInType.classNode));
|
||||
superDepth,
|
||||
_topologicalSortVisit(classNode.mixedInType.classNode, beingVisited,
|
||||
orderedList: orderedList));
|
||||
}
|
||||
for (var supertype in classNode.implementedTypes) {
|
||||
superDepth = max(superDepth, _topologicalSortVisit(supertype.classNode));
|
||||
superDepth = max(
|
||||
superDepth,
|
||||
_topologicalSortVisit(supertype.classNode, beingVisited,
|
||||
orderedList: orderedList));
|
||||
}
|
||||
_buildDeclaredMembers(classNode, info);
|
||||
_buildImplementedMembers(classNode, info);
|
||||
int id = _topSortIndex++;
|
||||
info.topologicalIndex = id;
|
||||
classes[id] = info.classNode;
|
||||
info.isBeingVisited = false;
|
||||
info.topologicalIndex = _topSortIndex++;
|
||||
|
||||
_infoFor[classNode] = info;
|
||||
orderedList?.add(classNode);
|
||||
beingVisited.remove(classNode);
|
||||
return info.depth = superDepth + 1;
|
||||
}
|
||||
|
||||
|
@ -1004,54 +1050,48 @@ class ClosedWorldClassHierarchy implements ClassHierarchy {
|
|||
}
|
||||
}
|
||||
|
||||
/// Downwards traversal of the class hierarchy that orders classes so local
|
||||
/// hierarchies have contiguous indices.
|
||||
int _topDownSortIndex = 0;
|
||||
void _topDownSortVisit(_ClassInfo info) {
|
||||
if (info.topDownIndex != -1) return;
|
||||
bool isMixedIn = info.directMixers.isNotEmpty;
|
||||
int index = _topDownSortIndex++;
|
||||
info.topDownIndex = index;
|
||||
_classesByTopDownIndex[index] = info.classNode;
|
||||
var subclassSetBuilder = new _IntervalListBuilder()..addSingleton(index);
|
||||
var submixtureSetBuilder =
|
||||
isMixedIn ? (new _IntervalListBuilder()..addSingleton(index)) : null;
|
||||
var subtypeSetBuilder = new _IntervalListBuilder()..addSingleton(index);
|
||||
for (var subtype in info.directExtenders) {
|
||||
_topDownSortVisit(subtype);
|
||||
subclassSetBuilder.addIntervalList(subtype.subclassIntervalList);
|
||||
submixtureSetBuilder?.addIntervalList(subtype.submixtureIntervalList);
|
||||
subtypeSetBuilder.addIntervalList(subtype.subtypeIntervalList);
|
||||
/// Downwards traversal of the class hierarchy, building lists of super types
|
||||
/// and super classes.
|
||||
void _topDownVisitClass(Class class_) {
|
||||
_ClassInfo info = _infoFor[class_];
|
||||
|
||||
var superclassSetBuilder = new _IntervalListBuilder()
|
||||
..addSingleton(info.topologicalIndex);
|
||||
var supertypeSetBuilder = new _IntervalListBuilder()
|
||||
..addSingleton(info.topologicalIndex);
|
||||
|
||||
if (class_.supertype != null) {
|
||||
_ClassInfo supertypeInfo = _infoFor[class_.supertype.classNode];
|
||||
superclassSetBuilder
|
||||
.addIntervalList(supertypeInfo.superclassIntervalList);
|
||||
supertypeSetBuilder.addIntervalList(supertypeInfo.supertypeIntervalList);
|
||||
}
|
||||
for (var subtype in info.directMixers) {
|
||||
_topDownSortVisit(subtype);
|
||||
submixtureSetBuilder.addIntervalList(subtype.submixtureIntervalList);
|
||||
subtypeSetBuilder.addIntervalList(subtype.subtypeIntervalList);
|
||||
|
||||
if (class_.mixedInType != null) {
|
||||
_ClassInfo mixedInTypeInfo = _infoFor[class_.mixedInType.classNode];
|
||||
supertypeSetBuilder
|
||||
.addIntervalList(mixedInTypeInfo.supertypeIntervalList);
|
||||
}
|
||||
for (var subtype in info.directImplementers) {
|
||||
_topDownSortVisit(subtype);
|
||||
subtypeSetBuilder.addIntervalList(subtype.subtypeIntervalList);
|
||||
|
||||
for (Supertype supertype in class_.implementedTypes) {
|
||||
_ClassInfo supertypeInfo = _infoFor[supertype.classNode];
|
||||
supertypeSetBuilder.addIntervalList(supertypeInfo.supertypeIntervalList);
|
||||
}
|
||||
info.subclassIntervalList = subclassSetBuilder.buildIntervalList();
|
||||
info.submixtureIntervalList = isMixedIn
|
||||
? submixtureSetBuilder.buildIntervalList()
|
||||
: info.subclassIntervalList;
|
||||
info.subtypeIntervalList = subtypeSetBuilder.buildIntervalList();
|
||||
|
||||
info.superclassIntervalList = superclassSetBuilder.buildIntervalList();
|
||||
info.supertypeIntervalList = supertypeSetBuilder.buildIntervalList();
|
||||
}
|
||||
|
||||
/// Creates a histogram such that index `N` contains the number of classes
|
||||
/// that have `N` intervals in its subclass or subtype set (whichever is
|
||||
/// larger).
|
||||
/// that have `N` intervals in itssupertype set.
|
||||
///
|
||||
/// The more numbers are condensed near the beginning, the more efficient the
|
||||
/// internal data structure is.
|
||||
List<int> getExpenseHistogram() {
|
||||
var result = <int>[];
|
||||
for (Class class_ in classes) {
|
||||
for (Class class_ in _infoFor.keys) {
|
||||
var info = _infoFor[class_];
|
||||
int intervals = max(info.subclassIntervalList.length,
|
||||
info.subtypeIntervalList.length) ~/
|
||||
2;
|
||||
int intervals = info.supertypeIntervalList.length ~/ 2;
|
||||
if (intervals >= result.length) {
|
||||
int oldLength = result.length;
|
||||
result.length = intervals + 1;
|
||||
|
@ -1062,29 +1102,30 @@ class ClosedWorldClassHierarchy implements ClassHierarchy {
|
|||
return result;
|
||||
}
|
||||
|
||||
/// Returns the average number of intervals per subtype relation (less
|
||||
/// Returns the average number of intervals per supertype relation (less
|
||||
/// is better, 1.0 is bad).
|
||||
///
|
||||
/// This is an estimate of the memory use compared to a data structure that
|
||||
/// enumerates all subclass/subtype pairs.
|
||||
/// enumerates all superclass/supertype pairs.
|
||||
double getCompressionRatio() {
|
||||
int intervals = 0;
|
||||
int sizes = 0;
|
||||
for (Class class_ in classes) {
|
||||
for (Class class_ in _infoFor.keys) {
|
||||
var info = _infoFor[class_];
|
||||
intervals += (info.subclassIntervalList.length +
|
||||
info.subtypeIntervalList.length) ~/
|
||||
intervals += (info.superclassIntervalList.length +
|
||||
info.supertypeIntervalList.length) ~/
|
||||
2;
|
||||
sizes += _intervalListSize(info.subclassIntervalList) +
|
||||
_intervalListSize(info.subtypeIntervalList);
|
||||
sizes += _intervalListSize(info.superclassIntervalList) +
|
||||
_intervalListSize(info.supertypeIntervalList);
|
||||
}
|
||||
|
||||
return sizes == 0 ? 1.0 : intervals / sizes;
|
||||
}
|
||||
|
||||
/// Returns the number of entries in hash tables storing hierarchy data.
|
||||
int getSuperTypeHashTableSize() {
|
||||
int sum = 0;
|
||||
for (Class class_ in classes) {
|
||||
for (Class class_ in _infoFor.keys) {
|
||||
sum += _infoFor[class_].genericSuperTypes?.length ?? 0;
|
||||
}
|
||||
return sum;
|
||||
|
@ -1179,8 +1220,6 @@ int _intervalListSize(Uint32List intervalList) {
|
|||
class _ClassInfo {
|
||||
final Class classNode;
|
||||
int topologicalIndex = 0;
|
||||
int topDownIndex = -1;
|
||||
bool isBeingVisited = false;
|
||||
int depth = 0;
|
||||
|
||||
// Super types must always occur before subtypes in these lists.
|
||||
|
@ -1191,30 +1230,15 @@ class _ClassInfo {
|
|||
//
|
||||
// Here `A` must occur before `B` in the list of direct extenders of Object,
|
||||
// because `B` is a subtype of `A`.
|
||||
final List<_ClassInfo> directExtenders = <_ClassInfo>[];
|
||||
final List<_ClassInfo> directMixers = <_ClassInfo>[];
|
||||
final List<_ClassInfo> directImplementers = <_ClassInfo>[];
|
||||
final Set<_ClassInfo> directExtenders = new LinkedHashSet<_ClassInfo>();
|
||||
final Set<_ClassInfo> directMixers = new LinkedHashSet<_ClassInfo>();
|
||||
final Set<_ClassInfo> directImplementers = new LinkedHashSet<_ClassInfo>();
|
||||
|
||||
/// Top-down indices of all subclasses of this class, represented as
|
||||
/// interleaved begin/end interval end points.
|
||||
Uint32List subclassIntervalList;
|
||||
Uint32List submixtureIntervalList;
|
||||
Uint32List subtypeIntervalList;
|
||||
Uint32List superclassIntervalList;
|
||||
Uint32List supertypeIntervalList;
|
||||
|
||||
List<_ClassInfo> leastUpperBoundInfos;
|
||||
|
||||
bool isSubclassOf(_ClassInfo other) {
|
||||
return _intervalListContains(other.subclassIntervalList, topDownIndex);
|
||||
}
|
||||
|
||||
bool isSubmixtureOf(_ClassInfo other) {
|
||||
return _intervalListContains(other.submixtureIntervalList, topDownIndex);
|
||||
}
|
||||
|
||||
bool isSubtypeOf(_ClassInfo other) {
|
||||
return _intervalListContains(other.subtypeIntervalList, topDownIndex);
|
||||
}
|
||||
|
||||
/// Maps generic supertype classes to the instantiation implemented by this
|
||||
/// class.
|
||||
///
|
||||
|
@ -1243,6 +1267,15 @@ class _ClassInfo {
|
|||
|
||||
_ClassInfo(this.classNode);
|
||||
|
||||
bool isSubclassOf(_ClassInfo other) {
|
||||
return _intervalListContains(
|
||||
superclassIntervalList, other.topologicalIndex);
|
||||
}
|
||||
|
||||
bool isSubtypeOf(_ClassInfo other) {
|
||||
return _intervalListContains(supertypeIntervalList, other.topologicalIndex);
|
||||
}
|
||||
|
||||
void recordGenericSuperType(Class cls, Supertype type,
|
||||
HandleAmbiguousSupertypes onAmbiguousSupertypes) {
|
||||
List<Supertype> existing = genericSuperTypes[cls];
|
||||
|
@ -1255,79 +1288,23 @@ class _ClassInfo {
|
|||
}
|
||||
}
|
||||
|
||||
/// An immutable set of classes, internally represented as an interval list.
|
||||
/// An immutable set of classes.
|
||||
class ClassSet extends IterableBase<Class> {
|
||||
final ClosedWorldClassHierarchy _hierarchy;
|
||||
final Uint32List _intervalList;
|
||||
final Set<Class> _classes;
|
||||
ClassSet(this._classes);
|
||||
|
||||
ClassSet(this._hierarchy, this._intervalList);
|
||||
|
||||
bool get isEmpty => _intervalList.isEmpty;
|
||||
|
||||
bool get isSingleton {
|
||||
var list = _intervalList;
|
||||
return list.length == 2 && list[0] + 1 == list[1];
|
||||
}
|
||||
|
||||
@override
|
||||
bool contains(Object class_) {
|
||||
return _intervalListContains(
|
||||
_intervalList, _hierarchy._infoFor[class_ as Class].topDownIndex);
|
||||
return _classes.contains(_classes);
|
||||
}
|
||||
|
||||
ClassSet union(ClassSet other) {
|
||||
assert(_hierarchy == other._hierarchy);
|
||||
if (identical(_intervalList, other._intervalList)) return this;
|
||||
_IntervalListBuilder builder = new _IntervalListBuilder();
|
||||
builder.addIntervalList(_intervalList);
|
||||
builder.addIntervalList(other._intervalList);
|
||||
return new ClassSet(_hierarchy, builder.buildIntervalList());
|
||||
Set<Class> result = new Set<Class>.from(_classes);
|
||||
result.addAll(other._classes);
|
||||
return new ClassSet(result);
|
||||
}
|
||||
|
||||
@override
|
||||
Iterator<Class> get iterator =>
|
||||
new _ClassSetIterator(_hierarchy, _intervalList);
|
||||
}
|
||||
|
||||
/// Iterator for [ClassSet].
|
||||
class _ClassSetIterator implements Iterator<Class> {
|
||||
final ClosedWorldClassHierarchy _hierarchy;
|
||||
final Uint32List _intervalList;
|
||||
int _intervalIndex;
|
||||
int _classIndex;
|
||||
int _classIndexLimit;
|
||||
|
||||
// Interval list is a list of pairs (start, end).
|
||||
static const int _intervalIndexStep = 2;
|
||||
|
||||
_ClassSetIterator(this._hierarchy, this._intervalList)
|
||||
: _intervalIndex = -_intervalIndexStep,
|
||||
_classIndex = -1,
|
||||
_classIndexLimit = -1;
|
||||
|
||||
@override
|
||||
bool moveNext() {
|
||||
if (_classIndex + 1 < _classIndexLimit) {
|
||||
_classIndex++;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_intervalIndex + _intervalIndexStep < _intervalList.length) {
|
||||
_intervalIndex += _intervalIndexStep;
|
||||
_classIndex = _intervalList[_intervalIndex];
|
||||
_classIndexLimit = _intervalList[_intervalIndex + 1];
|
||||
assert(_classIndex < _classIndexLimit);
|
||||
return true;
|
||||
}
|
||||
|
||||
_classIndex = _classIndexLimit = -1;
|
||||
return false;
|
||||
}
|
||||
|
||||
@override
|
||||
Class get current => (_classIndex >= 0)
|
||||
? _hierarchy._classesByTopDownIndex[_classIndex]
|
||||
: null;
|
||||
Iterator<Class> get iterator => _classes.iterator;
|
||||
}
|
||||
|
||||
/// Heap for use in computing least upper bounds.
|
||||
|
|
|
@ -63,7 +63,7 @@ class MixinFullResolution {
|
|||
}
|
||||
|
||||
// We might need to update the class hierarchy.
|
||||
hierarchy = hierarchy.applyChanges(transformedClasses);
|
||||
hierarchy = hierarchy.applyChanges(transformedClasses, transformedClasses);
|
||||
|
||||
if (!doSuperResolution) {
|
||||
return;
|
||||
|
|
|
@ -94,6 +94,9 @@ class ProgramRoot {
|
|||
class TreeShaker {
|
||||
final CoreTypes coreTypes;
|
||||
final ClosedWorldClassHierarchy hierarchy;
|
||||
final ClassHierarchySubtypes hierarchySubtypes;
|
||||
final Map<Class, int> numberedClasses;
|
||||
final List<Class> classes;
|
||||
final Component component;
|
||||
final bool strongMode;
|
||||
final List<ProgramRoot> programRoots;
|
||||
|
@ -213,7 +216,7 @@ class TreeShaker {
|
|||
}
|
||||
|
||||
ClassRetention getClassRetention(Class classNode) {
|
||||
int index = hierarchy.getClassIndex(classNode);
|
||||
int index = numberedClasses[classNode];
|
||||
return _classRetention[index];
|
||||
}
|
||||
|
||||
|
@ -227,11 +230,14 @@ class TreeShaker {
|
|||
|
||||
TreeShaker._internal(this.coreTypes, this.hierarchy, this.component,
|
||||
this.strongMode, this.programRoots)
|
||||
: this._dispatchedNames = new List<Set<Name>>(hierarchy.classes.length),
|
||||
: this._dispatchedNames = new List<Set<Name>>(hierarchy.numberOfClasses),
|
||||
this._usedMembersWithHost =
|
||||
new List<Set<Member>>(hierarchy.classes.length),
|
||||
new List<Set<Member>>(hierarchy.numberOfClasses),
|
||||
this._classRetention = new List<ClassRetention>.filled(
|
||||
hierarchy.classes.length, ClassRetention.None) {
|
||||
hierarchy.numberOfClasses, ClassRetention.None),
|
||||
this.hierarchySubtypes = hierarchy.computeSubtypesInformation(),
|
||||
this.numberedClasses = createMapNumberIndex(hierarchy.classes),
|
||||
this.classes = new List<Class>.from(hierarchy.classes) {
|
||||
_visitor = new _TreeShakerVisitor(this);
|
||||
_covariantVisitor = new _ExternalTypeVisitor(this, isCovariant: true);
|
||||
_contravariantVisitor =
|
||||
|
@ -246,6 +252,14 @@ class TreeShaker {
|
|||
}
|
||||
}
|
||||
|
||||
static Map<Class, int> createMapNumberIndex(Iterable<Class> classes) {
|
||||
Map<Class, int> result = new Map<Class, int>();
|
||||
for (Class class_ in classes) {
|
||||
result[class_] = result.length;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void _build() {
|
||||
if (component.mainMethod == null) {
|
||||
throw 'Cannot perform tree shaking on a component without a main method';
|
||||
|
@ -271,8 +285,8 @@ class TreeShaker {
|
|||
// Mark overridden members in order to preserve abstract members as
|
||||
// necessary.
|
||||
if (strongMode) {
|
||||
for (int i = hierarchy.classes.length - 1; i >= 0; --i) {
|
||||
Class class_ = hierarchy.classes[i];
|
||||
for (int i = classes.length - 1; i >= 0; --i) {
|
||||
Class class_ = classes[i];
|
||||
if (isHierarchyUsed(class_)) {
|
||||
hierarchy.forEachOverridePair(class_,
|
||||
(Member ownMember, Member superMember, bool isSetter) {
|
||||
|
@ -305,7 +319,7 @@ class TreeShaker {
|
|||
/// Registers the given name as seen in a dynamic dispatch, and discovers used
|
||||
/// instance members accordingly.
|
||||
void _addDispatchedName(Class receiver, Name name) {
|
||||
int index = hierarchy.getClassIndex(receiver);
|
||||
int index = numberedClasses[receiver];
|
||||
Set<Name> receiverNames = _dispatchedNames[index] ??= new Set<Name>();
|
||||
// TODO(asgerf): make use of selector arity and getter/setter kind
|
||||
if (receiverNames.add(name)) {
|
||||
|
@ -332,7 +346,7 @@ class TreeShaker {
|
|||
}
|
||||
}
|
||||
}
|
||||
var subtypes = hierarchy.getSubtypesOf(receiver);
|
||||
var subtypes = hierarchySubtypes.getSubtypesOf(receiver);
|
||||
var receiverSet = _receiversOfName[name];
|
||||
_receiversOfName[name] = receiverSet == null
|
||||
? subtypes
|
||||
|
@ -358,7 +372,7 @@ class TreeShaker {
|
|||
/// Registers the given class as instantiated and discovers new dispatch
|
||||
/// target candidates accordingly.
|
||||
void _addInstantiatedClass(Class classNode) {
|
||||
int index = hierarchy.getClassIndex(classNode);
|
||||
int index = numberedClasses[classNode];
|
||||
ClassRetention retention = _classRetention[index];
|
||||
if (retention.index < ClassRetention.Instance.index) {
|
||||
_classRetention[index] = ClassRetention.Instance;
|
||||
|
@ -368,7 +382,7 @@ class TreeShaker {
|
|||
|
||||
/// Register that an external subclass of the given class may be instantiated.
|
||||
void _addInstantiatedExternalSubclass(Class classNode) {
|
||||
int index = hierarchy.getClassIndex(classNode);
|
||||
int index = numberedClasses[classNode];
|
||||
ClassRetention retention = _classRetention[index];
|
||||
if (retention.index < ClassRetention.ExternalInstance.index) {
|
||||
_classRetention[index] = ClassRetention.ExternalInstance;
|
||||
|
@ -491,7 +505,7 @@ class TreeShaker {
|
|||
|
||||
/// Registers the given class as being used in a type annotation.
|
||||
void _addClassUsedInType(Class classNode) {
|
||||
int index = hierarchy.getClassIndex(classNode);
|
||||
int index = numberedClasses[classNode];
|
||||
ClassRetention retention = _classRetention[index];
|
||||
if (retention.index < ClassRetention.Hierarchy.index) {
|
||||
_classRetention[index] = ClassRetention.Hierarchy;
|
||||
|
@ -516,7 +530,7 @@ class TreeShaker {
|
|||
void _addStaticNamespace(TreeNode container) {
|
||||
assert(container is Class || container is Library);
|
||||
if (container is Class) {
|
||||
int index = hierarchy.getClassIndex(container);
|
||||
int index = numberedClasses[container];
|
||||
var oldRetention = _classRetention[index];
|
||||
if (oldRetention == ClassRetention.None) {
|
||||
_classRetention[index] = ClassRetention.Namespace;
|
||||
|
@ -537,7 +551,7 @@ class TreeShaker {
|
|||
}
|
||||
if (host != null) {
|
||||
// Check if the member has been seen with this host before.
|
||||
int index = hierarchy.getClassIndex(host);
|
||||
int index = numberedClasses[host];
|
||||
Set<Member> members = _usedMembersWithHost[index] ??= new Set<Member>();
|
||||
if (!members.add(member)) return;
|
||||
_usedMembers.putIfAbsent(member, _makeIncompleteSummary);
|
||||
|
|
|
@ -60,12 +60,23 @@ main(List<String> args) {
|
|||
hierarchies.add(buildHierarchy());
|
||||
}
|
||||
|
||||
List<Class> classes = hierarchies.first.classes.toList();
|
||||
|
||||
int currentHierarchy = 0;
|
||||
ClosedWorldClassHierarchy getClassHierarchy() {
|
||||
currentHierarchy = (currentHierarchy + 1) % hierarchies.length;
|
||||
return hierarchies[currentHierarchy];
|
||||
}
|
||||
|
||||
{
|
||||
var classHierarchy = getClassHierarchy();
|
||||
if (classHierarchy is ClosedWorldClassHierarchy) {
|
||||
for (Class class_ in classes) {
|
||||
classHierarchy.hasProperSubtypes(class_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Random rnd = new Random(12345);
|
||||
const int numQueryTrials = 100000;
|
||||
|
||||
|
@ -76,12 +87,11 @@ main(List<String> args) {
|
|||
watch.reset();
|
||||
for (int i = 0; i < numQueryTrials; i++) {
|
||||
var classHierarchy = getClassHierarchy();
|
||||
int first = rnd.nextInt(classHierarchy.classes.length);
|
||||
int second = rnd.nextInt(classHierarchy.classes.length);
|
||||
Class firstClass = classHierarchy.classes[first];
|
||||
Class secondClass = classHierarchy.classes[second];
|
||||
int first = rnd.nextInt(classes.length);
|
||||
int second = rnd.nextInt(classes.length);
|
||||
Class firstClass = classes[first];
|
||||
Class secondClass = classes[second];
|
||||
classHierarchy.isSubclassOf(firstClass, secondClass);
|
||||
classHierarchy.isSubmixtureOf(firstClass, secondClass);
|
||||
classHierarchy.isSubtypeOf(firstClass, secondClass);
|
||||
classHierarchy.getClassAsInstanceOf(firstClass, secondClass);
|
||||
}
|
||||
|
@ -89,10 +99,10 @@ main(List<String> args) {
|
|||
watch.reset();
|
||||
for (int i = 0; i < numQueryTrials; i++) {
|
||||
var classHierarchy = getClassHierarchy();
|
||||
int first = rnd.nextInt(classHierarchy.classes.length);
|
||||
int second = rnd.nextInt(classHierarchy.classes.length);
|
||||
Class firstClass = classHierarchy.classes[first];
|
||||
Class secondClass = classHierarchy.classes[second];
|
||||
int first = rnd.nextInt(classes.length);
|
||||
int second = rnd.nextInt(classes.length);
|
||||
Class firstClass = classes[first];
|
||||
Class secondClass = classes[second];
|
||||
classHierarchy.isSubclassOf(firstClass, secondClass);
|
||||
}
|
||||
int subclassQueryTime = watch.elapsedMicroseconds;
|
||||
|
@ -100,21 +110,10 @@ main(List<String> args) {
|
|||
watch.reset();
|
||||
for (int i = 0; i < numQueryTrials; i++) {
|
||||
var classHierarchy = getClassHierarchy();
|
||||
int first = rnd.nextInt(classHierarchy.classes.length);
|
||||
int second = rnd.nextInt(classHierarchy.classes.length);
|
||||
Class firstClass = classHierarchy.classes[first];
|
||||
Class secondClass = classHierarchy.classes[second];
|
||||
classHierarchy.isSubmixtureOf(firstClass, secondClass);
|
||||
}
|
||||
int submixtureQueryTime = watch.elapsedMicroseconds;
|
||||
|
||||
watch.reset();
|
||||
for (int i = 0; i < numQueryTrials; i++) {
|
||||
var classHierarchy = getClassHierarchy();
|
||||
int first = rnd.nextInt(classHierarchy.classes.length);
|
||||
int second = rnd.nextInt(classHierarchy.classes.length);
|
||||
Class firstClass = classHierarchy.classes[first];
|
||||
Class secondClass = classHierarchy.classes[second];
|
||||
int first = rnd.nextInt(classes.length);
|
||||
int second = rnd.nextInt(classes.length);
|
||||
Class firstClass = classes[first];
|
||||
Class secondClass = classes[second];
|
||||
classHierarchy.isSubtypeOf(firstClass, secondClass);
|
||||
}
|
||||
int subtypeQueryTime = watch.elapsedMicroseconds;
|
||||
|
@ -122,10 +121,10 @@ main(List<String> args) {
|
|||
watch.reset();
|
||||
for (int i = 0; i < numQueryTrials; i++) {
|
||||
var classHierarchy = getClassHierarchy();
|
||||
int first = rnd.nextInt(classHierarchy.classes.length);
|
||||
int second = rnd.nextInt(classHierarchy.classes.length);
|
||||
Class firstClass = classHierarchy.classes[first];
|
||||
Class secondClass = classHierarchy.classes[second];
|
||||
int first = rnd.nextInt(classes.length);
|
||||
int second = rnd.nextInt(classes.length);
|
||||
Class firstClass = classes[first];
|
||||
Class secondClass = classes[second];
|
||||
classHierarchy.getClassAsInstanceOf(firstClass, secondClass);
|
||||
}
|
||||
int asInstanceOfQueryTime = watch.elapsedMicroseconds;
|
||||
|
@ -133,21 +132,18 @@ main(List<String> args) {
|
|||
// Estimate the overhead from test case generation.
|
||||
watch.reset();
|
||||
for (int i = 0; i < numQueryTrials; i++) {
|
||||
var classHierarchy = getClassHierarchy();
|
||||
int first = rnd.nextInt(classHierarchy.classes.length);
|
||||
int second = rnd.nextInt(classHierarchy.classes.length);
|
||||
classHierarchy.classes[first];
|
||||
classHierarchy.classes[second];
|
||||
int first = rnd.nextInt(classes.length);
|
||||
int second = rnd.nextInt(classes.length);
|
||||
classes[first];
|
||||
classes[second];
|
||||
}
|
||||
int queryNoise = watch.elapsedMicroseconds;
|
||||
|
||||
subclassQueryTime -= queryNoise;
|
||||
submixtureQueryTime -= queryNoise;
|
||||
subtypeQueryTime -= queryNoise;
|
||||
asInstanceOfQueryTime -= queryNoise;
|
||||
|
||||
String subclassPerSecond = perSecond(subclassQueryTime, numQueryTrials);
|
||||
String submixturePerSecond = perSecond(submixtureQueryTime, numQueryTrials);
|
||||
String subtypePerSecond = perSecond(subtypeQueryTime, numQueryTrials);
|
||||
String asInstanceOfPerSecond =
|
||||
perSecond(asInstanceOfQueryTime, numQueryTrials);
|
||||
|
@ -156,8 +152,8 @@ main(List<String> args) {
|
|||
watch.reset();
|
||||
for (int i = 0; i < numQueryTrials; i++) {
|
||||
var classHierarchy = getClassHierarchy();
|
||||
int classId = rnd.nextInt(classHierarchy.classes.length);
|
||||
Class classNode = classHierarchy.classes[classId];
|
||||
int classId = rnd.nextInt(classes.length);
|
||||
Class classNode = classes[classId];
|
||||
classHierarchy.getDispatchTarget(classNode, new Name('toString'));
|
||||
}
|
||||
int dispatchToStringTime = watch.elapsedMicroseconds;
|
||||
|
@ -165,8 +161,8 @@ main(List<String> args) {
|
|||
watch.reset();
|
||||
for (int i = 0; i < numQueryTrials; i++) {
|
||||
var classHierarchy = getClassHierarchy();
|
||||
int classId = rnd.nextInt(classHierarchy.classes.length);
|
||||
Class classNode = classHierarchy.classes[classId];
|
||||
int classId = rnd.nextInt(classes.length);
|
||||
Class classNode = classes[classId];
|
||||
classHierarchy.getDispatchTarget(classNode, new Name('getFloo'));
|
||||
}
|
||||
int dispatchGenericGetTime = watch.elapsedMicroseconds;
|
||||
|
@ -174,8 +170,8 @@ main(List<String> args) {
|
|||
watch.reset();
|
||||
for (int i = 0; i < numQueryTrials; i++) {
|
||||
var classHierarchy = getClassHierarchy();
|
||||
int classId = rnd.nextInt(classHierarchy.classes.length);
|
||||
Class classNode = classHierarchy.classes[classId];
|
||||
int classId = rnd.nextInt(classes.length);
|
||||
Class classNode = classes[classId];
|
||||
for (var _ in classHierarchy.getDispatchTargets(classNode)) {}
|
||||
}
|
||||
int dispatchAllTargetsTime = watch.elapsedMicroseconds;
|
||||
|
@ -184,8 +180,8 @@ main(List<String> args) {
|
|||
watch.reset();
|
||||
for (int i = 0; i < numQueryTrials; i++) {
|
||||
var classHierarchy = getClassHierarchy();
|
||||
int classId = rnd.nextInt(classHierarchy.classes.length);
|
||||
Class classNode = classHierarchy.classes[classId];
|
||||
int classId = rnd.nextInt(classes.length);
|
||||
Class classNode = classes[classId];
|
||||
classHierarchy.getInterfaceMember(classNode, new Name('toString'));
|
||||
}
|
||||
int interfaceToStringTime = watch.elapsedMicroseconds;
|
||||
|
@ -193,8 +189,8 @@ main(List<String> args) {
|
|||
watch.reset();
|
||||
for (int i = 0; i < numQueryTrials; i++) {
|
||||
var classHierarchy = getClassHierarchy();
|
||||
int classId = rnd.nextInt(classHierarchy.classes.length);
|
||||
Class classNode = classHierarchy.classes[classId];
|
||||
int classId = rnd.nextInt(classes.length);
|
||||
Class classNode = classes[classId];
|
||||
classHierarchy.getInterfaceMember(classNode, new Name('getFloo'));
|
||||
}
|
||||
int interfaceGenericGetTime = watch.elapsedMicroseconds;
|
||||
|
@ -202,8 +198,8 @@ main(List<String> args) {
|
|||
watch.reset();
|
||||
for (int i = 0; i < numQueryTrials; i++) {
|
||||
var classHierarchy = getClassHierarchy();
|
||||
int classId = rnd.nextInt(classHierarchy.classes.length);
|
||||
Class classNode = classHierarchy.classes[classId];
|
||||
int classId = rnd.nextInt(classes.length);
|
||||
Class classNode = classes[classId];
|
||||
for (var _ in classHierarchy.getInterfaceMembers(classNode)) {}
|
||||
}
|
||||
int interfaceAllTargetsTime = watch.elapsedMicroseconds;
|
||||
|
@ -211,9 +207,8 @@ main(List<String> args) {
|
|||
// Estimate overhead from test case generation.
|
||||
watch.reset();
|
||||
for (int i = 0; i < numQueryTrials; i++) {
|
||||
var classHierarchy = getClassHierarchy();
|
||||
int classId = rnd.nextInt(classHierarchy.classes.length);
|
||||
classHierarchy.classes[classId];
|
||||
int classId = rnd.nextInt(classes.length);
|
||||
classes[classId];
|
||||
}
|
||||
int dispatchTargetNoise = watch.elapsedMicroseconds;
|
||||
|
||||
|
@ -241,7 +236,7 @@ main(List<String> args) {
|
|||
watch.reset();
|
||||
var classHierarchy = getClassHierarchy();
|
||||
int numberOfOverridePairs = 0;
|
||||
for (var class_ in classHierarchy.classes) {
|
||||
for (var class_ in classes) {
|
||||
classHierarchy.forEachOverridePair(class_, (member, supermember, isSetter) {
|
||||
++numberOfOverridePairs;
|
||||
});
|
||||
|
@ -251,13 +246,18 @@ main(List<String> args) {
|
|||
String overridePairsPerSecond =
|
||||
perSecond(overrideTime, numberOfOverridePairs);
|
||||
|
||||
List<int> depth = new List(classHierarchy.classes.length);
|
||||
Map<Class, int> classIds = new Map<Class, int>();
|
||||
for (Class class_ in classes) {
|
||||
classIds[class_] = classIds.length;
|
||||
}
|
||||
|
||||
List<int> depth = new List(classes.length);
|
||||
for (int i = 0; i < depth.length; ++i) {
|
||||
int parentDepth = 0;
|
||||
var classNode = classHierarchy.classes[i];
|
||||
var classNode = classes[i];
|
||||
for (var supertype in classNode.supers) {
|
||||
var superclass = supertype.classNode;
|
||||
int index = classHierarchy.getClassIndex(superclass);
|
||||
int index = classIds[superclass];
|
||||
if (!(index < i)) {
|
||||
throw '${classNode.name}($i) extends ${superclass.name}($index)';
|
||||
}
|
||||
|
@ -271,7 +271,7 @@ main(List<String> args) {
|
|||
double medianDepth = median(depth);
|
||||
int totalDepth = sum(depth);
|
||||
|
||||
int numberOfClasses = classHierarchy.classes.length;
|
||||
int numberOfClasses = classes.length;
|
||||
String expenseHistogram =
|
||||
classHierarchy.getExpenseHistogram().skip(1).join(' ');
|
||||
|
||||
|
@ -280,7 +280,6 @@ classes: $numberOfClasses
|
|||
build.cold: $coldBuildTime ms
|
||||
build.hot: $hotBuildTime ms
|
||||
query.isSubclassOf: $subclassPerSecond
|
||||
query.isSubmixtureOf: $submixturePerSecond
|
||||
query.isSubtypeOf: $subtypePerSecond
|
||||
query.getClassAsInstanceOf: $asInstanceOfPerSecond
|
||||
query.getDispatchTarget(toString): $dispatchToStringPerSecond
|
||||
|
|
|
@ -19,22 +19,17 @@ main(List<String> args) {
|
|||
void testClassHierarchyOnComponent(Component component, {bool verbose: false}) {
|
||||
BasicClassHierarchy basic = new BasicClassHierarchy(component);
|
||||
ClosedWorldClassHierarchy classHierarchy = new ClassHierarchy(component);
|
||||
int total = classHierarchy.classes.length;
|
||||
int total = classHierarchy.numberOfClasses;
|
||||
int progress = 0;
|
||||
for (var class1 in classHierarchy.classes) {
|
||||
for (var class2 in classHierarchy.classes) {
|
||||
bool isSubclass = classHierarchy.isSubclassOf(class1, class2);
|
||||
bool isSubmixture = classHierarchy.isSubmixtureOf(class1, class2);
|
||||
bool isSubtype = classHierarchy.isSubtypeOf(class1, class2);
|
||||
var asInstance = classHierarchy.getClassAsInstanceOf(class1, class2);
|
||||
if (isSubclass != basic.isSubclassOf(class1, class2)) {
|
||||
fail('isSubclassOf(${class1.name}, ${class2.name}) returned '
|
||||
'$isSubclass but should be ${!isSubclass}');
|
||||
}
|
||||
if (isSubmixture != basic.isSubmixtureOf(class1, class2)) {
|
||||
fail('isSubmixtureOf(${class1.name}, ${class2.name}) returned '
|
||||
'$isSubclass but should be ${!isSubclass}');
|
||||
}
|
||||
if (isSubtype != basic.isSubtypeOf(class1, class2)) {
|
||||
fail('isSubtypeOf(${class1.name}, ${class2.name}) returned '
|
||||
'$isSubtype but should be ${!isSubtype}');
|
||||
|
|
|
@ -25,7 +25,7 @@ class ClosedWorldClassHierarchyTest extends _ClassHierarchyTest {
|
|||
|
||||
void test_applyChanges() {
|
||||
var a = addClass(new Class(name: 'A', supertype: objectSuper));
|
||||
addClass(new Class(name: 'B', supertype: a.asThisSupertype));
|
||||
var b = addClass(new Class(name: 'B', supertype: a.asThisSupertype));
|
||||
|
||||
_assertTestLibraryText('''
|
||||
class A {}
|
||||
|
@ -33,12 +33,20 @@ class B extends self::A {}
|
|||
''');
|
||||
|
||||
// No updated classes, the same hierarchy.
|
||||
expect(hierarchy.applyChanges([]), same(hierarchy));
|
||||
expect(hierarchy.applyChanges([], []), same(hierarchy));
|
||||
expect(hierarchy.hasProperSubtypes(a), true);
|
||||
|
||||
// Has updated classes, a new hierarchy.
|
||||
var newHierarchy = hierarchy.applyChanges([a]);
|
||||
expect(newHierarchy, isNot(same(hierarchy)));
|
||||
expect(newHierarchy, new isInstanceOf<ClosedWorldClassHierarchy>());
|
||||
// Has updated classes, still the same hierarchy (instance). Can answer
|
||||
// queries about the new classes.
|
||||
var c = new Class(name: 'C', supertype: a.asThisSupertype);
|
||||
expect(hierarchy.applyChanges([b], [c]), same(hierarchy));
|
||||
expect(hierarchy.isSubclassOf(a, c), false);
|
||||
expect(hierarchy.isSubclassOf(c, a), true);
|
||||
expect(hierarchy.hasProperSubtypes(a), true);
|
||||
|
||||
// Remove so A should no longer be a super of anything.
|
||||
expect(hierarchy.applyChanges([c], []), same(hierarchy));
|
||||
expect(hierarchy.hasProperSubtypes(a), false);
|
||||
}
|
||||
|
||||
void test_getSingleTargetForInterfaceInvocation() {
|
||||
|
@ -84,12 +92,13 @@ abstract class E implements self::C {
|
|||
''');
|
||||
|
||||
ClosedWorldClassHierarchy cwch = hierarchy as ClosedWorldClassHierarchy;
|
||||
ClassHierarchySubtypes cwchst = cwch.computeSubtypesInformation();
|
||||
|
||||
expect(cwch.getSingleTargetForInterfaceInvocation(methodInA), methodInB);
|
||||
expect(cwch.getSingleTargetForInterfaceInvocation(methodInB),
|
||||
expect(cwchst.getSingleTargetForInterfaceInvocation(methodInA), methodInB);
|
||||
expect(cwchst.getSingleTargetForInterfaceInvocation(methodInB),
|
||||
null); // B::foo and D::foo
|
||||
expect(cwch.getSingleTargetForInterfaceInvocation(methodInD), methodInD);
|
||||
expect(cwch.getSingleTargetForInterfaceInvocation(methodInE),
|
||||
expect(cwchst.getSingleTargetForInterfaceInvocation(methodInD), methodInD);
|
||||
expect(cwchst.getSingleTargetForInterfaceInvocation(methodInE),
|
||||
null); // no concrete subtypes
|
||||
}
|
||||
|
||||
|
@ -129,15 +138,16 @@ class H extends self::G implements self::C, self::A {}
|
|||
''');
|
||||
|
||||
ClosedWorldClassHierarchy cwch = hierarchy as ClosedWorldClassHierarchy;
|
||||
ClassHierarchySubtypes cwchst = cwch.computeSubtypesInformation();
|
||||
|
||||
expect(cwch.getSubtypesOf(a), unorderedEquals([a, d, f, h]));
|
||||
expect(cwch.getSubtypesOf(b), unorderedEquals([b, e, f]));
|
||||
expect(cwch.getSubtypesOf(c), unorderedEquals([c, e, f, h]));
|
||||
expect(cwch.getSubtypesOf(d), unorderedEquals([d]));
|
||||
expect(cwch.getSubtypesOf(e), unorderedEquals([e, f]));
|
||||
expect(cwch.getSubtypesOf(f), unorderedEquals([f]));
|
||||
expect(cwch.getSubtypesOf(g), unorderedEquals([g, h]));
|
||||
expect(cwch.getSubtypesOf(h), unorderedEquals([h]));
|
||||
expect(cwchst.getSubtypesOf(a), unorderedEquals([a, d, f, h]));
|
||||
expect(cwchst.getSubtypesOf(b), unorderedEquals([b, e, f]));
|
||||
expect(cwchst.getSubtypesOf(c), unorderedEquals([c, e, f, h]));
|
||||
expect(cwchst.getSubtypesOf(d), unorderedEquals([d]));
|
||||
expect(cwchst.getSubtypesOf(e), unorderedEquals([e, f]));
|
||||
expect(cwchst.getSubtypesOf(f), unorderedEquals([f]));
|
||||
expect(cwchst.getSubtypesOf(g), unorderedEquals([g, h]));
|
||||
expect(cwchst.getSubtypesOf(h), unorderedEquals([h]));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -583,33 +593,6 @@ class Z {}
|
|||
expect(hierarchy.getClassAsInstanceOf(z, a), null);
|
||||
}
|
||||
|
||||
void test_getClassDepth() {
|
||||
var base = addClass(new Class(name: 'base', supertype: objectSuper));
|
||||
var extends_ =
|
||||
addClass(new Class(name: 'extends_', supertype: base.asThisSupertype));
|
||||
var with_ = addClass(new Class(
|
||||
name: 'with_',
|
||||
supertype: objectSuper,
|
||||
mixedInType: base.asThisSupertype));
|
||||
var implements_ = addClass(new Class(
|
||||
name: 'implements_',
|
||||
supertype: objectSuper,
|
||||
implementedTypes: [base.asThisSupertype]));
|
||||
|
||||
_assertTestLibraryText('''
|
||||
class base {}
|
||||
class extends_ extends self::base {}
|
||||
class with_ = core::Object with self::base {}
|
||||
class implements_ implements self::base {}
|
||||
''');
|
||||
|
||||
expect(hierarchy.getClassDepth(objectClass), 0);
|
||||
expect(hierarchy.getClassDepth(base), 1);
|
||||
expect(hierarchy.getClassDepth(extends_), 2);
|
||||
expect(hierarchy.getClassDepth(with_), 2);
|
||||
expect(hierarchy.getClassDepth(implements_), 2);
|
||||
}
|
||||
|
||||
/// Copy of the tests/language/least_upper_bound_expansive_test.dart test.
|
||||
void test_getClassicLeastUpperBound_expansive() {
|
||||
var int = coreTypes.intClass.rawType;
|
||||
|
@ -1291,32 +1274,6 @@ class B extends self::A {
|
|||
assertOrderOfClasses([c, b], [b, c]);
|
||||
}
|
||||
|
||||
void test_getRankedSuperclasses() {
|
||||
var a = addImplementsClass('A', []);
|
||||
var b = addImplementsClass('B', [a]);
|
||||
var c = addImplementsClass('C', [a]);
|
||||
var d = addImplementsClass('D', [c]);
|
||||
var e = addImplementsClass('E', [b, d]);
|
||||
|
||||
_assertTestLibraryText('''
|
||||
class A {}
|
||||
class B implements self::A {}
|
||||
class C implements self::A {}
|
||||
class D implements self::C {}
|
||||
class E implements self::B, self::D {}
|
||||
''');
|
||||
|
||||
expect(hierarchy.getRankedSuperclasses(a), [a, objectClass]);
|
||||
expect(hierarchy.getRankedSuperclasses(b), [b, a, objectClass]);
|
||||
expect(hierarchy.getRankedSuperclasses(c), [c, a, objectClass]);
|
||||
expect(hierarchy.getRankedSuperclasses(d), [d, c, a, objectClass]);
|
||||
if (hierarchy.getClassIndex(b) < hierarchy.getClassIndex(c)) {
|
||||
expect(hierarchy.getRankedSuperclasses(e), [e, d, b, c, a, objectClass]);
|
||||
} else {
|
||||
expect(hierarchy.getRankedSuperclasses(e), [e, d, c, b, a, objectClass]);
|
||||
}
|
||||
}
|
||||
|
||||
void test_getTypeAsInstanceOf_generic_extends() {
|
||||
var int = coreTypes.intClass.rawType;
|
||||
var bool = coreTypes.boolClass.rawType;
|
||||
|
|
|
@ -7,7 +7,7 @@ library vm.transformations.cha_devirtualization;
|
|||
import 'package:kernel/ast.dart';
|
||||
import 'package:kernel/core_types.dart' show CoreTypes;
|
||||
import 'package:kernel/class_hierarchy.dart'
|
||||
show ClassHierarchy, ClosedWorldClassHierarchy;
|
||||
show ClassHierarchy, ClassHierarchySubtypes, ClosedWorldClassHierarchy;
|
||||
|
||||
import '../metadata/direct_call.dart';
|
||||
|
||||
|
@ -15,9 +15,10 @@ import '../metadata/direct_call.dart';
|
|||
/// analysis. Assumes strong mode and closed world.
|
||||
Component transformComponent(CoreTypes coreTypes, Component component) {
|
||||
void ignoreAmbiguousSupertypes(Class cls, Supertype a, Supertype b) {}
|
||||
final hierarchy = new ClassHierarchy(component,
|
||||
ClosedWorldClassHierarchy hierarchy = new ClassHierarchy(component,
|
||||
onAmbiguousSupertypes: ignoreAmbiguousSupertypes);
|
||||
new CHADevirtualization(coreTypes, component, hierarchy)
|
||||
final hierarchySubtypes = hierarchy.computeSubtypesInformation();
|
||||
new CHADevirtualization(coreTypes, component, hierarchy, hierarchySubtypes)
|
||||
.visitComponent(component);
|
||||
return component;
|
||||
}
|
||||
|
@ -140,15 +141,16 @@ abstract class Devirtualization extends RecursiveVisitor<Null> {
|
|||
|
||||
/// Devirtualization based on the closed-world class hierarchy analysis.
|
||||
class CHADevirtualization extends Devirtualization {
|
||||
final ClosedWorldClassHierarchy _hierarchy;
|
||||
final ClassHierarchySubtypes _hierarchySubtype;
|
||||
|
||||
CHADevirtualization(CoreTypes coreTypes, Component component, this._hierarchy)
|
||||
: super(coreTypes, component, _hierarchy);
|
||||
CHADevirtualization(CoreTypes coreTypes, Component component,
|
||||
ClosedWorldClassHierarchy hierarchy, this._hierarchySubtype)
|
||||
: super(coreTypes, component, hierarchy);
|
||||
|
||||
@override
|
||||
DirectCallMetadata getDirectCall(TreeNode node, Member target,
|
||||
{bool setter = false}) {
|
||||
Member singleTarget = _hierarchy
|
||||
Member singleTarget = _hierarchySubtype
|
||||
.getSingleTargetForInterfaceInvocation(target, setter: setter);
|
||||
if (singleTarget == null) {
|
||||
return null;
|
||||
|
|
Loading…
Reference in a new issue