// 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/nnbd_top_merge.dart'; import 'src/norm.dart'; typedef HandleAmbiguousSupertypes = void Function(Class, Supertype, Supertype); /// 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 [class_], /// or `null` if [class_] does not implement [superclass] at all. Supertype? getClassAsInstanceOf(Class class_, Class superclass); /// Returns the instantiation of [typeDeclaration] that is implemented by /// [type], or `null` if [type] does not implement [typeDeclaration] at all. TypeDeclarationType? getTypeAsInstanceOf( TypeDeclarationType type, TypeDeclaration typeDeclaration); /// Returns the type arguments of the instantiation of [typeDeclaration] that /// is implemented by [type], or `null` if [type] does not implement /// [typeDeclaration] at all. List? getTypeArgumentsAsInstanceOf( TypeDeclarationType type, TypeDeclaration typeDeclaration); /// Returns the instantiation of [superclass] that is implemented by [type], /// or `null` if [type] does not implement [superclass] at all. InterfaceType? getInterfaceTypeAsInstanceOfClass( InterfaceType type, Class superclass); /// 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? getInterfaceTypeArgumentsAsInstanceOfClass( InterfaceType type, Class superclass); /// Returns the instantiation of [superDeclaration] that is implemented by /// [type], or `null` if [type] does not implement [superDeclaration] at all. ExtensionType? getExtensionTypeAsInstanceOfExtensionTypeDeclaration( ExtensionType type, ExtensionTypeDeclaration superDeclaration); /// Returns the instantiation of [superclass] that is implemented by [type], /// or `null` if [type] does not implement [superclass] at all. InterfaceType? getExtensionTypeAsInstanceOfClass( ExtensionType type, Class superclass); /// Returns the type arguments of the instantiation of [superDeclaration] that /// is implemented by [type], or `null` if [type] does not implement /// [superDeclaration] at all. List? getExtensionTypeArgumentsAsInstanceOfExtensionTypeDeclaration( ExtensionType type, ExtensionTypeDeclaration superDeclaration); /// Returns the type arguments of the instantiation of [superDeclaration] that /// is implemented by [type], or `null` if [type] does not implement /// [superDeclaration] at all. List? getExtensionTypeArgumentsAsInstanceOfClass( ExtensionType type, Class superclass); /// True if [subtype] inherits from [superclass] though zero or more /// `extends`, `with`, and `implements` relationships. bool isSubInterfaceOf(Class subtype, 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); /// Computes an upper bound of two types found in their given supertype lists /// /// This method can be seen as a generalization of [getLegacyLeastUpperBound]. /// It is expected to work exactly like [getLegacyLeastUpperBound] when /// [supertypes1] is `[`[type1]`]` and [supertypes2] is `[`[type2]`]`. /// /// If either of [type1] or [type2] is an extension type, the corresponding /// list of supertypes is its non-extension supertypes of maximal depth. In /// that case, the method finds the upper bound for the extension type among /// its non-extension supertypes. It is used as a part of the algorithm for /// finding the upper bound of extension types. InterfaceType getLegacyLeastUpperBoundFromSupertypeLists( TypeDeclarationType type1, TypeDeclarationType type2, List supertypes1, List supertypes2); } mixin ClassHierarchyExtensionTypeMixin implements ClassHierarchyBase { @override TypeDeclarationType? getTypeAsInstanceOf( TypeDeclarationType type, TypeDeclaration typeDeclaration) { switch (type) { case InterfaceType(): switch (typeDeclaration) { case Class(): return getInterfaceTypeAsInstanceOfClass(type, typeDeclaration); case ExtensionTypeDeclaration(): return null; } case ExtensionType(): switch (typeDeclaration) { case Class(): return getExtensionTypeAsInstanceOfClass(type, typeDeclaration); case ExtensionTypeDeclaration(): return getExtensionTypeAsInstanceOfExtensionTypeDeclaration( type, typeDeclaration); } } } @override List? getTypeArgumentsAsInstanceOf( TypeDeclarationType type, TypeDeclaration typeDeclaration) { switch (type) { case InterfaceType(): switch (typeDeclaration) { case Class(): return getInterfaceTypeArgumentsAsInstanceOfClass( type, typeDeclaration); case ExtensionTypeDeclaration(): return null; } case ExtensionType(): switch (typeDeclaration) { case Class(): return getExtensionTypeArgumentsAsInstanceOfClass( type, typeDeclaration); case ExtensionTypeDeclaration(): // ignore: lines_longer_than_80_chars return getExtensionTypeArgumentsAsInstanceOfExtensionTypeDeclaration( type, typeDeclaration); } } } ExtensionType? getExtensionTypeDeclarationAsInstanceOfExtensionTypeDeclaration( ExtensionTypeDeclaration subDeclaration, ExtensionTypeDeclaration superDeclaration) { // TODO(johnniwinther): Improve lookup performance. if (identical(subDeclaration, superDeclaration)) { return coreTypes.thisExtensionType( subDeclaration, Nullability.nonNullable); } for (DartType implement in subDeclaration.implements) { if (implement is ExtensionType) { ExtensionType? supertype = getExtensionTypeDeclarationAsInstanceOfExtensionTypeDeclaration( implement.extensionTypeDeclaration, superDeclaration); if (supertype != null) { if (implement.typeArguments.isNotEmpty) { supertype = Substitution.fromExtensionType(implement) .substituteType(supertype) as ExtensionType; } return supertype; } } else if (implement is InterfaceType) { // Extension type declarations cannot be implemented through classes. } else { assert( false, "Unexpected supertype $implement extension type declaration of " "$subDeclaration."); } } return null; } InterfaceType? getExtensionTypeDeclarationAsInstanceOfClass( ExtensionTypeDeclaration subDeclaration, Class superclass) { // TODO(johnniwinther): Improve lookup performance. for (DartType implement in subDeclaration.implements) { if (implement is ExtensionType) { InterfaceType? supertype = getExtensionTypeDeclarationAsInstanceOfClass( implement.extensionTypeDeclaration, superclass); if (supertype != null) { if (implement.typeArguments.isNotEmpty) { supertype = Substitution.fromExtensionType(implement) .substituteType(supertype) as InterfaceType; } return supertype; } } else if (implement is InterfaceType) { Supertype? supertype = getClassAsInstanceOf(implement.classNode, superclass); if (supertype != null) { if (implement.typeArguments.isNotEmpty) { supertype = Substitution.fromInterfaceType(implement) .substituteSupertype(supertype); } return new InterfaceType(supertype.classNode, Nullability.nonNullable, supertype.typeArguments); } } else { assert( false, "Unexpected supertype $implement extension type declaration of " "$subDeclaration."); } } return null; } @override ExtensionType? getExtensionTypeAsInstanceOfExtensionTypeDeclaration( ExtensionType type, ExtensionTypeDeclaration superclass) { ExtensionType? supertype = getExtensionTypeDeclarationAsInstanceOfExtensionTypeDeclaration( type.extensionTypeDeclaration, superclass); if (supertype != null) { if (type.typeArguments.isNotEmpty) { supertype = Substitution.fromExtensionType(type) .substituteType(supertype) as ExtensionType; } return supertype; } return null; } @override InterfaceType? getExtensionTypeAsInstanceOfClass( ExtensionType type, Class superclass) { InterfaceType? supertype = getExtensionTypeDeclarationAsInstanceOfClass( type.extensionTypeDeclaration, superclass); if (supertype != null) { if (type.typeArguments.isNotEmpty) { supertype = Substitution.fromExtensionType(type) .substituteType(supertype) as InterfaceType; } return supertype; } return null; } @override List? getExtensionTypeArgumentsAsInstanceOfExtensionTypeDeclaration( ExtensionType type, ExtensionTypeDeclaration superDeclaration) { return getExtensionTypeAsInstanceOfExtensionTypeDeclaration( type, superDeclaration) ?.typeArguments; } @override List? getExtensionTypeArgumentsAsInstanceOfClass( ExtensionType type, Class superclass) { return getExtensionTypeAsInstanceOfClass(type, superclass)?.typeArguments; } } abstract class ClassHierarchyMembers { /// 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 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}) { 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) .._initialize(component.libraries); } void set coreTypes(CoreTypes coreTypes); /// 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 getOrderedClasses(Iterable unordered); /// 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 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 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 getDeclaredMembers(Class class_, {bool setters = false}); /// True if [subclass] inherits from [superclass] though zero or more /// `extends` relationships. bool isSubclassOf(Class subclass, Class superclass); /// 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 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 removedLibraries, Iterable ensureKnownLibraries, Iterable 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 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 mergeSortedLists( List first, List second) { if (first.isEmpty) return second; if (second.isEmpty) return first; List result = new List.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) { if (firstLibrary == null) return -1; 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 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 _classesByTopDownIndex; final Map _infoMap = {}; bool invalidated = false; _ClosedWorldClassHierarchySubtypes(this.hierarchy) : _classesByTopDownIndex = new List.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 result = new Set(); 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 with ClassHierarchyExtensionTypeMixin implements ClassHierarchy { @override CoreTypes coreTypes; late HandleAmbiguousSupertypes _onAmbiguousSupertypes; late HandleAmbiguousSupertypes _onAmbiguousSupertypesNotWrapped; /// The insert order is important. final Map _infoMap = new LinkedHashMap(); List getTestingClassInfo() { List result = []; for (_ClassInfo info in _infoMap.values) { result.add(new ForTestingClassInfo._(info)); } return result; } List getAllSupertypeClassesForTesting(Class class_) { List allClassesByIndex = new List.filled(_infoMap.length, null); for (MapEntry c in _infoMap.entries) { allClassesByIndex[c.value.topologicalIndex] = c.key; } List 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 getUsedClasses() { List result = []; 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 knownLibraries = new Set(); 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> _recordedAmbiguousSupertypes = new LinkedHashMap>(); Iterable get classes { allBetsOff = true; return _infoMap.keys; } _ClosedWorldClassHierarchySubtypes? _cachedClassHierarchySubtypes; ClosedWorldClassHierarchy._internal( this.coreTypes, HandleAmbiguousSupertypes onAmbiguousSupertypes) { _onAmbiguousSupertypesNotWrapped = onAmbiguousSupertypes; _onAmbiguousSupertypes = (Class class_, Supertype a, Supertype b) { onAmbiguousSupertypes(class_, a, b); List? recorded = _recordedAmbiguousSupertypes[class_]; if (recorded == null) { recorded = []; _recordedAmbiguousSupertypes[class_] = recorded; } recorded.add(a); recorded.add(b); }; } ClassHierarchySubtypes computeSubtypesInformation() { _cachedClassHierarchySubtypes ??= new _ClosedWorldClassHierarchySubtypes(this); return _cachedClassHierarchySubtypes!; } @override Iterable getOrderedClasses(Iterable unordered) { Set 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 isSubInterfaceOf(Class subtype, Class superclass) { if (identical(subtype, superclass)) return true; return infoFor(subtype).isSubtypeOf(infoFor(superclass)); } 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; } List<_ClassInfo> _getCombinedRankedSuperclassInfosFromList( List<_ClassInfo> infos) { if (infos.length == 1 && infos.single.leastUpperBoundInfos != null) { return infos.single.leastUpperBoundInfos!; } _LubHeap heap = new _LubHeap(); for (_ClassInfo info in infos) { heap.add(info); if (info.leastUpperBoundInfos == null) { List<_ClassInfo> chainForInfo = _getRankedSuperclassInfos(info); for (_ClassInfo fromChain in chainForInfo) { heap.add(fromChain); } } } List<_ClassInfo> chain = <_ClassInfo>[]; while (heap.isNotEmpty) { _ClassInfo info = heap.remove(); chain.add(info); } return chain; } @override InterfaceType getLegacyLeastUpperBound( InterfaceType type1, InterfaceType type2) { // The algorithm is: first we compute a list of superclasses for both types, // ordered from greatest to least depth, and ordered by topological sort // index within each depth. Due to the sort order, we can find the // intersection of these lists by a simple walk. // // Then, for each class in the intersection, determine the exact type that // is implemented by type1 and type2. If the types match, that type is a // candidate (it's a member of S_n). As soon as we find a candidate which // is unique for its depth, we return it. // // As an optimization, if the class for I is a subtype of the class for J, // then we know that the list of superclasses of J is a subset of the list // of superclasses for I; therefore it is sufficient to compute just the // list of superclasses for J. To avoid complicating the code below (which // intersects the two lists), we set both lists equal to the list of // superclasses for J. And vice versa with the role of I and J swapped. // Compute the list of superclasses for both types, with the above // optimization. _ClassInfo info1 = infoFor(type1.classNode); _ClassInfo info2 = infoFor(type2.classNode); List<_ClassInfo> classes1; List<_ClassInfo> classes2; if (identical(info1, info2) || info1.isSubtypeOf(info2)) { classes1 = classes2 = _getRankedSuperclassInfos(info2); } else if (info2.isSubtypeOf(info1)) { classes1 = classes2 = _getRankedSuperclassInfos(info1); } else { classes1 = _getRankedSuperclassInfos(info1); classes2 = _getRankedSuperclassInfos(info2); } return _getLegacyLeastUpperBoundInternal( type1, type2, info1, info2, classes1, classes2); } InterfaceType _getLegacyLeastUpperBoundInternal( TypeDeclarationType type1, TypeDeclarationType type2, _ClassInfo? info1, _ClassInfo? info2, List<_ClassInfo> classInfos1, List<_ClassInfo> classInfos2) { assert(type1 is! InterfaceType || info1 != null); assert(type2 is! InterfaceType || info2 != null); // 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 = classInfos1[i1]; _ClassInfo next2 = classInfos2[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; if (type1 is InterfaceType) { superType1 = identical(info1!, next) ? type1 : Substitution.fromInterfaceType(type1).substituteType( info1.genericSuperType![next.classNode]!.asInterfaceType) as InterfaceType; } else { type1 as ExtensionType; superType1 = getExtensionTypeAsInstanceOfClass(type1, next.classNode)!; } InterfaceType superType2; if (type2 is InterfaceType) { superType2 = identical(info2!, next) ? type2 : Substitution.fromInterfaceType(type2).substituteType( info2.genericSuperType![next.classNode]!.asInterfaceType) as InterfaceType; } else { type2 as ExtensionType; superType2 = getExtensionTypeAsInstanceOfClass(type2, next.classNode)!; } if (superType1 == superType2) { candidate = superType1.withDeclaredNullability( uniteNullabilities(type1.nullability, type2.nullability)); ++numCandidatesAtThisDepth; } } } } @override InterfaceType getLegacyLeastUpperBoundFromSupertypeLists( TypeDeclarationType type1, TypeDeclarationType type2, List supertypes1, List supertypes2) { assert(supertypes1.isNotEmpty || type1 is ExtensionType); assert(supertypes2.isNotEmpty || type2 is ExtensionType); if (supertypes1.isEmpty || supertypes2.isEmpty) { if (type1 is ExtensionType && type1.isPotentiallyNullable || type2 is ExtensionType && type2.isPotentiallyNullable) { return coreTypes.objectNullableRawType; } else { return coreTypes.objectRawType( uniteNullabilities(type1.nullability, type2.nullability)); } } List<_ClassInfo> combinedInfos1 = _getCombinedRankedSuperclassInfosFromList(<_ClassInfo>[ for (InterfaceType supertype in supertypes1) infoFor(supertype.classNode) ]); List<_ClassInfo> combinedInfos2 = _getCombinedRankedSuperclassInfosFromList(<_ClassInfo>[ for (InterfaceType supertype in supertypes2) infoFor(supertype.classNode) ]); return _getLegacyLeastUpperBoundInternal( type1, type2, type1 is InterfaceType ? infoFor(type1.classNode) : null, type2 is InterfaceType ? infoFor(type2.classNode) : null, combinedInfos1, combinedInfos2); } @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? getInterfaceTypeAsInstanceOfClass( InterfaceType type, Class superclass) { List? typeArguments = getInterfaceTypeArgumentsAsInstanceOfClass(type, superclass); if (typeArguments == null) return null; return new InterfaceType(superclass, type.nullability, typeArguments); } @override List? getInterfaceTypeArgumentsAsInstanceOfClass( InterfaceType type, Class superclass) { if (type.classReference == superclass.reference) { // 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` 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`. 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 []; return Substitution.fromInterfaceType(type) .substituteSupertype(castedType) .typeArguments; } @override Member? getDispatchTarget(Class class_, Name name, {bool setter = false}) { List 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 getDispatchTargets(Class class_, {bool setters = false}) { return _buildImplementedMembers(class_, infoFor(class_), setters: setters); } @override Member? getInterfaceMember(Class class_, Name name, {bool setter = false}) { List list = getInterfaceMembers(class_, setters: setter); return ClassHierarchy.findMemberByName(list, name); } @override List getInterfaceMembers(Class class_, {bool setters = false}) { return _buildInterfaceMembers(class_, infoFor(class_), setters: setters); } @override List 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 superGetters = getInterfaceMembers(superclass); List 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 declaredList, List 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; } } } // Returns the instantiation of each generic supertype implemented by this // class (e.g. getClassAsInstanceOf applied to all superclasses and // interfaces). List genericSupertypesOf(Class class_) { Map? supertypes = infoFor(class_).genericSuperType; if (supertypes == null) return const []; return supertypes.values.toList(); } @override ClassHierarchy applyTreeChanges(Iterable removedLibraries, Iterable ensureKnownLibraries, Iterable 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 libs = new Set.of(reissueAmbiguousSupertypesFor.libraries); for (Class class_ in _recordedAmbiguousSupertypes.keys) { if (!libs.contains(class_.enclosingLibrary)) continue; List recorded = _recordedAmbiguousSupertypes[class_]!; for (int i = 0; i < recorded.length; i += 2) { _onAmbiguousSupertypesNotWrapped( class_, recorded[i], recorded[i + 1]); } } } // Add the new classes. List addedClassesSorted = []; int expectedStartIndex = _topSortIndex; for (Library lib in ensureKnownLibraries) { if (knownLibraries.contains(lib)) continue; for (Class class_ in lib.classes) { _topologicalSortVisit(class_, new Set(), orderedList: addedClassesSorted); } knownLibraries.add(lib); } for (_ClassInfo info in changedClasses) { _topologicalSortVisit(info.classNode, new Set(), orderedList: addedClassesSorted); } _initializeTopologicallySortedClasses( addedClassesSorted, expectedStartIndex); assert(sanityChecks()); return this; } @override ClassHierarchy applyMemberChanges(Iterable 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> map = {}; for (Class c in _infoMap.keys) { String className = "${c.enclosingLibrary.importUri}::${c.name}"; List? list = map[className]; if (list == null) { map[className] = list = []; } list.add(c); } StringBuffer? sb; for (MapEntry> 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; } void _initialize(List 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()); } 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 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) { _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 beingVisited, {List? 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 _buildImplementedMembers(Class classNode, _ClassInfo info, {required bool setters}) { List? members = setters ? info.lazyImplementedSetters : info.lazyImplementedGettersAndCalls; if (members != null) return members; List inherited; Supertype? supertype = classNode.supertype; if (supertype == null) { inherited = const []; } 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 _buildDeclaredMembers(Class classNode, _ClassInfo info, {required bool setters}) { List? 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 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 _buildInterfaceMembers(Class classNode, _ClassInfo info, {required bool setters}) { List? members = setters ? info.lazyInterfaceSetters : info.lazyInterfaceGettersAndCalls; if (members != null) return members; List allInheritedMembers = []; List declared = _buildDeclaredMembers(classNode, info, setters: setters); void inheritFrom(Supertype? type) { if (type == null) return; List 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 _inheritMembers( List declared, List inherited, {bool skipAbstractMembers = false}) { List result = new List.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 _getUnshadowedInheritedMembers( List declared, List inherited) { List result = new List.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 ??= {}; subInfo.genericSuperTypes ??= >{}; 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 ??= {}; subInfo.genericSuperTypes ??= >{}; 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 getExpenseHistogram() { List result = []; 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 events = []; 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? lazyDeclaredGettersAndCalls; final List? lazyDeclaredSetters; final List? lazyImplementedGettersAndCalls; final List? lazyImplementedSetters; final List? lazyInterfaceGettersAndCalls; final List? 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` for a class that directly or indirectly /// implements `List`. Map>? genericSuperTypes; /// Maps generic supertype classes to the canonical instantiation implemented /// by this class. /// /// E.g. `List` maps to `List` for a class that directly or indirectly /// implements `List`. Map? genericSuperType; /// Instance fields, getters, methods, and operators declared in this class /// or its mixed-in class, sorted according to [_compareMembers]. List? lazyDeclaredGettersAndCalls; /// Non-final instance fields and setters declared in this class or its /// mixed-in class, sorted according to [_compareMembers]. List? lazyDeclaredSetters; /// Instance fields, getters, methods, and operators implemented by this class /// (declared or inherited). List? lazyImplementedGettersAndCalls; /// Non-final instance fields and setters implemented by this class /// (declared or inherited). List? lazyImplementedSetters; List? lazyInterfaceGettersAndCalls; List? 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) { canonical = type; genericSuperType![cls] = canonical; genericSuperTypes![cls] = [type]; } else { genericSuperTypes![cls]!.add(type); Supertype? result = nnbdTopMergeSupertype(coreTypes, normSupertype(coreTypes, type), normSupertype(coreTypes, canonical)); if (result == null) { onAmbiguousSupertypes(classNode, canonical, type); } else { genericSuperType![cls] = result; } } 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 { final Set _classes; ClassSet(this._classes); @override bool contains(Object? class_) { return _classes.contains(class_); } @override Iterator 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; } }