| // 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<DartType>? 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<DartType>? 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<DartType>? 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<DartType>? 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<InterfaceType> supertypes1, | |
| List<InterfaceType> 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<DartType>? 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<DartType>? getExtensionTypeArgumentsAsInstanceOfExtensionTypeDeclaration( | |
| ExtensionType type, ExtensionTypeDeclaration superDeclaration) { | |
| return getExtensionTypeAsInstanceOfExtensionTypeDeclaration( | |
| type, superDeclaration) | |
| ?.typeArguments; | |
| } | |
| @override | |
| List<DartType>? 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<Class> getOrderedClasses(Iterable<Class> 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<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); | |
| /// 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) { | |
| 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<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 | |
| with ClassHierarchyExtensionTypeMixin | |
| implements ClassHierarchy { | |
| @override | |
| CoreTypes coreTypes; | |
| late HandleAmbiguousSupertypes _onAmbiguousSupertypes; | |
| late HandleAmbiguousSupertypes _onAmbiguousSupertypesNotWrapped; | |
| /// 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; | |
| } | |
| _ClosedWorldClassHierarchySubtypes? _cachedClassHierarchySubtypes; | |
| ClosedWorldClassHierarchy._internal( | |
| this.coreTypes, 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); | |
| }; | |
| } | |
| 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 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<InterfaceType> supertypes1, | |
| List<InterfaceType> 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<DartType>? typeArguments = | |
| getInterfaceTypeArgumentsAsInstanceOfClass(type, superclass); | |
| if (typeArguments == null) return null; | |
| return new InterfaceType(superclass, type.nullability, typeArguments); | |
| } | |
| @override | |
| List<DartType>? 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<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; | |
| } | |
| } | |
| } | |
| // 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_) { | |
| 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; | |
| } | |
| 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) { | |
| _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) { | |
| canonical = type; | |
| genericSuperType![cls] = canonical; | |
| genericSuperTypes![cls] = <Supertype>[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<Class> { | |
| final Set<Class> _classes; | |
| ClassSet(this._classes); | |
| @override | |
| bool contains(Object? class_) { | |
| return _classes.contains(class_); | |
| } | |
| @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; | |
| } | |
| } |