Files
sdk/pkg/kernel/lib/class_hierarchy.dart
T
Jens Johansen 0794821969 [kernel] Add tool for comparing class hierarchies of two dills
This CL adds a tool that can compare the class hierarchies of two dill files.
Currently it only checks for public classes and their public supertypes.
We could extend the tool later if needed.

Currently it gives this for flutter:

```
$ out/ReleaseX64/dart pkg/kernel/bin/compare_hierarchies.dart /path/to/flutter/bin/cache/flutter_web_sdk/kernel/flutter_ddc_sdk_sound.dill /path/to/flutter/bin/cache/artifacts/engine/common/flutter_patched_sdk/platform_strong.dill "dart:nativewrappers#NativeFieldWrapperClass1"
(1): /path/to/flutter/bin/cache/flutter_web_sdk/kernel/flutter_ddc_sdk_sound.dill
(2): /path/to/flutter/bin/cache/artifacts/engine/common/flutter_patched_sdk/platform_strong.dill

Missing classes in lib dart:_internal
In (2) but not in (1): [VMLibraryHooks, Lists, VMInternalsForTesting, ClassID]

Missing classes in lib dart:ui
In (1) but not in (2): [PlatformViewRegistry]

Class(ColorFilter) in dart.ui from (2) has these extra supertypes: [ImageFilter (dart:ui)]
```

where the last line is https://github.com/dart-lang/sdk/issues/48245

It gives this for the dart sdk:
```
$ out/ReleaseX64/dart pkg/kernel/bin/compare_hierarchies.dart out/ReleaseX64/vm_platform_strong.dill out/ReleaseX64/ddc_platform_sound.dill "dart:nativewrappers#NativeFieldWrapperClass1"
(1): out/ReleaseX64/vm_platform_strong.dill
(2): out/ReleaseX64/ddc_platform_sound.dill

Missing classes in lib dart:_internal
In (1) but not in (2): [VMLibraryHooks, Lists, VMInternalsForTesting, ClassID]
```

which seems fine (dart:_internal after all)

Change-Id: I03c0dbcc3e5058a0c7d6a996b511cf6d449dd4f3
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/231100
Reviewed-by: Johnni Winther <johnniwinther@google.com>
Commit-Queue: Jens Johansen <jensj@google.com>
2022-02-02 10:47:25 +00:00

1760 lines
62 KiB
Dart

// 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.
library kernel.class_hierarchy;
import 'dart:collection';
import 'dart:math';
import 'dart:typed_data';
import 'ast.dart';
import 'core_types.dart';
import 'type_algebra.dart';
import 'src/heap.dart';
import 'src/legacy_erasure.dart';
import 'src/nnbd_top_merge.dart';
import 'src/norm.dart';
typedef HandleAmbiguousSupertypes = void Function(Class, Supertype, Supertype);
abstract class MixinInferrer {
void infer(ClassHierarchy hierarchy, Class classNode);
}
/// Core interface for answering queries needed to compute the subtyping
/// relation.
abstract class ClassHierarchyBase {
CoreTypes get coreTypes;
/// Returns the instantiation of [superclass] that is implemented by [type],
/// or `null` if [type] does not implement [superclass] at all.
InterfaceType? getTypeAsInstanceOf(
InterfaceType type, Class superclass, Library clientLibrary);
/// Returns the type arguments of the instantiation of [superclass] that is
/// implemented by [type], or `null` if [type] does not implement [superclass]
/// at all.
List<DartType>? getTypeArgumentsAsInstanceOf(
InterfaceType type, Class superclass);
/// 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 "legacy" least upper bound to distinguish it from the
/// Dart 2 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 getLegacyLeastUpperBound(
InterfaceType type1, InterfaceType type2, Library clientLibrary);
}
abstract class ClassHierarchyMembers {
/// Returns the possibly abstract interface member of [class_] with the given
/// [name].
///
/// If [setter] is `false`, only fields, methods, and getters with that name
/// will be found. If [setter] is `true`, only non-final fields and setters
/// will be found.
///
/// If multiple members with that name are inherited and not overridden, the
/// member from the first declared supertype is returned.
Member? getInterfaceMember(Class class_, Name name, {bool setter: false});
}
/// Interface for answering various subclassing queries.
abstract class ClassHierarchy
implements ClassHierarchyBase, ClassHierarchyMembers {
factory ClassHierarchy(Component component, CoreTypes coreTypes,
{HandleAmbiguousSupertypes? onAmbiguousSupertypes,
MixinInferrer? mixinInferrer}) {
onAmbiguousSupertypes ??= (Class cls, Supertype a, Supertype b) {
// See https://github.com/dart-lang/sdk/issues/32091
throw "$cls can't implement both $a and $b";
};
return new ClosedWorldClassHierarchy._internal(
coreTypes, onAmbiguousSupertypes, mixinInferrer)
.._initialize(component.libraries);
}
void set coreTypes(CoreTypes coreTypes);
void set onAmbiguousSupertypes(
HandleAmbiguousSupertypes onAmbiguousSupertypes);
void set mixinInferrer(MixinInferrer mixinInferrer);
/// Given the [unordered] classes, return them in such order that classes
/// occur after their superclasses. If some superclasses are not in
/// [unordered], they are not included.
Iterable<Class> getOrderedClasses(Iterable<Class> unordered);
// Returns the instantiation of each generic supertype implemented by this
// class (e.g. getClassAsInstanceOf applied to all superclasses and
// interfaces).
List<Supertype> genericSupertypesOf(Class class_);
/// 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);
/// Returns the instantiation of [superclass] that is implemented by [type],
/// or `null` if [type] does not implement [superclass]. [superclass] must
/// be a generic class.
Supertype? asInstantiationOf(Supertype type, Class superclass);
/// Returns the instance member that would respond to a dynamic dispatch of
/// [name] to an instance of [class_], or `null` if no such member exists.
///
/// If [setter] is `false`, the name is dispatched as a getter or call,
/// and will return a field, getter, method, or operator (or null).
///
/// If [setter] is `true`, the name is dispatched as a setter, roughly
/// corresponding to `name=` in the Dart specification, but note that the
/// returned member will not have a name ending with `=`. In this case,
/// a non-final field or setter (or null) will be returned.
///
/// If the class is abstract, abstract members are ignored and the dispatch
/// is resolved if the class was not abstract.
Member? getDispatchTarget(Class class_, Name name, {bool setter: false});
/// Returns the list of potential targets of dynamic dispatch to an instance
/// of [class_].
///
/// If [setters] is `false`, only potential targets of a getter or call
/// dispatch are returned. If [setters] is `true`, only potential targets
/// of a setter dispatch are returned.
///
/// See [getDispatchTarget] for more details.
///
/// The returned list should not be modified.
List<Member> getDispatchTargets(Class class_, {bool setters: false});
/// Returns the list of members denoting the interface for [class_], which
/// may include abstract members.
///
/// The list may contain multiple members with a given name. This happens
/// when members are inherited through different supertypes and not overridden
/// in the class.
///
/// Also see [getInterfaceMember].
List<Member> getInterfaceMembers(Class class_, {bool setters: false});
/// Returns the list of members declared in [class_], including abstract
/// members.
///
/// Members are sorted by name so that they may be efficiently compared across
/// classes.
List<Member> getDeclaredMembers(Class class_, {bool setters: false});
/// True if [subclass] inherits from [superclass] though zero or more
/// `extends` relationships.
bool isSubclassOf(Class subclass, Class superclass);
/// True if [subtype] inherits from [superclass] though zero or more
/// `extends`, `with`, and `implements` relationships.
bool isSubtypeOf(Class subtype, Class superclass);
/// True if the given class is used as the right-hand operand to a
/// mixin application (i.e. [Class.mixedInType]).
bool isUsedAsMixin(Class class_);
/// True if the given class is extended by another class using `extends`.
bool isExtended(Class class_);
/// Returns the set of libraries for which this class hierarchy can be
/// queried.
///
/// Classes outside the set of known libraries are not part of the internal
/// model and queries about such classes will fail.
Iterable<Library> get knownLibraries;
/// 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_]).
///
/// We use the term "inheritable" for members that are candidates for
/// inheritance but may have been overridden. The "declared" members of a
/// mixin application are those declared in the mixed-in type. The callback is
/// invoked in the following cases:
///
/// 1. A member declared in the class overrides a member inheritable through
/// one of the supertypes of the class.
///
/// 2. A non-abstract member is inherited from a superclass, and in the
/// context of this class, it overrides an abstract member inheritable through
/// one of its superinterfaces.
///
/// 3. A non-abstract member is inherited from a superclass, and it overrides
/// an abstract member declared in this class.
///
/// This method will not report that a member overrides itself. A given pair
/// may be reported multiple times when there are multiple inheritance paths
/// to the overridden member.
///
/// It is possible for two methods to override one another in both directions.
///
/// By default getters and setters are overridden separately. The [isSetter]
/// callback parameter determines which type of access is being overridden.
void forEachOverridePair(Class class_,
callback(Member declaredMember, Member interfaceMember, bool isSetter));
/// This method is invoked by the client after a change: removal, addition,
/// or modification of classes (via libraries).
///
/// 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 it is the clients responsibility to mark all subclasses as
/// changed too.
// TODO(johnniwinther): Support class hierarchy changes directly. Currently
// we can handle added superclasses but not removed superclasses.
ClassHierarchy applyTreeChanges(Iterable<Library> removedLibraries,
Iterable<Library> ensureKnownLibraries, Iterable<Class> updatedClasses,
{Component reissueAmbiguousSupertypesFor});
/// This method is invoked by the client after a member change on classes:
/// Some of the information that this hierarchy might have cached,
/// is not valid anymore.
/// Note, that it is the clients responsibility to mark all subclasses as
/// changed too, or - if [findDescendants] is true, the ClassHierarchy will
/// spend the time to find them for the caller.
ClassHierarchy applyMemberChanges(Iterable<Class> classes,
{bool findDescendants: false});
/// Merges two sorted lists.
///
/// If a given member occurs in both lists, the merge will attempt to exclude
/// the duplicate member, but is not strictly guaranteed to do so.
///
/// The sort has the following stability properties:
///
/// - If both x and y came from the same input list, and x preceded y in the
/// input list, x will precede y in the output list. This holds even if x
/// and y have matching names.
///
/// - If m is a contiguous subsequence of the output list containing at least
/// one element from each input list, and all elements of m have matching
/// names, then the elements of m from [first] will precede the elements of
/// m from [second].
static List<Member> mergeSortedLists(
List<Member> first, List<Member> second) {
if (first.isEmpty) return second;
if (second.isEmpty) return first;
List<Member> result = new List<Member>.filled(
first.length + second.length, dummyMember,
growable: true);
int storeIndex = 0;
int i = 0, j = 0;
while (i < first.length && j < second.length) {
Member firstMember = first[i];
Member secondMember = second[j];
int compare = ClassHierarchy.compareMembers(firstMember, secondMember);
if (compare <= 0) {
result[storeIndex++] = firstMember;
++i;
// If the same member occurs in both lists, skip the duplicate.
if (identical(firstMember, secondMember)) {
++j;
}
} else {
result[storeIndex++] = secondMember;
++j;
}
}
while (i < first.length) {
result[storeIndex++] = first[i++];
}
while (j < second.length) {
result[storeIndex++] = second[j++];
}
result.length = storeIndex;
return result;
}
/// Compares members by name, using the same sort order as
/// [getDeclaredMembers] and [getInterfaceMembers].
static int compareMembers(Member first, Member second) {
if (first == second) return 0;
return compareNames(first.name, second.name);
}
/// Compares names, using the same sort order as [getDeclaredMembers] and
/// [getInterfaceMembers].
///
/// This is an arbitrary as-fast-as-possible sorting criterion.
static int compareNames(Name firstName, Name secondName) {
int firstHash = firstName.hashCode;
int secondHash = secondName.hashCode;
if (firstHash != secondHash) return firstHash - secondHash;
String firstString = firstName.text;
String secondString = secondName.text;
int firstLength = firstString.length;
int secondLength = secondString.length;
if (firstLength != secondLength) {
return firstLength - secondLength;
}
Library? firstLibrary = firstName.library;
Library? secondLibrary = secondName.library;
if (firstLibrary != secondLibrary) {
// ignore: unnecessary_null_comparison
if (firstLibrary == null) return -1;
// ignore: unnecessary_null_comparison
if (secondLibrary == null) return 1;
return firstLibrary.compareTo(secondLibrary);
}
for (int i = 0; i < firstLength; ++i) {
int firstUnit = firstString.codeUnitAt(i);
int secondUnit = secondString.codeUnitAt(i);
int delta = firstUnit - secondUnit;
if (delta != 0) return delta;
}
return 0;
}
/// Returns the member with the given name, or `null` if no member has the
/// name. In case the list contains multiple members with the given name,
/// the one that occurs first in the list is returned.
///
/// The list is assumed to be sorted according to [compareMembers].
static Member? findMemberByName(List<Member> members, Name name) {
int low = 0, high = members.length - 1;
while (low <= high) {
int mid = low + ((high - low) >> 1);
Member pivot = members[mid];
int comparison = compareNames(name, pivot.name);
if (comparison < 0) {
high = mid - 1;
} else if (comparison > 0) {
low = mid + 1;
} else if (high != mid) {
// Ensure we find the first element of the given name.
high = mid;
} else {
return pivot;
}
}
return null;
}
}
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.
late final Uint32List subtypeIntervalList;
_ClassInfoSubtype(this.classInfo);
}
class _ClosedWorldClassHierarchySubtypes implements ClassHierarchySubtypes {
final ClosedWorldClassHierarchy hierarchy;
final List<Class?> _classesByTopDownIndex;
final Map<Class, _ClassInfoSubtype> _infoMap = <Class, _ClassInfoSubtype>{};
bool invalidated = false;
_ClosedWorldClassHierarchySubtypes(this.hierarchy)
: _classesByTopDownIndex =
new List<Class?>.filled(hierarchy._infoMap.length, null) {
hierarchy.allBetsOff = true;
if (hierarchy._infoMap.isNotEmpty) {
for (Class class_ in hierarchy._infoMap.keys) {
_infoMap[class_] = new _ClassInfoSubtype(hierarchy._infoMap[class_]!);
}
_topDownSortVisit(_infoMap[hierarchy._infoMap.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;
_IntervalListBuilder subtypeSetBuilder = new _IntervalListBuilder()
..addSingleton(index);
for (_ClassInfo subtype in subInfo.classInfo.directExtenders) {
_ClassInfoSubtype subtypeInfo = _infoMap[subtype.classNode]!;
_topDownSortVisit(subtypeInfo);
subtypeSetBuilder.addIntervalList(subtypeInfo.subtypeIntervalList);
}
for (_ClassInfo subtype in subInfo.classInfo.directMixers) {
_ClassInfoSubtype subtypeInfo = _infoMap[subtype.classNode]!;
_topDownSortVisit(subtypeInfo);
subtypeSetBuilder.addIntervalList(subtypeInfo.subtypeIntervalList);
}
for (_ClassInfo subtype in subInfo.classInfo.directImplementers) {
_ClassInfoSubtype subtypeInfo = _infoMap[subtype.classNode]!;
_topDownSortVisit(subtypeInfo);
subtypeSetBuilder.addIntervalList(subtypeInfo.subtypeIntervalList);
}
subInfo.subtypeIntervalList = subtypeSetBuilder.buildIntervalList();
}
@override
Member? getSingleTargetForInterfaceInvocation(Member interfaceTarget,
{bool setter: false}) {
if (invalidated) throw "This data structure 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 data structure has been invalidated";
Set<Class> result = new Set<Class>();
Uint32List list = _infoMap[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 {
@override
CoreTypes coreTypes;
late HandleAmbiguousSupertypes _onAmbiguousSupertypes;
late HandleAmbiguousSupertypes _onAmbiguousSupertypesNotWrapped;
MixinInferrer? mixinInferrer;
@override
void set onAmbiguousSupertypes(
HandleAmbiguousSupertypes onAmbiguousSupertypes) {
_onAmbiguousSupertypesNotWrapped = onAmbiguousSupertypes;
_onAmbiguousSupertypes = (Class class_, Supertype a, Supertype b) {
onAmbiguousSupertypes(class_, a, b);
List<Supertype>? recorded = _recordedAmbiguousSupertypes[class_];
if (recorded == null) {
recorded = <Supertype>[];
_recordedAmbiguousSupertypes[class_] = recorded;
}
recorded.add(a);
recorded.add(b);
};
}
/// The insert order is important.
final Map<Class, _ClassInfo> _infoMap =
new LinkedHashMap<Class, _ClassInfo>();
List<ForTestingClassInfo> getTestingClassInfo() {
List<ForTestingClassInfo> result = <ForTestingClassInfo>[];
for (_ClassInfo info in _infoMap.values) {
result.add(new ForTestingClassInfo._(info));
}
return result;
}
List<Class> getAllSupertypeClassesForTesting(Class class_) {
List<Class?> allClassesByIndex =
new List<Class?>.filled(_infoMap.length, null);
for (MapEntry<Class, _ClassInfo> c in _infoMap.entries) {
allClassesByIndex[c.value.topologicalIndex] = c.key;
}
List<Class> result = [];
Uint32List list = _infoMap[class_]!.supertypeIntervalList;
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(allClassesByIndex[j]!);
}
}
return result;
}
_ClassInfo infoFor(Class cls) {
_ClassInfo? info = _infoMap[cls];
if (info == null) {
throw "${cls.fileUri}: No class info for ${cls.name}";
}
info.used = true;
return info;
}
List<Class> getUsedClasses() {
List<Class> result = <Class>[];
for (_ClassInfo classInfo in _infoMap.values) {
if (classInfo.used) {
result.add(classInfo.classNode);
}
}
return result;
}
void resetUsed() {
for (_ClassInfo classInfo in _infoMap.values) {
classInfo.used = false;
}
allBetsOff = false;
}
@override
final Set<Library> knownLibraries = new Set<Library>();
bool allBetsOff = false;
/// Recorded errors for classes we have already calculated the class hierarchy
/// for, but will have to be reissued when re-using the calculation.
final Map<Class, List<Supertype>> _recordedAmbiguousSupertypes =
new LinkedHashMap<Class, List<Supertype>>();
Iterable<Class> get classes {
allBetsOff = true;
return _infoMap.keys;
}
int get numberOfClasses {
allBetsOff = true;
return _infoMap.length;
}
_ClosedWorldClassHierarchySubtypes? _cachedClassHierarchySubtypes;
ClosedWorldClassHierarchy._internal(this.coreTypes,
HandleAmbiguousSupertypes onAmbiguousSupertypes, this.mixinInferrer) {
this.onAmbiguousSupertypes = onAmbiguousSupertypes;
}
ClassHierarchySubtypes computeSubtypesInformation() {
_cachedClassHierarchySubtypes ??=
new _ClosedWorldClassHierarchySubtypes(this);
return _cachedClassHierarchySubtypes!;
}
@override
Iterable<Class> getOrderedClasses(Iterable<Class> unordered) {
Set<Class> unorderedSet = unordered.toSet();
for (Class c in unordered) {
_infoMap[c]?.used = true;
}
return _infoMap.keys.where(unorderedSet.contains);
}
@override
bool isSubclassOf(Class subclass, Class superclass) {
if (identical(subclass, superclass)) return true;
return infoFor(subclass).isSubclassOf(infoFor(superclass));
}
@override
bool isSubtypeOf(Class subtype, Class superclass) {
if (identical(subtype, superclass)) return true;
return infoFor(subtype).isSubtypeOf(infoFor(superclass));
}
@override
bool isUsedAsMixin(Class class_) {
return infoFor(class_).directMixers.isNotEmpty;
}
@override
bool isExtended(Class class_) {
return infoFor(class_).directExtenders.isNotEmpty;
}
List<_ClassInfo> _getRankedSuperclassInfos(_ClassInfo info) {
if (info.leastUpperBoundInfos != null) return info.leastUpperBoundInfos!;
_LubHeap heap = new _LubHeap()..add(info);
List<_ClassInfo> chain = <_ClassInfo>[];
info.leastUpperBoundInfos = chain;
_ClassInfo? lastInfo = null;
while (heap.isNotEmpty) {
_ClassInfo nextInfo = heap.remove();
if (identical(nextInfo, lastInfo)) continue;
chain.add(nextInfo);
lastInfo = nextInfo;
Class classNode = nextInfo.classNode;
void addToHeap(Supertype supertype) {
heap.add(infoFor(supertype.classNode));
}
Supertype? supertype = classNode.supertype;
if (supertype != null) {
addToHeap(supertype);
}
Supertype? mixedInType = classNode.mixedInType;
if (mixedInType != null) {
addToHeap(mixedInType);
}
classNode.implementedTypes.forEach(addToHeap);
}
return chain;
}
@override
InterfaceType getLegacyLeastUpperBound(
InterfaceType type1, InterfaceType type2, Library clientLibrary) {
// 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.
// LLUB(Null, List<dynamic>*) works differently for opt-in and opt-out
// libraries. In opt-out libraries the legacy behavior is preserved, so
// LLUB(Null, List<dynamic>*) = List<dynamic>*. In opt-in libraries the
// rules imply that LLUB(Null, List<dynamic>*) = List<dynamic>?.
if (!clientLibrary.isNonNullableByDefault) {
if (type1 is NullType) return type2;
if (type2 is NullType) return type1;
}
_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.classNode.isAnonymousMixin) {
// Never find unnamed mixin application in least upper bound.
continue;
}
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 = coreTypes.rawType(next.classNode,
uniteNullabilities(type1.nullability, type2.nullability));
if (currentDepth == 0) return candidate;
++numCandidatesAtThisDepth;
} else {
InterfaceType superType1 = identical(info1, next)
? type1
: Substitution.fromInterfaceType(type1).substituteType(
info1.genericSuperType![next.classNode]!.asInterfaceType)
as InterfaceType;
InterfaceType superType2 = identical(info2, next)
? type2
: Substitution.fromInterfaceType(type2).substituteType(
info2.genericSuperType![next.classNode]!.asInterfaceType)
as InterfaceType;
if (!clientLibrary.isNonNullableByDefault) {
superType1 = legacyErasure(superType1) as InterfaceType;
superType2 = legacyErasure(superType2) as InterfaceType;
}
if (superType1 == superType2) {
candidate = superType1.withDeclaredNullability(
uniteNullabilities(type1.nullability, type2.nullability));
++numCandidatesAtThisDepth;
}
}
}
}
@override
Supertype? getClassAsInstanceOf(Class class_, Class superclass) {
if (identical(class_, superclass)) return class_.asThisSupertype;
_ClassInfo info = infoFor(class_);
_ClassInfo superInfo = infoFor(superclass);
if (!info.isSubtypeOf(superInfo)) return null;
if (superclass.typeParameters.isEmpty) return superclass.asRawSupertype;
assert(info.genericSuperType!.containsKey(superclass),
"No canonical instance of $superclass found for $class_.");
return info.genericSuperType![superclass];
}
@override
InterfaceType? getTypeAsInstanceOf(
InterfaceType type, Class superclass, Library clientLibrary) {
List<DartType>? typeArguments =
getTypeArgumentsAsInstanceOf(type, superclass);
if (typeArguments == null) return null;
// The return value should be a legacy type if it's computed for an
// opted-out library, unless the return value is Null? which is always
// nullable.
Nullability nullability = clientLibrary.isNonNullableByDefault
? type.nullability
: Nullability.legacy;
return new InterfaceType(superclass, nullability, typeArguments);
}
@override
List<DartType>? getTypeArgumentsAsInstanceOf(
InterfaceType type, Class superclass) {
if (type.classNode == superclass) {
// TODO(johnniwinther): This is necessary because [getClassAsInstanceOf]
// returns a [Supertype] whose type arguments are type parameter types
// whose nullability is set to the default nullability of the
// enclosing library. If for instance [type] is `A<int!>` but `A` is
// declared in an opt-out library, the substitution below will combine
// nullabilities of the type arguments in [type] with the type parameters
// and thus give the result `A<int*>`. See issue #42792.
// For now we bypass the substitution but long term we need to ensure
// that [getClassAsInstanceOf] doesn't cause similar problems in other
// situations.
return type.typeArguments;
}
Supertype? castedType = getClassAsInstanceOf(type.classNode, superclass);
if (castedType == null) return null;
if (superclass.typeParameters.isEmpty) return const <DartType>[];
return Substitution.fromInterfaceType(type)
.substituteSupertype(castedType)
.typeArguments;
}
@override
Member? getDispatchTarget(Class class_, Name name, {bool setter: false}) {
List<Member> list =
_buildImplementedMembers(class_, infoFor(class_), setters: setter);
Member? member = ClassHierarchy.findMemberByName(list, name);
assert(
member == null || !member.isAbstract,
"Abstract member $member found as dispatch target "
"for $name on $class_");
return member;
}
@override
List<Member> getDispatchTargets(Class class_, {bool setters: false}) {
return _buildImplementedMembers(class_, infoFor(class_), setters: setters);
}
@override
Member? getInterfaceMember(Class class_, Name name, {bool setter: false}) {
List<Member> list = getInterfaceMembers(class_, setters: setter);
return ClassHierarchy.findMemberByName(list, name);
}
@override
List<Member> getInterfaceMembers(Class class_, {bool setters: false}) {
return _buildInterfaceMembers(class_, infoFor(class_), setters: setters);
}
@override
List<Member> getDeclaredMembers(Class class_, {bool setters: false}) {
return _buildDeclaredMembers(class_, infoFor(class_), setters: setters);
}
@override
void forEachOverridePair(Class class_,
callback(Member declaredMember, Member interfaceMember, bool isSetter),
{bool crossGettersSetters: false}) {
_ClassInfo info = infoFor(class_);
for (Supertype supertype in class_.supers) {
Class superclass = supertype.classNode;
List<Member> superGetters = getInterfaceMembers(superclass);
List<Member> superSetters =
getInterfaceMembers(superclass, setters: true);
_reportOverrides(_buildDeclaredMembers(class_, info, setters: false),
superGetters, callback);
_reportOverrides(_buildDeclaredMembers(class_, info, setters: true),
superSetters, callback,
isSetter: true);
}
}
static void _reportOverrides(
List<Member> declaredList,
List<Member> inheritedList,
callback(Member declaredMember, Member interfaceMember, bool isSetter),
{bool isSetter: false}) {
int i = 0, j = 0;
while (i < declaredList.length && j < inheritedList.length) {
Member declared = declaredList[i];
Member inherited = inheritedList[j];
int comparison = ClassHierarchy.compareMembers(declared, inherited);
if (comparison < 0) {
++i;
} else if (comparison > 0) {
++j;
} else {
if (!identical(declared, inherited)) {
callback(declared, inherited, isSetter);
}
// A given declared member may override multiple interface members,
// so only move past the interface member.
++j;
}
}
}
@override
List<Supertype> genericSupertypesOf(Class class_) {
Map<Class, Supertype>? supertypes = infoFor(class_).genericSuperType;
if (supertypes == null) return const <Supertype>[];
return supertypes.values.toList();
}
@override
ClassHierarchy applyTreeChanges(Iterable<Library> removedLibraries,
Iterable<Library> ensureKnownLibraries, Iterable<Class> updatedClasses,
{Component? reissueAmbiguousSupertypesFor}) {
Set<_ClassInfo> changedClasses = <_ClassInfo>{};
void removeClass(Class cls) {
_ClassInfo? info = _infoMap[cls];
if (info == null) return;
Supertype? supertype = cls.supertype;
if (supertype != null) {
_infoMap[supertype.classNode]?.directExtenders.remove(info);
}
Supertype? mixedInType = cls.mixedInType;
if (mixedInType != null) {
_infoMap[mixedInType.classNode]?.directMixers.remove(info);
}
for (Supertype supertype in cls.implementedTypes) {
_infoMap[supertype.classNode]?.directImplementers.remove(info);
// Remove from directMixers too as the mixin transformation will
// "move" the type here.
if (cls.isAnonymousMixin || cls.isEliminatedMixin) {
_infoMap[supertype.classNode]?.directMixers.remove(info);
}
}
_infoMap.remove(cls);
_recordedAmbiguousSupertypes.remove(cls);
}
void invalidateClass(_ClassInfo? info) {
if (info == null) return;
if (!changedClasses.add(info)) return;
for (_ClassInfo i in info.directExtenders.toList()) {
invalidateClass(i);
}
for (_ClassInfo i in info.directMixers.toList()) {
invalidateClass(i);
}
for (_ClassInfo i in info.directImplementers.toList()) {
invalidateClass(i);
}
removeClass(info.classNode);
}
for (Class cls in updatedClasses) {
invalidateClass(_infoMap[cls]);
}
// Remove all references to the removed classes.
for (Library lib in removedLibraries) {
if (!knownLibraries.contains(lib)) continue;
for (Class class_ in lib.classes) {
removeClass(class_);
}
knownLibraries.remove(lib);
}
// If we have a cached computation of subtypes, invalidate it and stop
// caching it.
if (_cachedClassHierarchySubtypes != null) {
_cachedClassHierarchySubtypes!.invalidated = true;
}
if (_recordedAmbiguousSupertypes.isNotEmpty &&
reissueAmbiguousSupertypesFor != null) {
Set<Library> libs =
new Set<Library>.of(reissueAmbiguousSupertypesFor.libraries);
for (Class class_ in _recordedAmbiguousSupertypes.keys) {
if (!libs.contains(class_.enclosingLibrary)) continue;
List<Supertype> recorded = _recordedAmbiguousSupertypes[class_]!;
for (int i = 0; i < recorded.length; i += 2) {
_onAmbiguousSupertypesNotWrapped(
class_, recorded[i], recorded[i + 1]);
}
}
}
// Add the new classes.
List<Class> addedClassesSorted = <Class>[];
int expectedStartIndex = _topSortIndex;
for (Library lib in ensureKnownLibraries) {
if (knownLibraries.contains(lib)) continue;
for (Class class_ in lib.classes) {
_topologicalSortVisit(class_, new Set<Class>(),
orderedList: addedClassesSorted);
}
knownLibraries.add(lib);
}
for (_ClassInfo info in changedClasses) {
_topologicalSortVisit(info.classNode, new Set<Class>(),
orderedList: addedClassesSorted);
}
_initializeTopologicallySortedClasses(
addedClassesSorted, expectedStartIndex);
assert(sanityChecks());
return this;
}
@override
ClassHierarchy applyMemberChanges(Iterable<Class> classes,
{bool findDescendants: false}) {
if (classes.isEmpty) return this;
List<_ClassInfo> infos = <_ClassInfo>[];
if (findDescendants) {
Set<_ClassInfo> processedClasses = new Set<_ClassInfo>();
List<_ClassInfo> worklist = <_ClassInfo>[];
for (Class class_ in classes) {
_ClassInfo info = infoFor(class_);
worklist.add(info);
}
while (worklist.isNotEmpty) {
_ClassInfo info = worklist.removeLast();
if (processedClasses.add(info)) {
worklist.addAll(info.directExtenders);
worklist.addAll(info.directImplementers);
worklist.addAll(info.directMixers);
}
}
infos.addAll(processedClasses);
} else {
for (Class class_ in classes) {
_ClassInfo info = infoFor(class_);
infos.add(info);
}
}
for (_ClassInfo info in infos) {
info.lazyDeclaredGettersAndCalls = null;
info.lazyDeclaredSetters = null;
info.lazyImplementedGettersAndCalls = null;
info.lazyImplementedSetters = null;
info.lazyInterfaceGettersAndCalls = null;
info.lazyInterfaceSetters = null;
}
assert(sanityChecks());
return this;
}
bool sanityChecks() {
Map<String, List<Class>> map = {};
for (Class c in _infoMap.keys) {
String className = "${c.enclosingLibrary.importUri}::${c.name}";
List<Class>? list = map[className];
if (list == null) {
map[className] = list = [];
}
list.add(c);
}
StringBuffer? sb;
for (MapEntry<String, List<Class>> entry in map.entries) {
if (entry.value.length != 1) {
sb ??= new StringBuffer();
sb.writeln("Found ${entry.value.length} entries for ${entry.key}");
}
}
if (sb != null) throw new StateError(sb.toString());
for (Class c in _infoMap.keys) {
if (!knownLibraries.contains(c.enclosingLibrary)) {
throw new StateError("Didn't know library of $c (from ${c.fileUri})");
}
}
for (_ClassInfo info in _infoMap.values) {
for (_ClassInfo subInfo in info.directExtenders) {
if (!_infoMap.containsKey(subInfo.classNode)) {
throw new StateError(
"Found $subInfo (${subInfo.classNode}) in directExtenders");
}
}
for (_ClassInfo subInfo in info.directMixers) {
if (!_infoMap.containsKey(subInfo.classNode)) {
throw new StateError(
"Found $subInfo (${subInfo.classNode}) in directMixers");
}
}
for (_ClassInfo subInfo in info.directImplementers) {
if (!_infoMap.containsKey(subInfo.classNode)) {
throw new StateError(
"Found $subInfo (${subInfo.classNode}) in directImplementers");
}
}
}
return true;
}
@override
Supertype? asInstantiationOf(Supertype type, Class superclass) {
// This is similar to getTypeAsInstanceOf, except that it assumes that
// superclass is a generic class. It thus does not rely on being able
// to answer isSubtypeOf queries and so can be used before we have built
// the intervals needed for those queries.
assert(superclass.typeParameters.isNotEmpty);
if (type.classNode == superclass) {
return superclass.asThisSupertype;
}
Map<Class, Supertype>? map = infoFor(type.classNode).genericSuperType;
return map == null ? null : map[superclass];
}
void _initialize(List<Library> libraries) {
// Build the class ordering based on a topological sort.
for (Library library in libraries) {
for (Class classNode in library.classes) {
_topologicalSortVisit(classNode, new Set<Class>());
}
knownLibraries.add(library);
}
_initializeTopologicallySortedClasses(_infoMap.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 _initializeTopologicallySortedClasses(
Iterable<Class> classes, int expectedStartingTopologicalIndex) {
int i = expectedStartingTopologicalIndex;
for (Class class_ in classes) {
_ClassInfo? info = _infoMap[class_];
if (info == null) {
throw "No info for ${class_.name} from ${class_.fileUri}.";
}
if (class_.supertype != null) {
_infoMap[class_.supertype!.classNode]!.directExtenders.add(info);
}
if (class_.mixedInType != null) {
_infoMap[class_.mixedInType!.classNode]!.directMixers.add(info);
}
for (Supertype supertype in class_.implementedTypes) {
_infoMap[supertype.classNode]!.directImplementers.add(info);
}
_collectSupersForClass(class_);
Supertype? supertype = class_.supertype;
if (supertype != null) {
_recordSuperTypes(info, supertype);
}
Supertype? mixedInType = class_.mixedInType;
if (mixedInType != null) {
mixinInferrer?.infer(this, class_);
_recordSuperTypes(info, mixedInType);
}
for (Supertype supertype in class_.implementedTypes) {
_recordSuperTypes(info, supertype);
}
if (info.topologicalIndex != i) {
throw "Unexpected topologicalIndex (${info.topologicalIndex} != $i) "
"for ${class_.name} from ${class_.fileUri}.";
}
i++;
}
}
/// 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;
int _topologicalSortVisit(Class classNode, Set<Class> beingVisited,
{List<Class>? orderedList}) {
_ClassInfo? info = _infoMap[classNode];
if (info != null) {
return info.depth;
}
if (!beingVisited.add(classNode)) {
throw 'Cyclic inheritance involving ${classNode.name}';
}
info = new _ClassInfo(classNode);
int superDepth = -1;
if (classNode.supertype != null) {
superDepth = max(
superDepth,
_topologicalSortVisit(classNode.supertype!.classNode, beingVisited,
orderedList: orderedList));
}
if (classNode.mixedInType != null) {
superDepth = max(
superDepth,
_topologicalSortVisit(classNode.mixedInType!.classNode, beingVisited,
orderedList: orderedList));
}
for (Supertype supertype in classNode.implementedTypes) {
superDepth = max(
superDepth,
_topologicalSortVisit(supertype.classNode, beingVisited,
orderedList: orderedList));
}
info.topologicalIndex = _topSortIndex++;
_infoMap[classNode] = info;
orderedList?.add(classNode);
beingVisited.remove(classNode);
return info.depth = superDepth + 1;
}
List<Member> _buildImplementedMembers(Class classNode, _ClassInfo info,
{required bool setters}) {
List<Member>? members = setters
? info.lazyImplementedSetters
: info.lazyImplementedGettersAndCalls;
if (members != null) return members;
List<Member> inherited;
Supertype? supertype = classNode.supertype;
if (supertype == null) {
inherited = const <Member>[];
} else {
Class superClassNode = supertype.classNode;
_ClassInfo superInfo = _infoMap[superClassNode]!;
inherited =
_buildImplementedMembers(superClassNode, superInfo, setters: setters);
}
members = _inheritMembers(
_buildDeclaredMembers(classNode, info, setters: setters), inherited,
skipAbstractMembers: true);
if (setters) {
info.lazyImplementedSetters = members;
} else {
info.lazyImplementedGettersAndCalls = members;
}
return members;
}
List<Member> _buildDeclaredMembers(Class classNode, _ClassInfo info,
{required bool setters}) {
List<Member>? members =
setters ? info.lazyDeclaredSetters : info.lazyDeclaredGettersAndCalls;
if (members != null) return members;
// To support that mixin application can declare their own members, for
// instance cloned mixin members and concrete forwarding stubs, we first
// collect the members in a map before creating the list of members, so that
// declared members can replace mixed in members.
Map<Name, Member> memberMap = {};
if (classNode.mixedInType != null) {
Class mixedInClassNode = classNode.mixedInType!.classNode;
_ClassInfo mixedInInfo = _infoMap[mixedInClassNode]!;
for (Member mixinMember in _buildDeclaredMembers(
mixedInClassNode, mixedInInfo,
setters: setters)) {
if (mixinMember is! Procedure || !mixinMember.isSynthetic) {
memberMap[mixinMember.name] = mixinMember;
}
}
}
for (Procedure procedure in classNode.procedures) {
if (procedure.isStatic) continue;
if (procedure.kind == ProcedureKind.Setter) {
if (setters) {
memberMap[procedure.name] = procedure;
}
} else {
if (!setters) {
memberMap[procedure.name] = procedure;
}
}
}
for (Field field in classNode.fields) {
if (field.isStatic) continue;
if (!setters) {
memberMap[field.name] = field;
}
if (setters && field.hasSetter) {
memberMap[field.name] = field;
}
}
members = memberMap.values.toList();
members.sort(ClassHierarchy.compareMembers);
if (setters) {
info.lazyDeclaredSetters = members;
} else {
info.lazyDeclaredGettersAndCalls = members;
}
return members;
}
List<Member> _buildInterfaceMembers(Class classNode, _ClassInfo info,
{required bool setters}) {
List<Member>? members =
setters ? info.lazyInterfaceSetters : info.lazyInterfaceGettersAndCalls;
if (members != null) return members;
List<Member> allInheritedMembers = <Member>[];
List<Member> declared =
_buildDeclaredMembers(classNode, info, setters: setters);
void inheritFrom(Supertype? type) {
if (type == null) return;
List<Member> inherited = _buildInterfaceMembers(
type.classNode, _infoMap[type.classNode]!,
setters: setters);
inherited = _getUnshadowedInheritedMembers(declared, inherited);
allInheritedMembers =
ClassHierarchy.mergeSortedLists(allInheritedMembers, inherited);
}
inheritFrom(classNode.supertype);
inheritFrom(classNode.mixedInType);
classNode.implementedTypes.forEach(inheritFrom);
members = _inheritMembers(declared, allInheritedMembers);
if (setters) {
info.lazyInterfaceSetters = members;
} else {
info.lazyInterfaceGettersAndCalls = members;
}
return members;
}
/// Computes the list of implemented members, based on the declared instance
/// members and inherited instance members.
///
/// Both lists must be sorted by name beforehand.
static List<Member> _inheritMembers(
List<Member> declared, List<Member> inherited,
{bool skipAbstractMembers: false}) {
List<Member> result = new List<Member>.filled(
declared.length + inherited.length, dummyMember,
growable: true);
// Since both lists are sorted, we can fuse them like in merge sort.
int storeIndex = 0;
int i = 0, j = 0;
while (i < declared.length && j < inherited.length) {
Member declaredMember = declared[i];
Member inheritedMember = inherited[j];
if (skipAbstractMembers && declaredMember.isAbstract) {
++i;
continue;
}
if (skipAbstractMembers && inheritedMember.isAbstract) {
++j;
continue;
}
int comparison =
ClassHierarchy.compareMembers(declaredMember, inheritedMember);
if (comparison < 0) {
result[storeIndex++] = declaredMember;
++i;
} else if (comparison > 0) {
result[storeIndex++] = inheritedMember;
++j;
} else {
result[storeIndex++] = declaredMember;
++i;
++j; // Move past overridden member.
}
}
// One of the two lists is now exhausted, copy over the remains.
while (i < declared.length) {
Member declaredMember = declared[i++];
if (skipAbstractMembers && declaredMember.isAbstract) continue;
result[storeIndex++] = declaredMember;
}
while (j < inherited.length) {
Member inheritedMember = inherited[j++];
if (skipAbstractMembers && inheritedMember.isAbstract) continue;
result[storeIndex++] = inheritedMember;
}
result.length = storeIndex;
return result;
}
/// Returns the subset of members in [inherited] for which a member with the
/// same name does not occur in [declared].
///
/// The input lists must be sorted, and the returned list is sorted.
static List<Member> _getUnshadowedInheritedMembers(
List<Member> declared, List<Member> inherited) {
List<Member> result =
new List<Member>.filled(inherited.length, dummyMember, growable: true);
int storeIndex = 0;
int i = 0, j = 0;
while (i < declared.length && j < inherited.length) {
Member declaredMember = declared[i];
Member inheritedMember = inherited[j];
int comparison =
ClassHierarchy.compareMembers(declaredMember, inheritedMember);
if (comparison < 0) {
++i;
} else if (comparison > 0) {
result[storeIndex++] = inheritedMember;
++j;
} else {
// Move past the shadowed member, but retain the declared member, as
// it may shadow multiple members.
++j;
}
}
// If the list of declared members is exhausted, copy over the remains of
// the inherited members.
while (j < inherited.length) {
result[storeIndex++] = inherited[j++];
}
result.length = storeIndex;
return result;
}
void _recordSuperTypes(_ClassInfo subInfo, Supertype supertype) {
_ClassInfo superInfo = _infoMap[supertype.classNode]!;
if (supertype.typeArguments.isEmpty) {
if (superInfo.genericSuperTypes == null) return;
// Copy over the super type entries.
subInfo.genericSuperType ??= <Class, Supertype>{};
subInfo.genericSuperTypes ??= <Class, List<Supertype>>{};
superInfo.genericSuperType?.forEach((Class key, Supertype type) {
subInfo.recordGenericSuperType(
coreTypes, key, type, _onAmbiguousSupertypes);
});
} else {
// Copy over all transitive generic super types, and substitute the
// free variables with those provided in [supertype].
Class superclass = supertype.classNode;
Substitution substitution = Substitution.fromPairs(
superclass.typeParameters, supertype.typeArguments);
subInfo.genericSuperType ??= <Class, Supertype>{};
subInfo.genericSuperTypes ??= <Class, List<Supertype>>{};
superInfo.genericSuperType?.forEach((Class key, Supertype type) {
subInfo.recordGenericSuperType(coreTypes, key,
substitution.substituteSupertype(type), _onAmbiguousSupertypes);
});
subInfo.recordGenericSuperType(
coreTypes, superclass, supertype, _onAmbiguousSupertypes);
}
}
/// Build lists of super types and super classes.
/// Note that the super class and super types of the class must already have
/// had their supers collected.
void _collectSupersForClass(Class class_) {
_ClassInfo info = _infoMap[class_]!;
_IntervalListBuilder superclassSetBuilder = new _IntervalListBuilder()
..addSingleton(info.topologicalIndex);
_IntervalListBuilder supertypeSetBuilder = new _IntervalListBuilder()
..addSingleton(info.topologicalIndex);
if (class_.supertype != null) {
_ClassInfo supertypeInfo = _infoMap[class_.supertype!.classNode]!;
superclassSetBuilder
.addIntervalList(supertypeInfo.superclassIntervalList);
supertypeSetBuilder.addIntervalList(supertypeInfo.supertypeIntervalList);
}
if (class_.mixedInType != null) {
_ClassInfo mixedInTypeInfo = _infoMap[class_.mixedInType!.classNode]!;
supertypeSetBuilder
.addIntervalList(mixedInTypeInfo.supertypeIntervalList);
}
for (Supertype supertype in class_.implementedTypes) {
_ClassInfo supertypeInfo = _infoMap[supertype.classNode]!;
supertypeSetBuilder.addIntervalList(supertypeInfo.supertypeIntervalList);
}
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 supertype set.
///
/// The more numbers are condensed near the beginning, the more efficient the
/// internal data structure is.
List<int> getExpenseHistogram() {
List<int> result = <int>[];
for (Class class_ in _infoMap.keys) {
_ClassInfo info = _infoMap[class_]!;
int intervals = info.supertypeIntervalList.length ~/ 2;
while (result.length <= intervals) {
result.add(0);
}
result[intervals] += 1;
}
return result;
}
/// 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 superclass/supertype pairs.
double getCompressionRatio() {
int intervals = 0;
int sizes = 0;
for (Class class_ in _infoMap.keys) {
_ClassInfo info = _infoMap[class_]!;
intervals += (info.superclassIntervalList.length +
info.supertypeIntervalList.length) ~/
2;
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 _infoMap.keys) {
sum += _infoMap[class_]!.genericSuperTypes?.length ?? 0;
}
return sum;
}
}
class _IntervalListBuilder {
final List<int> events = <int>[];
void addInterval(int start, int end) {
// Add an event point for each interval end point, using the low bit to
// distinguish opening from closing end points. Closing end points should
// have the high bit to ensure they occur after an opening end point.
events.add(start << 1);
events.add((end << 1) + 1);
}
void addSingleton(int x) {
addInterval(x, x + 1);
}
void addIntervalList(Uint32List intervals) {
for (int i = 0; i < intervals.length; i += 2) {
addInterval(intervals[i], intervals[i + 1]);
}
}
Uint32List buildIntervalList() {
// Sort the event points and sweep left to right while tracking how many
// intervals we are currently inside. Record an interval end point when the
// number of intervals drop to zero or increase from zero to one.
// Event points are encoded so that an opening end point occur before a
// closing end point at the same value.
events.sort();
int insideCount = 0; // The number of intervals we are currently inside.
int storeIndex = 0;
for (int i = 0; i < events.length; ++i) {
int event = events[i];
if (event & 1 == 0) {
// Start point
++insideCount;
if (insideCount == 1) {
// Store the results temporarily back in the event array.
events[storeIndex++] = event >> 1;
}
} else {
// End point
--insideCount;
if (insideCount == 0) {
events[storeIndex++] = event >> 1;
}
}
}
// Copy the results over to a typed array of the correct length.
Uint32List result = new Uint32List(storeIndex);
for (int i = 0; i < storeIndex; ++i) {
result[i] = events[i];
}
return result;
}
}
bool _intervalListContains(Uint32List intervalList, int x) {
int low = 0, high = intervalList.length - 1;
if (high == -1 || x < intervalList[0] || intervalList[high] <= x) {
return false;
}
// Find the lower bound of x in the list.
// If the lower bound is at an even index, the lower bound is an opening point
// of an interval that contains x, otherwise it is a closing point of an
// interval below x and there is no interval containing x.
while (low < high) {
int mid = high - ((high - low) >> 1); // Get middle, rounding up.
int pivot = intervalList[mid];
if (pivot <= x) {
low = mid;
} else {
high = mid - 1;
}
}
return low == high && (low & 1) == 0;
}
int _intervalListSize(Uint32List intervalList) {
int size = 0;
for (int i = 0; i < intervalList.length; i += 2) {
size += intervalList[i + 1] - intervalList[i];
}
return size;
}
class ForTestingClassInfo {
final Class classNode;
final List<Member>? lazyDeclaredGettersAndCalls;
final List<Member>? lazyDeclaredSetters;
final List<Member>? lazyImplementedGettersAndCalls;
final List<Member>? lazyImplementedSetters;
final List<Member>? lazyInterfaceGettersAndCalls;
final List<Member>? lazyInterfaceSetters;
ForTestingClassInfo._(_ClassInfo c)
: classNode = c.classNode,
lazyDeclaredGettersAndCalls = c.lazyDeclaredGettersAndCalls,
lazyDeclaredSetters = c.lazyDeclaredSetters,
lazyImplementedGettersAndCalls = c.lazyImplementedGettersAndCalls,
lazyImplementedSetters = c.lazyImplementedSetters,
lazyInterfaceGettersAndCalls = c.lazyInterfaceGettersAndCalls,
lazyInterfaceSetters = c.lazyInterfaceSetters;
}
class _ClassInfo {
bool used = false;
final Class classNode;
int topologicalIndex = 0;
int depth = 0;
// Super types must always occur before subtypes in these lists.
// For example:
//
// class A extends Object
// class B extends Object implements A
//
// Here `A` must occur before `B` in the list of direct extenders of Object,
// because `B` is a subtype of `A`.
final Set<_ClassInfo> directExtenders = new LinkedHashSet<_ClassInfo>();
final Set<_ClassInfo> directMixers = new LinkedHashSet<_ClassInfo>();
final Set<_ClassInfo> directImplementers = new LinkedHashSet<_ClassInfo>();
late final Uint32List superclassIntervalList;
late final Uint32List supertypeIntervalList;
List<_ClassInfo>? leastUpperBoundInfos;
/// Maps generic supertype classes to the instantiations implemented by this
/// class.
///
/// E.g. `List` maps to `List<String>` for a class that directly or indirectly
/// implements `List<String>`.
Map<Class, List<Supertype>>? genericSuperTypes;
/// Maps generic supertype classes to the canonical instantiation implemented
/// by this class.
///
/// E.g. `List` maps to `List<String>` for a class that directly or indirectly
/// implements `List<String>`.
Map<Class, Supertype>? genericSuperType;
/// Instance fields, getters, methods, and operators declared in this class
/// or its mixed-in class, sorted according to [_compareMembers].
List<Member>? lazyDeclaredGettersAndCalls;
/// Non-final instance fields and setters declared in this class or its
/// mixed-in class, sorted according to [_compareMembers].
List<Member>? lazyDeclaredSetters;
/// Instance fields, getters, methods, and operators implemented by this class
/// (declared or inherited).
List<Member>? lazyImplementedGettersAndCalls;
/// Non-final instance fields and setters implemented by this class
/// (declared or inherited).
List<Member>? lazyImplementedSetters;
List<Member>? lazyInterfaceGettersAndCalls;
List<Member>? lazyInterfaceSetters;
_ClassInfo(this.classNode);
bool isSubclassOf(_ClassInfo other) {
return _intervalListContains(
superclassIntervalList, other.topologicalIndex);
}
bool isSubtypeOf(_ClassInfo other) {
return _intervalListContains(supertypeIntervalList, other.topologicalIndex);
}
void recordGenericSuperType(CoreTypes coreTypes, Class cls, Supertype type,
HandleAmbiguousSupertypes onAmbiguousSupertypes) {
Supertype? canonical = genericSuperType![cls];
if (canonical == null) {
if (!classNode.enclosingLibrary.isNonNullableByDefault) {
canonical = legacyErasureSupertype(type);
} else {
canonical = type;
}
// ignore: unnecessary_null_comparison
assert(canonical != null,
"No canonical instantiation computed for $cls in $classNode.");
genericSuperType![cls] = canonical;
genericSuperTypes![cls] = <Supertype>[type];
} else {
genericSuperTypes![cls]!.add(type);
if (classNode.enclosingLibrary.isNonNullableByDefault) {
Supertype? result = nnbdTopMergeSupertype(
coreTypes,
normSupertype(coreTypes, type),
normSupertype(coreTypes, canonical));
if (result == null) {
onAmbiguousSupertypes(classNode, canonical, type);
} else {
genericSuperType![cls] = result;
}
} else {
type = legacyErasureSupertype(type);
if (type != canonical) {
onAmbiguousSupertypes(classNode, canonical, type);
}
}
}
assert(genericSuperType!.containsKey(cls),
"No canonical instantiation computed for $cls in $classNode.");
assert(genericSuperTypes!.containsKey(cls),
"No instantiations computed for $cls in $classNode.");
}
}
/// An immutable set of classes.
class ClassSet extends IterableBase<Class> {
final Set<Class> _classes;
ClassSet(this._classes);
@override
bool contains(Object? class_) {
return _classes.contains(class_);
}
ClassSet union(ClassSet other) {
Set<Class> result = new Set<Class>.of(_classes);
result.addAll(other._classes);
return new ClassSet(result);
}
@override
Iterator<Class> get iterator => _classes.iterator;
}
/// 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;
}
}