| // Copyright (c) 2014, 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 deferred_load; |
| |
| import 'constants/expressions.dart'; |
| import 'constants/values.dart' show |
| ConstantValue, |
| ConstructedConstantValue, |
| DeferredConstantValue, |
| StringConstantValue; |
| |
| import 'dart2jslib.dart' show |
| Backend, |
| Compiler, |
| CompilerTask, |
| invariant, |
| MessageKind; |
| |
| import 'dart_backend/dart_backend.dart' show |
| DartBackend; |
| |
| import 'js_backend/js_backend.dart' show |
| JavaScriptBackend; |
| |
| import 'elements/elements.dart' show |
| AstElement, |
| ClassElement, |
| Element, |
| ElementKind, |
| Elements, |
| FunctionElement, |
| LibraryElement, |
| MetadataAnnotation, |
| PrefixElement, |
| ScopeContainerElement, |
| TypedefElement, |
| VoidElement; |
| |
| import 'util/util.dart' show |
| Link, makeUnique; |
| import 'util/uri_extras.dart' as uri_extras; |
| |
| import 'util/setlet.dart' show |
| Setlet; |
| |
| import 'tree/tree.dart' show |
| Import, |
| LibraryTag, |
| LibraryDependency, |
| LiteralDartString, |
| LiteralString, |
| NewExpression, |
| Node; |
| |
| import 'tree/tree.dart' as ast; |
| |
| import 'resolution/resolution.dart' show |
| AnalyzableElementX, |
| TreeElements; |
| |
| /// A "hunk" of the program that will be loaded whenever one of its [imports] |
| /// are loaded. |
| /// |
| /// Elements that are only used in one deferred import, is in an OutputUnit with |
| /// the deferred import as single element in the [imports] set. |
| /// |
| /// Whenever a deferred Element is shared between several deferred imports it is |
| /// in an output unit with those imports in the [imports] Set. |
| /// |
| /// OutputUnits are equal if their [imports] are equal. |
| class OutputUnit { |
| /// The deferred imports that will load this output unit when one of them is |
| /// loaded. |
| final Setlet<Import> imports = new Setlet<Import>(); |
| |
| /// `true` if this output unit is for the main output file. |
| final bool isMainOutput; |
| |
| /// A unique name representing this [OutputUnit]. |
| /// Based on the set of [imports]. |
| String name; |
| |
| OutputUnit({this.isMainOutput: false}); |
| |
| String toString() => "OutputUnit($name)"; |
| |
| bool operator==(OutputUnit other) { |
| return imports.length == other.imports.length && |
| imports.containsAll(other.imports); |
| } |
| |
| int get hashCode { |
| int sum = 0; |
| for (Import import in imports) { |
| sum = (sum + import.hashCode) & 0x3FFFFFFF; // Stay in 30 bit range. |
| } |
| return sum; |
| } |
| } |
| |
| /// For each deferred import, find elements and constants to be loaded when that |
| /// import is loaded. Elements that are used by several deferred imports are in |
| /// shared OutputUnits. |
| class DeferredLoadTask extends CompilerTask { |
| /// The name of this task. |
| String get name => 'Deferred Loading'; |
| |
| /// DeferredLibrary from dart:async |
| ClassElement get deferredLibraryClass => compiler.deferredLibraryClass; |
| |
| /// A synthetic [Import] representing the loading of the main |
| /// program. |
| final Import _fakeMainImport = new Import(null, new LiteralString(null, |
| new LiteralDartString("main")), null, null, null); |
| |
| /// The OutputUnit that will be loaded when the program starts. |
| final OutputUnit mainOutputUnit = new OutputUnit(isMainOutput: true); |
| |
| /// A set containing (eventually) all output units that will result from the |
| /// program. |
| final Set<OutputUnit> allOutputUnits = new Set<OutputUnit>(); |
| |
| /// Will be `true` if the program contains deferred libraries. |
| bool isProgramSplit = false; |
| |
| /// A mapping from the name of a defer import to all the output units it |
| /// depends on in a list of lists to be loaded in the order they appear. |
| /// |
| /// For example {"lib1": [[lib1_lib2_lib3], [lib1_lib2, lib1_lib3], |
| /// [lib1]]} would mean that in order to load "lib1" first the hunk |
| /// lib1_lib2_lib2 should be loaded, then the hunks lib1_lib2 and lib1_lib3 |
| /// can be loaded in parallel. And finally lib1 can be loaded. |
| final Map<String, List<OutputUnit>> hunksToLoad = |
| new Map<String, List<OutputUnit>>(); |
| final Map<Import, String> importDeferName = new Map<Import, String>(); |
| |
| /// A mapping from elements and constants to their output unit. Query this via |
| /// [outputUnitForElement] |
| final Map<Element, OutputUnit> _elementToOutputUnit = |
| new Map<Element, OutputUnit>(); |
| |
| /// A mapping from constants to their output unit. Query this via |
| /// [outputUnitForConstant] |
| final Map<ConstantValue, OutputUnit> _constantToOutputUnit = |
| new Map<ConstantValue, OutputUnit>(); |
| |
| /// All the imports with a [DeferredLibrary] annotation, mapped to the |
| /// [LibraryElement] they import. |
| /// The main library is included in this set for convenience. |
| final Map<Import, LibraryElement> _allDeferredImports = |
| new Map<Import, LibraryElement>(); |
| |
| /// Because the token-stream is forgotten later in the program, we cache a |
| /// description of each deferred import. |
| final _deferredImportDescriptions = new Map<Import, ImportDescription>(); |
| |
| // For each deferred import we want to know exactly what elements have to |
| // be loaded. |
| Map<Import, Set<Element>> _importedDeferredBy = null; |
| Map<Import, Set<ConstantValue>> _constantsDeferredBy = null; |
| |
| Set<Element> _mainElements = new Set<Element>(); |
| |
| DeferredLoadTask(Compiler compiler) : super(compiler) { |
| mainOutputUnit.imports.add(_fakeMainImport); |
| } |
| |
| Backend get backend => compiler.backend; |
| |
| /// Returns the [OutputUnit] where [element] belongs. |
| OutputUnit outputUnitForElement(Element element) { |
| if (!isProgramSplit) return mainOutputUnit; |
| |
| element = element.implementation; |
| while (!_elementToOutputUnit.containsKey(element)) { |
| // TODO(21051): workaround: it looks like we output annotation constants |
| // for classes that we don't include in the output. This seems to happen |
| // when we have reflection but can see that some classes are not needed. |
| // We still add the annotation but don't run through it below (where we |
| // assign every element to its output unit). |
| if (element.enclosingElement == null) { |
| _elementToOutputUnit[element] = mainOutputUnit; |
| break; |
| } |
| element = element.enclosingElement.implementation; |
| } |
| return _elementToOutputUnit[element]; |
| } |
| |
| /// Returns the [OutputUnit] where [constant] belongs. |
| OutputUnit outputUnitForConstant(ConstantValue constant) { |
| if (!isProgramSplit) return mainOutputUnit; |
| |
| return _constantToOutputUnit[constant]; |
| } |
| |
| bool isDeferred(Element element) { |
| return outputUnitForElement(element) != mainOutputUnit; |
| } |
| |
| /// Returns true if e1 and e2 are in the same output unit. |
| bool inSameOutputUnit(Element e1, Element e2) { |
| return outputUnitForElement(e1) == outputUnitForElement(e2); |
| } |
| |
| void registerConstantDeferredUse(DeferredConstantValue constant, |
| PrefixElement prefix) { |
| OutputUnit outputUnit = new OutputUnit(); |
| outputUnit.imports.add(prefix.deferredImport); |
| _constantToOutputUnit[constant] = outputUnit; |
| } |
| |
| /// Answers whether [element] is explicitly deferred when referred to from |
| /// [library]. |
| bool _isExplicitlyDeferred(Element element, LibraryElement library) { |
| Link<Import> imports = _getImports(element, library); |
| // If the element is not imported explicitly, it is implicitly imported |
| // not deferred. |
| if (imports.isEmpty) return false; |
| // An element could potentially be loaded by several imports. If all of them |
| // is explicitly deferred, we say the element is explicitly deferred. |
| // TODO(sigurdm): We might want to give a warning if the imports do not |
| // agree. |
| return imports.every((Import import) => import.isDeferred); |
| } |
| |
| /// Returns a [Link] of every [Import] that imports [element] into [library]. |
| Link<Import> _getImports(Element element, LibraryElement library) { |
| if (element.isClassMember) { |
| element = element.enclosingClass; |
| } |
| if (element.isAccessor) { |
| element = (element as FunctionElement).abstractField; |
| } |
| return library.getImportsFor(element); |
| } |
| |
| /// Finds all elements and constants that [element] depends directly on. |
| /// (not the transitive closure.) |
| /// |
| /// Adds the results to [elements] and [constants]. |
| void _collectAllElementsAndConstantsResolvedFrom( |
| Element element, |
| Set<Element> elements, |
| Set<ConstantValue> constants, |
| isMirrorUsage) { |
| |
| /// Recursively add the constant and its dependencies to [constants]. |
| void addConstants(ConstantValue constant) { |
| if (constants.contains(constant)) return; |
| constants.add(constant); |
| if (constant is ConstructedConstantValue) { |
| elements.add(constant.type.element); |
| } |
| constant.getDependencies().forEach(addConstants); |
| } |
| |
| /// Collects all direct dependencies of [element]. |
| /// |
| /// The collected dependent elements and constants are are added to |
| /// [elements] and [constants] respectively. |
| void collectDependencies(Element element) { |
| // TODO(johnniwinther): Remove this when [AbstractFieldElement] has been |
| // removed. |
| if (element is! AstElement) return; |
| AstElement astElement = element; |
| |
| // TODO(sigurdm): We want to be more specific about this - need a better |
| // way to query "liveness". |
| if (astElement is! TypedefElement && |
| !compiler.enqueuer.resolution.hasBeenResolved(astElement)) { |
| return; |
| } |
| |
| TreeElements treeElements = astElement.resolvedAst.elements; |
| |
| assert(treeElements != null); |
| |
| for (Element dependency in treeElements.allElements) { |
| if (dependency.isLocal && !dependency.isFunction) continue; |
| if (dependency.isErroneous) continue; |
| if (dependency.isTypeVariable) continue; |
| |
| elements.add(dependency); |
| } |
| treeElements.forEachConstantNode((Node node, _) { |
| // Explicitly depend on the backend constants. |
| addConstants( |
| backend.constants.getConstantForNode(node, treeElements).value); |
| }); |
| elements.addAll(treeElements.otherDependencies); |
| } |
| |
| // TODO(sigurdm): How is metadata on a patch-class handled? |
| for (MetadataAnnotation metadata in element.metadata) { |
| ConstantExpression constant = |
| backend.constants.getConstantForMetadata(metadata); |
| if (constant != null) { |
| addConstants(constant.value); |
| } |
| } |
| if (element.isClass) { |
| // If we see a class, add everything its live instance members refer |
| // to. Static members are not relevant, unless we are processing |
| // extra dependencies due to mirrors. |
| void addLiveInstanceMember(Element element) { |
| if (!compiler.enqueuer.resolution.hasBeenResolved(element)) return; |
| if (!isMirrorUsage && !element.isInstanceMember) return; |
| collectDependencies(element.implementation); |
| } |
| ClassElement cls = element.declaration; |
| cls.forEachLocalMember(addLiveInstanceMember); |
| if (cls.implementation != cls) { |
| // TODO(ahe): Why doesn't ClassElement.forEachLocalMember do this? |
| cls.implementation.forEachLocalMember(addLiveInstanceMember); |
| } |
| for (var type in cls.implementation.allSupertypes) { |
| elements.add(type.element.implementation); |
| } |
| elements.add(cls.implementation); |
| } else if (Elements.isStaticOrTopLevel(element) || |
| element.isConstructor) { |
| collectDependencies(element); |
| } |
| if (element.isGenerativeConstructor) { |
| // When instantiating a class, we record a reference to the |
| // constructor, not the class itself. We must add all the |
| // instance members of the constructor's class. |
| ClassElement implementation = |
| element.enclosingClass.implementation; |
| _collectAllElementsAndConstantsResolvedFrom( |
| implementation, elements, constants, isMirrorUsage); |
| } |
| |
| // Other elements, in particular instance members, are ignored as |
| // they are processed as part of the class. |
| } |
| |
| /// Returns the transitive closure of all libraries that are imported |
| /// from root without DeferredLibrary annotations. |
| Set<LibraryElement> _nonDeferredReachableLibraries(LibraryElement root) { |
| Set<LibraryElement> result = new Set<LibraryElement>(); |
| |
| void traverseLibrary(LibraryElement library) { |
| if (result.contains(library)) return; |
| result.add(library); |
| |
| iterateTags(LibraryElement library) { |
| // TODO(sigurdm): Make helper getLibraryDependencyTags when tags is |
| // changed to be a List instead of a Link. |
| for (LibraryTag tag in library.tags) { |
| if (tag is! LibraryDependency) continue; |
| LibraryDependency libraryDependency = tag; |
| if (!(libraryDependency is Import && libraryDependency.isDeferred)) { |
| LibraryElement importedLibrary = library.getLibraryFromTag(tag); |
| traverseLibrary(importedLibrary); |
| } |
| } |
| } |
| |
| iterateTags(library); |
| if (library.isPatched) { |
| iterateTags(library.implementation); |
| } |
| } |
| traverseLibrary(root); |
| result.add(compiler.coreLibrary); |
| return result; |
| } |
| |
| /// Recursively traverses the graph of dependencies from [element], mapping |
| /// deferred imports to each dependency it needs in the sets |
| /// [_importedDeferredBy] and [_constantsDeferredBy]. |
| void _mapDependencies(Element element, Import import, |
| {isMirrorUsage: false}) { |
| Set<Element> elements = _importedDeferredBy.putIfAbsent(import, |
| () => new Set<Element>()); |
| Set<ConstantValue> constants = _constantsDeferredBy.putIfAbsent(import, |
| () => new Set<ConstantValue>()); |
| |
| // Only process elements once, unless we are doing dependencies due to |
| // mirrors, which are added in additional traversals. |
| if (!isMirrorUsage && elements.contains(element)) return; |
| // Anything used directly by main will be loaded from the start |
| // We do not need to traverse it again. |
| if (import != _fakeMainImport && _mainElements.contains(element)) return; |
| |
| // Here we modify [_importedDeferredBy]. |
| elements.add(element); |
| |
| Set<Element> dependentElements = new Set<Element>(); |
| |
| // This call can modify [_importedDeferredBy] and [_constantsDeferredBy]. |
| _collectAllElementsAndConstantsResolvedFrom( |
| element, dependentElements, constants, isMirrorUsage); |
| |
| LibraryElement library = element.library; |
| for (Element dependency in dependentElements) { |
| if (_isExplicitlyDeferred(dependency, library)) { |
| for (Import deferredImport in _getImports(dependency, library)) { |
| _mapDependencies(dependency, deferredImport); |
| }; |
| } else { |
| _mapDependencies(dependency, import); |
| } |
| } |
| } |
| |
| /// Adds extra dependencies coming from mirror usage. |
| /// |
| /// The elements are added with [_mapDependencies]. |
| void _addMirrorElements() { |
| void mapDependenciesIfResolved(Element element, Import deferredImport) { |
| // If an element is the target of a MirrorsUsed annotation but never used |
| // It will not be resolved, and we should not call isNeededForReflection. |
| // TODO(sigurdm): Unresolved elements should just answer false when |
| // asked isNeededForReflection. Instead an internal error is triggered. |
| // So we have to filter them out here. |
| if (element is AnalyzableElementX && !element.hasTreeElements) return; |
| if (compiler.backend.isAccessibleByReflection(element)) { |
| _mapDependencies(element, deferredImport, isMirrorUsage: true); |
| } |
| } |
| |
| // For each deferred import we analyze all elements reachable from the |
| // imported library through non-deferred imports. |
| handleLibrary(LibraryElement library, Import deferredImport) { |
| library.implementation.forEachLocalMember((Element element) { |
| mapDependenciesIfResolved(element, deferredImport); |
| }); |
| |
| for (MetadataAnnotation metadata in library.metadata) { |
| ConstantExpression constant = |
| backend.constants.getConstantForMetadata(metadata); |
| if (constant != null) { |
| _mapDependencies(constant.value.getType(compiler.coreTypes).element, |
| deferredImport); |
| } |
| } |
| for (LibraryTag tag in library.tags) { |
| for (MetadataAnnotation metadata in tag.metadata) { |
| ConstantExpression constant = |
| backend.constants.getConstantForMetadata(metadata); |
| if (constant != null) { |
| _mapDependencies(constant.value.getType(compiler.coreTypes).element, |
| deferredImport); |
| } |
| } |
| } |
| } |
| |
| for (Import deferredImport in _allDeferredImports.keys) { |
| LibraryElement deferredLibrary = _allDeferredImports[deferredImport]; |
| for (LibraryElement library in |
| _nonDeferredReachableLibraries(deferredLibrary)) { |
| handleLibrary(library, deferredImport); |
| } |
| } |
| } |
| |
| /// Computes a unique string for the name field for each outputUnit. |
| /// |
| /// Also sets up the [hunksToLoad] mapping. |
| void _assignNamesToOutputUnits(Set<OutputUnit> allOutputUnits) { |
| Set<String> usedImportNames = new Set<String>(); |
| |
| // Finds the first argument to the [DeferredLibrary] annotation |
| void computeImportDeferName(Import import) { |
| String result; |
| if (import == _fakeMainImport) { |
| result = "main"; |
| } else if (import.isDeferred) { |
| result = import.prefix.toString(); |
| } else { |
| Link<MetadataAnnotation> metadatas = import.metadata; |
| assert(metadatas != null); |
| for (MetadataAnnotation metadata in metadatas) { |
| metadata.ensureResolved(compiler); |
| Element element = |
| metadata.constant.value.getType(compiler.coreTypes).element; |
| if (element == deferredLibraryClass) { |
| ConstructedConstantValue constant = metadata.constant.value; |
| StringConstantValue s = constant.fields[0]; |
| result = s.primitiveValue.slowToString(); |
| break; |
| } |
| } |
| } |
| assert(result != null); |
| importDeferName[import] = makeUnique(result, usedImportNames);; |
| } |
| |
| int counter = 1; |
| |
| for (Import import in _allDeferredImports.keys) { |
| computeImportDeferName(import); |
| } |
| |
| for (OutputUnit outputUnit in allOutputUnits) { |
| if (outputUnit == mainOutputUnit) { |
| outputUnit.name = "main"; |
| } else { |
| outputUnit.name = "$counter"; |
| ++counter; |
| } |
| } |
| |
| List sortedOutputUnits = new List.from(allOutputUnits); |
| // Sort the output units in descending order of the number of imports they |
| // include. |
| |
| // The loading of the output units mut be ordered because a superclass needs |
| // to be initialized before its subclass. |
| // But a class can only depend on another class in an output unit shared by |
| // a strict superset of the imports: |
| // By contradiction: Assume a class C in output unit shared by imports in |
| // the set S1 = (lib1,.., lib_n) depends on a class D in an output unit |
| // shared by S2 such that S2 not a superset of S1. Let lib_s be a library in |
| // S1 not in S2. lib_s must depend on C, and then in turn on D therefore D |
| // is not in the right output unit. |
| sortedOutputUnits.sort((a, b) => b.imports.length - a.imports.length); |
| |
| // For each deferred import we find out which outputUnits to load. |
| for (Import import in _allDeferredImports.keys) { |
| if (import == _fakeMainImport) continue; |
| hunksToLoad[importDeferName[import]] = new List<OutputUnit>(); |
| for (OutputUnit outputUnit in sortedOutputUnits) { |
| if (outputUnit == mainOutputUnit) continue; |
| if (outputUnit.imports.contains(import)) { |
| hunksToLoad[importDeferName[import]].add(outputUnit); |
| } |
| } |
| } |
| } |
| |
| void onResolutionComplete(FunctionElement main) { |
| if (!isProgramSplit) { |
| allOutputUnits.add(mainOutputUnit); |
| return; |
| } |
| if (main == null) return; |
| LibraryElement mainLibrary = main.library; |
| _importedDeferredBy = new Map<Import, Set<Element>>(); |
| _constantsDeferredBy = new Map<Import, Set<ConstantValue>>(); |
| _importedDeferredBy[_fakeMainImport] = _mainElements; |
| |
| measureElement(mainLibrary, () { |
| |
| // Starting from main, traverse the program and find all dependencies. |
| _mapDependencies(compiler.mainFunction, _fakeMainImport); |
| |
| // Also add "global" dependencies to the main OutputUnit. These are |
| // things that the backend need but cannot associate with a particular |
| // element, for example, startRootIsolate. This set also contains |
| // elements for which we lack precise information. |
| for (Element element in compiler.globalDependencies.otherDependencies) { |
| _mapDependencies(element, _fakeMainImport); |
| } |
| |
| // Now check to see if we have to add more elements due to mirrors. |
| if (compiler.mirrorsLibrary != null) { |
| _addMirrorElements(); |
| } |
| |
| // Build the OutputUnits using these two maps. |
| Map<Element, OutputUnit> elementToOutputUnitBuilder = |
| new Map<Element, OutputUnit>(); |
| Map<ConstantValue, OutputUnit> constantToOutputUnitBuilder = |
| new Map<ConstantValue, OutputUnit>(); |
| |
| // Reverse the mappings. For each element record an OutputUnit collecting |
| // all deferred imports mapped to this element. Same for constants. |
| for (Import import in _importedDeferredBy.keys) { |
| for (Element element in _importedDeferredBy[import]) { |
| // Only one file should be loaded when the program starts, so make |
| // sure that only one OutputUnit is created for [fakeMainImport]. |
| if (import == _fakeMainImport) { |
| elementToOutputUnitBuilder[element] = mainOutputUnit; |
| } else { |
| elementToOutputUnitBuilder |
| .putIfAbsent(element, () => new OutputUnit()) |
| .imports.add(import); |
| } |
| } |
| for (ConstantValue constant in _constantsDeferredBy[import]) { |
| // Only one file should be loaded when the program starts, so make |
| // sure that only one OutputUnit is created for [fakeMainImport]. |
| if (import == _fakeMainImport) { |
| constantToOutputUnitBuilder[constant] = mainOutputUnit; |
| } else { |
| constantToOutputUnitBuilder |
| .putIfAbsent(constant, () => new OutputUnit()) |
| .imports.add(import); |
| } |
| } |
| } |
| |
| // Release maps; |
| _importedDeferredBy = null; |
| _constantsDeferredBy = null; |
| |
| // Find all the output units elements/constants have been mapped to, and |
| // canonicalize them. |
| elementToOutputUnitBuilder.forEach( |
| (Element element, OutputUnit outputUnit) { |
| OutputUnit representative = allOutputUnits.lookup(outputUnit); |
| if (representative == null) { |
| representative = outputUnit; |
| allOutputUnits.add(representative); |
| } |
| _elementToOutputUnit[element] = representative; |
| }); |
| constantToOutputUnitBuilder.forEach( |
| (ConstantValue constant, OutputUnit outputUnit) { |
| OutputUnit representative = allOutputUnits.lookup(outputUnit); |
| if (representative == null) { |
| representative = outputUnit; |
| allOutputUnits.add(representative); |
| } |
| _constantToOutputUnit[constant] = representative; |
| }); |
| |
| // Generate a unique name for each OutputUnit. |
| _assignNamesToOutputUnits(allOutputUnits); |
| }); |
| } |
| |
| void beforeResolution(Compiler compiler) { |
| if (compiler.mainApp == null) return; |
| _allDeferredImports[_fakeMainImport] = compiler.mainApp; |
| var lastDeferred; |
| // When detecting duplicate prefixes of deferred libraries there are 4 |
| // cases of duplicate prefixes: |
| // 1. |
| // import "lib.dart" deferred as a; |
| // import "lib2.dart" deferred as a; |
| // 2. |
| // import "lib.dart" deferred as a; |
| // import "lib2.dart" as a; |
| // 3. |
| // import "lib.dart" as a; |
| // import "lib2.dart" deferred as a; |
| // 4. |
| // import "lib.dart" as a; |
| // import "lib2.dart" as a; |
| // We must be able to signal error for case 1, 2, 3, but accept case 4. |
| |
| // The prefixes that have been used by any imports in this library. |
| Setlet<String> usedPrefixes = new Setlet<String>(); |
| // The last deferred import we saw with a given prefix (if any). |
| Map<String, Import> prefixDeferredImport = new Map<String, Import>(); |
| for (LibraryElement library in compiler.libraryLoader.libraries) { |
| compiler.withCurrentElement(library, () { |
| prefixDeferredImport.clear(); |
| usedPrefixes.clear(); |
| // TODO(sigurdm): Make helper getLibraryImportTags when tags is a List |
| // instead of a Link. |
| for (LibraryTag tag in library.tags) { |
| if (tag is! Import) continue; |
| Import import = tag; |
| |
| /// Give an error if the old annotation-based syntax has been used. |
| Link<MetadataAnnotation> metadataList = import.metadata; |
| if (metadataList != null) { |
| for (MetadataAnnotation metadata in metadataList) { |
| metadata.ensureResolved(compiler); |
| Element element = |
| metadata.constant.value.getType(compiler.coreTypes).element; |
| if (element == deferredLibraryClass) { |
| compiler.reportError( |
| import, MessageKind.DEFERRED_OLD_SYNTAX); |
| } |
| } |
| } |
| |
| String prefix = (import.prefix != null) |
| ? import.prefix.toString() |
| : null; |
| // The last import we saw with the same prefix. |
| Import previousDeferredImport = prefixDeferredImport[prefix]; |
| if (import.isDeferred) { |
| LibraryElement importedLibrary = library.getLibraryFromTag(import); |
| _allDeferredImports[import] = importedLibrary; |
| |
| if (prefix == null) { |
| compiler.reportError(import, |
| MessageKind.DEFERRED_LIBRARY_WITHOUT_PREFIX); |
| } else { |
| prefixDeferredImport[prefix] = import; |
| _deferredImportDescriptions[import] = |
| new ImportDescription(import, library, compiler); |
| } |
| isProgramSplit = true; |
| lastDeferred = import; |
| } |
| if (prefix != null) { |
| if (previousDeferredImport != null || |
| (import.isDeferred && usedPrefixes.contains(prefix))) { |
| Import failingImport = (previousDeferredImport != null) |
| ? previousDeferredImport |
| : import; |
| compiler.reportError(failingImport.prefix, |
| MessageKind.DEFERRED_LIBRARY_DUPLICATE_PREFIX); |
| } |
| usedPrefixes.add(prefix); |
| } |
| } |
| }); |
| } |
| Backend backend = compiler.backend; |
| if (isProgramSplit && backend is JavaScriptBackend) { |
| backend.registerCheckDeferredIsLoaded(compiler.globalDependencies); |
| } |
| if (isProgramSplit && backend is DartBackend) { |
| // TODO(sigurdm): Implement deferred loading for dart2dart. |
| compiler.reportWarning( |
| lastDeferred, |
| MessageKind.DEFERRED_LIBRARY_DART_2_DART); |
| isProgramSplit = false; |
| } |
| } |
| |
| /// If [send] is a static send with a deferred element, returns the |
| /// [PrefixElement] that the first prefix of the send resolves to. |
| /// Otherwise returns null. |
| /// |
| /// Precondition: send must be static. |
| /// |
| /// Example: |
| /// |
| /// import "a.dart" deferred as a; |
| /// |
| /// main() { |
| /// print(a.loadLibrary.toString()); |
| /// a.loadLibrary().then((_) { |
| /// a.run(); |
| /// a.foo.method(); |
| /// }); |
| /// } |
| /// |
| /// Returns null for a.loadLibrary() (the special |
| /// function loadLibrary is not deferred). And returns the PrefixElement for |
| /// a.run() and a.foo. |
| /// a.loadLibrary.toString() and a.foo.method() are dynamic sends - and |
| /// this functions should not be called on them. |
| PrefixElement deferredPrefixElement(ast.Send send, TreeElements elements) { |
| Element element = elements[send]; |
| // The DeferredLoaderGetter is not deferred, therefore we do not return the |
| // prefix. |
| if (element != null && element.isDeferredLoaderGetter) return null; |
| |
| ast.Node firstNode(ast.Node node) { |
| if (node is! ast.Send) { |
| return node; |
| } else { |
| ast.Send send = node; |
| ast.Node receiver = send.receiver; |
| ast.Node receiverFirst = firstNode(receiver); |
| if (receiverFirst != null) { |
| return receiverFirst; |
| } else { |
| return firstNode(send.selector); |
| } |
| } |
| } |
| ast.Node first = firstNode(send); |
| ast.Node identifier = first.asIdentifier(); |
| if (identifier == null) return null; |
| Element maybePrefix = elements[identifier]; |
| if (maybePrefix != null && maybePrefix.isPrefix) { |
| PrefixElement prefixElement = maybePrefix; |
| if (prefixElement.isDeferred) { |
| return prefixElement; |
| } |
| } |
| return null; |
| } |
| |
| /// Returns a json-style map for describing what files that are loaded by a |
| /// given deferred import. |
| /// The mapping is structured as: |
| /// library uri -> {"name": library name, "files": (prefix -> list of files)} |
| /// Where |
| /// |
| /// - <library uri> is the relative uri of the library making a deferred |
| /// import |
| /// - <library name> is the name of the libary, and "<unnamed>" if it is |
| /// unnamed. |
| /// - <prefix> is the `as` prefix used for a given deferred import. |
| /// - <list of files> is a list of the filenames the must be loaded when that |
| /// import is loaded. |
| Map<String, Map<String, dynamic>> computeDeferredMap() { |
| JavaScriptBackend backend = compiler.backend; |
| Map<String, Map<String, dynamic>> mapping = |
| new Map<String, Map<String, dynamic>>(); |
| _deferredImportDescriptions.keys.forEach((ast.Import import) { |
| List<OutputUnit> outputUnits = hunksToLoad[importDeferName[import]]; |
| ImportDescription description = _deferredImportDescriptions[import]; |
| Map<String, dynamic> libraryMap = |
| mapping.putIfAbsent(description.importingUri, |
| () => <String, dynamic>{"name": description.importingLibraryName, |
| "imports": <String, List<String>>{}}); |
| |
| libraryMap["imports"][description.prefix] = outputUnits.map( |
| (OutputUnit outputUnit) { |
| return backend.deferredPartFileName(outputUnit.name); |
| }).toList(); |
| }); |
| return mapping; |
| } |
| } |
| |
| class ImportDescription { |
| /// Relative uri to the importing library. |
| final String importingUri; |
| /// The prefix this import is imported as. |
| final String prefix; |
| final LibraryElement _importingLibrary; |
| |
| ImportDescription(Import import, |
| LibraryElement importingLibrary, |
| Compiler compiler) |
| : importingUri = uri_extras.relativize( |
| compiler.mainApp.canonicalUri, |
| importingLibrary.canonicalUri, false), |
| prefix = import.prefix.source, |
| _importingLibrary = importingLibrary; |
| |
| String get importingLibraryName { |
| String libraryName = _importingLibrary.getLibraryName(); |
| return libraryName == "" |
| ? "<unnamed>" |
| : libraryName; |
| } |
| |
| } |