| // Copyright (c) 2018, 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. |
| |
| // @dart = 2.10 |
| |
| import 'package:kernel/ast.dart' as ir; |
| import 'package:front_end/src/api_unstable/dart2js.dart' show Link; |
| |
| import '../closure.dart'; |
| import '../common.dart'; |
| import '../common/elements.dart' show JCommonElements, JElementEnvironment; |
| import '../common/names.dart'; |
| import '../deferred_load/output_unit.dart' |
| show LateOutputUnitDataBuilder, OutputUnitData; |
| import '../elements/entities.dart'; |
| import '../elements/entity_utils.dart' as utils; |
| import '../elements/names.dart'; |
| import '../elements/types.dart'; |
| import '../environment.dart'; |
| import '../inferrer/abstract_value_domain.dart'; |
| import '../inferrer/abstract_value_strategy.dart'; |
| import '../js_emitter/sorter.dart'; |
| import '../js_backend/annotations.dart'; |
| import '../js_backend/field_analysis.dart'; |
| import '../js_backend/backend_usage.dart'; |
| import '../js_backend/interceptor_data.dart'; |
| import '../js_backend/native_data.dart'; |
| import '../js_backend/no_such_method_registry.dart'; |
| import '../js_backend/runtime_types_resolution.dart'; |
| import '../ordered_typeset.dart'; |
| import '../options.dart'; |
| import '../serialization/serialization.dart'; |
| import '../universe/class_hierarchy.dart'; |
| import '../universe/class_set.dart'; |
| import '../universe/function_set.dart' show FunctionSet; |
| import '../universe/member_usage.dart'; |
| import '../universe/selector.dart'; |
| import '../world.dart'; |
| import 'element_map.dart'; |
| import 'element_map_impl.dart'; |
| import 'locals.dart'; |
| |
| class JsClosedWorld implements JClosedWorld { |
| static const String tag = 'closed-world'; |
| |
| @override |
| final NativeData nativeData; |
| @override |
| final InterceptorData interceptorData; |
| @override |
| final BackendUsage backendUsage; |
| @override |
| final NoSuchMethodData noSuchMethodData; |
| |
| FunctionSet _allFunctions; |
| |
| final Map<ClassEntity, Set<ClassEntity>> mixinUses; |
| Map<ClassEntity, List<ClassEntity>> _liveMixinUses; |
| |
| final Map<ClassEntity, Set<ClassEntity>> typesImplementedBySubclasses; |
| |
| final Map<ClassEntity, Map<ClassEntity, bool>> _subtypeCoveredByCache = {}; |
| |
| // TODO(johnniwinther): Can this be derived from [ClassSet]s? |
| final Set<ClassEntity> implementedClasses; |
| |
| @override |
| final Set<MemberEntity> liveInstanceMembers; |
| |
| /// Members that are written either directly or through a setter selector. |
| final Set<MemberEntity> assignedInstanceMembers; |
| |
| @override |
| final Set<ClassEntity> liveNativeClasses; |
| |
| @override |
| final Set<MemberEntity> processedMembers; |
| |
| @override |
| final Set<ClassEntity> extractTypeArgumentsInterfacesNewRti; |
| |
| @override |
| final ClassHierarchy classHierarchy; |
| |
| @override |
| final JsKernelToElementMap elementMap; |
| @override |
| final RuntimeTypesNeed rtiNeed; |
| AbstractValueDomain /*!*/ _abstractValueDomain; |
| @override |
| final JFieldAnalysis fieldAnalysis; |
| @override |
| final AnnotationsData annotationsData; |
| @override |
| final ClosureData closureDataLookup; |
| @override |
| final OutputUnitData outputUnitData; |
| Sorter _sorter; |
| |
| final Map<MemberEntity, MemberAccess> memberAccess; |
| |
| JsClosedWorld( |
| this.elementMap, |
| this.nativeData, |
| this.interceptorData, |
| this.backendUsage, |
| this.rtiNeed, |
| this.fieldAnalysis, |
| this.noSuchMethodData, |
| this.implementedClasses, |
| this.liveNativeClasses, |
| this.liveInstanceMembers, |
| this.assignedInstanceMembers, |
| this.processedMembers, |
| this.extractTypeArgumentsInterfacesNewRti, |
| this.mixinUses, |
| this.typesImplementedBySubclasses, |
| this.classHierarchy, |
| AbstractValueStrategy abstractValueStrategy, |
| this.annotationsData, |
| this.closureDataLookup, |
| this.outputUnitData, |
| this.memberAccess) { |
| _abstractValueDomain = abstractValueStrategy.createDomain(this); |
| } |
| |
| /// Deserializes a [JsClosedWorld] object from [source]. |
| factory JsClosedWorld.readFromDataSource( |
| CompilerOptions options, |
| DiagnosticReporter reporter, |
| Environment environment, |
| AbstractValueStrategy abstractValueStrategy, |
| ir.Component component, |
| DataSourceReader source) { |
| source.begin(tag); |
| |
| JsKernelToElementMap elementMap = JsKernelToElementMap.readFromDataSource( |
| options, reporter, environment, component, source); |
| ClassHierarchy classHierarchy = |
| ClassHierarchy.readFromDataSource(source, elementMap.commonElements); |
| NativeData nativeData = |
| NativeData.readFromDataSource(source, elementMap.elementEnvironment); |
| elementMap.nativeData = nativeData; |
| InterceptorData interceptorData = InterceptorData.readFromDataSource( |
| source, nativeData, elementMap.commonElements); |
| BackendUsage backendUsage = BackendUsage.readFromDataSource(source); |
| RuntimeTypesNeed rtiNeed = RuntimeTypesNeed.readFromDataSource( |
| source, elementMap.elementEnvironment); |
| JFieldAnalysis allocatorAnalysis = |
| JFieldAnalysis.readFromDataSource(source, options); |
| NoSuchMethodData noSuchMethodData = |
| NoSuchMethodData.readFromDataSource(source); |
| |
| Set<ClassEntity> implementedClasses = source.readClasses().toSet(); |
| Set<ClassEntity> liveNativeClasses = source.readClasses().toSet(); |
| Set<ClassEntity> extractTypeArgumentsInterfacesNewRti = |
| source.readClasses().toSet(); |
| Set<MemberEntity> liveInstanceMembers = source.readMembers().toSet(); |
| Set<MemberEntity> assignedInstanceMembers = source.readMembers().toSet(); |
| Set<MemberEntity> processedMembers = source.readMembers().toSet(); |
| Map<ClassEntity, Set<ClassEntity>> mixinUses = |
| source.readClassMap(() => source.readClasses().toSet()); |
| Map<ClassEntity, Set<ClassEntity>> typesImplementedBySubclasses = |
| source.readClassMap(() => source.readClasses().toSet()); |
| |
| AnnotationsData annotationsData = |
| AnnotationsData.readFromDataSource(options, reporter, source); |
| |
| ClosureData closureData = |
| ClosureData.readFromDataSource(elementMap, source); |
| |
| OutputUnitData outputUnitData = OutputUnitData.readFromDataSource(source); |
| elementMap.lateOutputUnitDataBuilder = |
| LateOutputUnitDataBuilder(outputUnitData); |
| |
| Map<MemberEntity, MemberAccess> memberAccess = source.readMemberMap( |
| (MemberEntity member) => MemberAccess.readFromDataSource(source)); |
| |
| source.end(tag); |
| |
| return JsClosedWorld( |
| elementMap, |
| nativeData, |
| interceptorData, |
| backendUsage, |
| rtiNeed, |
| allocatorAnalysis, |
| noSuchMethodData, |
| implementedClasses, |
| liveNativeClasses, |
| liveInstanceMembers, |
| assignedInstanceMembers, |
| processedMembers, |
| extractTypeArgumentsInterfacesNewRti, |
| mixinUses, |
| typesImplementedBySubclasses, |
| classHierarchy, |
| abstractValueStrategy, |
| annotationsData, |
| closureData, |
| outputUnitData, |
| memberAccess); |
| } |
| |
| /// Serializes this [JsClosedWorld] to [sink]. |
| void writeToDataSink(DataSinkWriter sink) { |
| sink.begin(tag); |
| elementMap.writeToDataSink(sink); |
| classHierarchy.writeToDataSink(sink); |
| nativeData.writeToDataSink(sink); |
| interceptorData.writeToDataSink(sink); |
| backendUsage.writeToDataSink(sink); |
| rtiNeed.writeToDataSink(sink); |
| fieldAnalysis.writeToDataSink(sink); |
| noSuchMethodData.writeToDataSink(sink); |
| sink.writeClasses(implementedClasses); |
| sink.writeClasses(liveNativeClasses); |
| sink.writeClasses(extractTypeArgumentsInterfacesNewRti); |
| sink.writeMembers(liveInstanceMembers); |
| sink.writeMembers(assignedInstanceMembers); |
| sink.writeMembers(processedMembers); |
| sink.writeClassMap( |
| mixinUses, (Set<ClassEntity> set) => sink.writeClasses(set)); |
| sink.writeClassMap(typesImplementedBySubclasses, |
| (Set<ClassEntity> set) => sink.writeClasses(set)); |
| annotationsData.writeToDataSink(sink); |
| closureDataLookup.writeToDataSink(sink); |
| outputUnitData.writeToDataSink(sink); |
| sink.writeMemberMap( |
| memberAccess, |
| (MemberEntity member, MemberAccess access) => |
| access.writeToDataSink(sink)); |
| sink.end(tag); |
| } |
| |
| @override |
| JElementEnvironment get elementEnvironment => elementMap.elementEnvironment; |
| |
| @override |
| JCommonElements get commonElements => elementMap.commonElements; |
| |
| @override |
| DartTypes get dartTypes => elementMap.types; |
| |
| @override |
| bool isImplemented(ClassEntity cls) { |
| return implementedClasses.contains(cls); |
| } |
| |
| @override |
| ClassEntity getLubOfInstantiatedSubclasses(ClassEntity cls) { |
| if (nativeData.isJsInteropClass(cls)) { |
| return getLubOfInstantiatedSubclasses( |
| commonElements.jsLegacyJavaScriptObjectClass); |
| } |
| ClassHierarchyNode hierarchy = classHierarchy.getClassHierarchyNode(cls); |
| return hierarchy?.getLubOfInstantiatedSubclasses(); |
| } |
| |
| @override |
| ClassEntity getLubOfInstantiatedSubtypes(ClassEntity cls) { |
| if (nativeData.isJsInteropClass(cls)) { |
| return getLubOfInstantiatedSubtypes( |
| commonElements.jsLegacyJavaScriptObjectClass); |
| } |
| ClassSet classSet = classHierarchy.getClassSet(cls); |
| return classSet?.getLubOfInstantiatedSubtypes(); |
| } |
| |
| @override |
| bool isUsedAsMixin(ClassEntity cls) { |
| return !mixinUsesOf(cls).isEmpty; |
| } |
| |
| @override |
| bool hasAnySubclassOfMixinUseThatImplements( |
| ClassEntity cls, ClassEntity type) { |
| return mixinUsesOf(cls) |
| .any((use) => hasAnySubclassThatImplements(use, type)); |
| } |
| |
| @override |
| bool everySubtypeIsSubclassOfOrMixinUseOf(ClassEntity x, ClassEntity y) { |
| Map<ClassEntity, bool> secondMap = _subtypeCoveredByCache[x] ??= {}; |
| return secondMap[y] ??= classHierarchy.subtypesOf(x).every( |
| (ClassEntity cls) => |
| classHierarchy.isSubclassOf(cls, y) || |
| isSubclassOfMixinUseOf(cls, y)); |
| } |
| |
| @override |
| bool hasAnySubclassThatImplements(ClassEntity superclass, ClassEntity type) { |
| Set<ClassEntity> subclasses = typesImplementedBySubclasses[superclass]; |
| if (subclasses == null) return false; |
| return subclasses.contains(type); |
| } |
| |
| @override |
| bool needsNoSuchMethod( |
| ClassEntity base, Selector selector, ClassQuery query) { |
| /// Returns `true` if subclasses in the [rootNode] tree needs noSuchMethod |
| /// handling. |
| bool subclassesNeedNoSuchMethod(ClassHierarchyNode rootNode) { |
| if (!rootNode.isInstantiated) { |
| // No subclass needs noSuchMethod handling since they are all |
| // uninstantiated. |
| return false; |
| } |
| ClassEntity rootClass = rootNode.cls; |
| if (_hasConcreteMatch(rootClass, selector)) { |
| // The root subclass has a concrete implementation so no subclass needs |
| // noSuchMethod handling. |
| return false; |
| } else if (rootNode.isExplicitlyInstantiated) { |
| // The root class need noSuchMethod handling. |
| return true; |
| } |
| IterationStep result = rootNode.forEachSubclass((ClassEntity subclass) { |
| if (_hasConcreteMatch(subclass, selector, |
| stopAtSuperclass: rootClass)) { |
| // Found a match - skip all subclasses. |
| return IterationStep.SKIP_SUBCLASSES; |
| } else { |
| // Stop fast - we found a need for noSuchMethod handling. |
| return IterationStep.STOP; |
| } |
| }, ClassHierarchyNode.EXPLICITLY_INSTANTIATED, strict: true); |
| // We stopped fast so we need noSuchMethod handling. |
| return result == IterationStep.STOP; |
| } |
| |
| ClassSet classSet = classHierarchy.getClassSet(base); |
| assert(classSet != null, failedAt(base, "No class set for $base.")); |
| ClassHierarchyNode node = classSet.node; |
| if (query == ClassQuery.EXACT) { |
| return node.isExplicitlyInstantiated && |
| !_hasConcreteMatch(base, selector); |
| } else if (query == ClassQuery.SUBCLASS) { |
| return subclassesNeedNoSuchMethod(node); |
| } else { |
| if (subclassesNeedNoSuchMethod(node)) return true; |
| for (ClassHierarchyNode subtypeNode in classSet.subtypeNodes) { |
| if (subclassesNeedNoSuchMethod(subtypeNode)) return true; |
| } |
| return false; |
| } |
| } |
| |
| @override |
| Iterable<ClassEntity> commonSupertypesOf(Iterable<ClassEntity> classes) { |
| Iterator<ClassEntity> iterator = classes.iterator; |
| if (!iterator.moveNext()) return const <ClassEntity>[]; |
| |
| ClassEntity cls = iterator.current; |
| OrderedTypeSet typeSet = elementMap.getOrderedTypeSet(cls); |
| if (!iterator.moveNext()) return typeSet.types.map((type) => type.element); |
| |
| int depth = typeSet.maxDepth; |
| Link<OrderedTypeSet> otherTypeSets = const Link<OrderedTypeSet>(); |
| do { |
| ClassEntity otherClass = iterator.current; |
| OrderedTypeSet otherTypeSet = elementMap.getOrderedTypeSet(otherClass); |
| otherTypeSets = otherTypeSets.prepend(otherTypeSet); |
| if (otherTypeSet.maxDepth < depth) { |
| depth = otherTypeSet.maxDepth; |
| } |
| } while (iterator.moveNext()); |
| |
| List<ClassEntity> commonSupertypes = <ClassEntity>[]; |
| OUTER: |
| for (Link<InterfaceType> link = typeSet[depth]; |
| link.head.element != commonElements.objectClass; |
| link = link.tail) { |
| ClassEntity cls = link.head.element; |
| for (Link<OrderedTypeSet> link = otherTypeSets; |
| !link.isEmpty; |
| link = link.tail) { |
| if (link.head.asInstanceOf(cls, elementMap.getHierarchyDepth(cls)) == |
| null) { |
| continue OUTER; |
| } |
| } |
| commonSupertypes.add(cls); |
| } |
| commonSupertypes.add(commonElements.objectClass); |
| return commonSupertypes; |
| } |
| |
| @override |
| Iterable<ClassEntity> mixinUsesOf(ClassEntity cls) { |
| if (_liveMixinUses == null) { |
| _liveMixinUses = Map<ClassEntity, List<ClassEntity>>(); |
| for (ClassEntity mixin in mixinUses.keys) { |
| List<ClassEntity> uses = <ClassEntity>[]; |
| |
| void addLiveUse(ClassEntity mixinApplication) { |
| if (classHierarchy.isInstantiated(mixinApplication)) { |
| uses.add(mixinApplication); |
| } else if (_isNamedMixinApplication(mixinApplication)) { |
| Set<ClassEntity> next = mixinUses[mixinApplication]; |
| if (next != null) { |
| next.forEach(addLiveUse); |
| } |
| } |
| } |
| |
| mixinUses[mixin].forEach(addLiveUse); |
| if (uses.isNotEmpty) { |
| _liveMixinUses[mixin] = uses; |
| } |
| } |
| } |
| Iterable<ClassEntity> uses = _liveMixinUses[cls]; |
| return uses ?? const <ClassEntity>[]; |
| } |
| |
| @override |
| bool hasAnySubclassThatMixes(ClassEntity superclass, ClassEntity mixin) { |
| return mixinUsesOf(mixin).any((ClassEntity each) { |
| return classHierarchy.isSubclassOf(each, superclass); |
| }); |
| } |
| |
| @override |
| bool isSubclassOfMixinUseOf(ClassEntity cls, ClassEntity mixin) { |
| if (isUsedAsMixin(mixin)) { |
| ClassEntity current = cls; |
| while (current != null) { |
| ClassEntity currentMixin = elementMap.getAppliedMixin(current); |
| if (currentMixin == mixin) return true; |
| current = elementEnvironment.getSuperClass(current); |
| } |
| } |
| return false; |
| } |
| |
| void _ensureFunctionSet() { |
| if (_allFunctions == null) { |
| // [FunctionSet] is created lazily because it is not used when we switch |
| // from a frontend to a backend model before inference. |
| _allFunctions = FunctionSet(liveInstanceMembers); |
| } |
| } |
| |
| ClassEntity __functionLub; |
| ClassEntity get _functionLub => __functionLub ??= |
| getLubOfInstantiatedSubtypes(commonElements.functionClass); |
| |
| @override |
| bool includesClosureCallInDomain(Selector selector, AbstractValue receiver, |
| AbstractValueDomain abstractValueDomain) { |
| return selector.name == Identifiers.call && |
| (receiver == null || |
| // This is logically equivalent to the former implementation using |
| // `abstractValueDomain.contains` (which wrapped `containsMask`). |
| // The switch to `abstractValueDomain.containsType` is because |
| // `contains` was generally unsound but happened to work correctly |
| // here. See https://dart-review.googlesource.com/c/sdk/+/130565 |
| // for further discussion. |
| // |
| // This checks if the receiver mask contains the entire type cone |
| // originating from [_functionLub] and may therefore be unsound if |
| // the receiver mask contains only part of the type cone. (Is this |
| // possible?) |
| // |
| // TODO(fishythefish): Use `isDisjoint` or equivalent instead of |
| // `containsType` once we can ensure it's fast enough. |
| abstractValueDomain |
| .containsType(receiver, _functionLub) |
| .isPotentiallyTrue); |
| } |
| |
| @override |
| bool includesClosureCall(Selector selector, AbstractValue receiver) { |
| return includesClosureCallInDomain(selector, receiver, abstractValueDomain); |
| } |
| |
| @override |
| Selector getSelector(ir.Expression node) => elementMap.getSelector(node); |
| |
| @override |
| AbstractValue computeReceiverType(Selector selector, AbstractValue receiver) { |
| _ensureFunctionSet(); |
| if (includesClosureCall(selector, receiver)) { |
| return abstractValueDomain.dynamicType; |
| } |
| return _allFunctions.receiverType(selector, receiver, abstractValueDomain); |
| } |
| |
| @override |
| Iterable<MemberEntity> locateMembersInDomain(Selector selector, |
| AbstractValue /*?*/ receiver, AbstractValueDomain abstractValueDomain) { |
| _ensureFunctionSet(); |
| return _allFunctions.filter(selector, receiver, abstractValueDomain); |
| } |
| |
| @override |
| Iterable<MemberEntity> locateMembers( |
| Selector selector, AbstractValue /*?*/ receiver) { |
| return locateMembersInDomain(selector, receiver, abstractValueDomain); |
| } |
| |
| bool hasAnyUserDefinedGetter(Selector selector, AbstractValue receiver) { |
| _ensureFunctionSet(); |
| return _allFunctions |
| .filter(selector, receiver, abstractValueDomain) |
| .any((each) => each.isGetter); |
| } |
| |
| @override |
| MemberEntity locateSingleMember(Selector selector, AbstractValue receiver) { |
| if (includesClosureCall(selector, receiver)) { |
| return null; |
| } |
| receiver ??= abstractValueDomain.dynamicType; |
| return abstractValueDomain.locateSingleMember(receiver, selector); |
| } |
| |
| @override |
| bool fieldNeverChanges(MemberEntity element) { |
| if (!element.isField) return false; |
| if (nativeData.isNativeMember(element)) { |
| // Some native fields are views of data that may be changed by operations. |
| // E.g. node.firstChild depends on parentNode.removeBefore(n1, n2). |
| // TODO(sra): Refine the effect classification so that native effects are |
| // distinct from ordinary Dart effects. |
| return false; |
| } |
| |
| if (!element.isAssignable) { |
| return true; |
| } |
| if (element.isInstanceMember) { |
| return !assignedInstanceMembers.contains(element); |
| } |
| return false; |
| } |
| |
| @override |
| Sorter get sorter { |
| return _sorter ??= KernelSorter(elementMap); |
| } |
| |
| @override |
| AbstractValueDomain get abstractValueDomain { |
| return _abstractValueDomain; |
| } |
| |
| @override |
| bool hasElementIn(ClassEntity cls, Name name, Entity element) { |
| while (cls != null) { |
| MemberEntity member = |
| elementEnvironment.lookupLocalClassMember(cls, name); |
| if (member != null && !member.isAbstract) { |
| return member == element; |
| } |
| cls = elementEnvironment.getSuperClass(cls); |
| } |
| return false; |
| } |
| |
| /// Returns whether a [selector] call on an instance of [cls] |
| /// will hit a method at runtime, and not go through [noSuchMethod]. |
| bool _hasConcreteMatch(ClassEntity cls, Selector selector, |
| {ClassEntity stopAtSuperclass}) { |
| assert(classHierarchy.isInstantiated(cls), |
| failedAt(cls, '$cls has not been instantiated.')); |
| MemberEntity element = |
| elementEnvironment.lookupClassMember(cls, selector.memberName); |
| if (element == null) return false; |
| |
| if (element.isAbstract) { |
| ClassEntity enclosingClass = element.enclosingClass; |
| return _hasConcreteMatch( |
| elementEnvironment.getSuperClass(enclosingClass), selector); |
| } |
| return selector.appliesUnnamed(element); |
| } |
| |
| bool _isNamedMixinApplication(ClassEntity cls) { |
| return elementEnvironment.isMixinApplication(cls) && |
| !elementEnvironment.isUnnamedMixinApplication(cls); |
| } |
| |
| @override |
| MemberAccess getMemberAccess(MemberEntity member) { |
| return memberAccess[member]; |
| } |
| |
| @override |
| void registerExtractTypeArguments(ClassEntity interface) { |
| extractTypeArgumentsInterfacesNewRti.add(interface); |
| } |
| } |
| |
| class KernelSorter implements Sorter { |
| final JsToElementMap elementMap; |
| |
| KernelSorter(this.elementMap); |
| |
| int _compareLibraries(LibraryEntity a, LibraryEntity b) { |
| return utils.compareLibrariesUris(a.canonicalUri, b.canonicalUri); |
| } |
| |
| int _compareSourceSpans(Entity entity1, SourceSpan sourceSpan1, |
| Entity entity2, SourceSpan sourceSpan2) { |
| int r = utils.compareSourceUris(sourceSpan1.uri, sourceSpan2.uri); |
| if (r != 0) return r; |
| return utils.compareEntities( |
| entity1, sourceSpan1.begin, null, entity2, sourceSpan2.begin, null); |
| } |
| |
| @override |
| Iterable<LibraryEntity> sortLibraries(Iterable<LibraryEntity> libraries) { |
| return libraries.toList()..sort(_compareLibraries); |
| } |
| |
| @override |
| Iterable<T> sortMembers<T extends MemberEntity>(Iterable<T> members) { |
| return members.toList()..sort(compareMembersByLocation); |
| } |
| |
| @override |
| Iterable<ClassEntity> sortClasses(Iterable<ClassEntity> classes) { |
| List<ClassEntity> regularClasses = <ClassEntity>[]; |
| List<ClassEntity> unnamedMixins = <ClassEntity>[]; |
| for (ClassEntity cls in classes) { |
| if (elementMap.elementEnvironment.isUnnamedMixinApplication(cls)) { |
| unnamedMixins.add(cls); |
| } else { |
| regularClasses.add(cls); |
| } |
| } |
| List<ClassEntity> sorted = <ClassEntity>[]; |
| regularClasses.sort(compareClassesByLocation); |
| sorted.addAll(regularClasses); |
| unnamedMixins.sort((a, b) { |
| int result = _compareLibraries(a.library, b.library); |
| if (result != 0) return result; |
| result = a.name.compareTo(b.name); |
| assert(result != 0, |
| failedAt(a, "Multiple mixins named ${a.name}: $a vs $b.")); |
| return result; |
| }); |
| sorted.addAll(unnamedMixins); |
| return sorted; |
| } |
| |
| @override |
| int compareLibrariesByLocation(LibraryEntity a, LibraryEntity b) { |
| return _compareLibraries(a, b); |
| } |
| |
| @override |
| int compareClassesByLocation(ClassEntity a, ClassEntity b) { |
| int r = _compareLibraries(a.library, b.library); |
| if (r != 0) return r; |
| ClassDefinition definition1 = elementMap.getClassDefinition(a); |
| ClassDefinition definition2 = elementMap.getClassDefinition(b); |
| return _compareSourceSpans( |
| a, definition1.location, b, definition2.location); |
| } |
| |
| @override |
| int compareMembersByLocation(MemberEntity a, MemberEntity b) { |
| int r = _compareLibraries(a.library, b.library); |
| if (r != 0) return r; |
| MemberDefinition definition1 = elementMap.getMemberDefinition(a); |
| MemberDefinition definition2 = elementMap.getMemberDefinition(b); |
| return _compareSourceSpans( |
| a, definition1.location, b, definition2.location); |
| } |
| } |
| |
| /// [LocalLookup] implementation used to deserialize [JsClosedWorld]. |
| class LocalLookupImpl implements LocalLookup { |
| final GlobalLocalsMap _globalLocalsMap; |
| |
| LocalLookupImpl(this._globalLocalsMap); |
| |
| @override |
| Local getLocalByIndex(MemberEntity memberContext, int index) { |
| KernelToLocalsMapImpl map = _globalLocalsMap.getLocalsMap(memberContext); |
| return map.getLocalByIndex(index); |
| } |
| } |