blob: ff7d59ae25c578070bcbb5c6a2221b60b70e6c01 [file] [log] [blame]
// Copyright (c) 2012, 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 dart2js.library_loader;
import 'dart:async';
import 'dart2jslib.dart'
show Compiler,
CompilerTask,
MessageKind,
Script,
invariant;
import 'elements/elements.dart'
show CompilationUnitElement,
Element,
LibraryElement,
PrefixElement;
import 'elements/modelx.dart'
show CompilationUnitElementX,
DeferredLoaderGetterElementX,
ErroneousElementX,
LibraryElementX,
PrefixElementX;
import 'helpers/helpers.dart'; // Included for debug helpers.
import 'native/native.dart' as native;
import 'tree/tree.dart';
import 'util/util.dart' show Link, LinkBuilder;
/**
* [CompilerTask] for loading libraries and setting up the import/export scopes.
*
* The library loader uses four different kinds of URIs in different parts of
* the loading process.
*
* ## User URI ##
*
* A 'user URI' is a URI provided by the user in code and as the main entry URI
* at the command line. These generally come in 3 versions:
*
* * A relative URI such as 'foo.dart', '../bar.dart', and 'baz/boz.dart'.
*
* * A dart URI such as 'dart:core' and 'dart:_js_helper'.
*
* * A package URI such as 'package:foo.dart' and 'package:bar/baz.dart'.
*
* A user URI can also be absolute, like 'file:///foo.dart' or
* 'http://example.com/bar.dart', but such URIs cannot necessarily be used for
* locating source files, since the scheme must be supported by the input
* provider. The standard input provider for dart2js only supports the 'file'
* and 'http' scheme.
*
* ## Resolved URI ##
*
* A 'resolved URI' is a (user) URI that has been resolved to an absolute URI
* based on the readable URI (see below) from which it was loaded. A URI with an
* explicit scheme (such as 'dart:', 'package:' or 'file:') is already resolved.
* A relative URI like for instance '../foo/bar.dart' is translated into an
* resolved URI in one of three ways:
*
* * If provided as the main entry URI at the command line, the URI is resolved
* relative to the current working directory, say
* 'file:///current/working/dir/', and the resolved URI is therefore
* 'file:///current/working/foo/bar.dart'.
*
* * If the relative URI is provided in an import, export or part tag, and the
* readable URI of the enclosing compilation unit is a file URI,
* 'file://some/path/baz.dart', then the resolved URI is
* 'file://some/foo/bar.dart'.
*
* * If the relative URI is provided in an import, export or part tag, and the
* readable URI of the enclosing compilation unit is a package URI,
* 'package:some/path/baz.dart', then the resolved URI is
* 'package:some/foo/bar.dart'.
*
* The resolved URI thus preserves the scheme through resolution: A readable
* file URI results in an resolved file URI and a readable package URI results
* in an resolved package URI. Note that since a dart URI is not a readable URI,
* import, export or part tags within platform libraries are not interpreted as
* dart URIs but instead relative to the library source file location.
*
* The resolved URI of a library is also used as the canonical URI
* ([LibraryElement.canonicalUri]) by which we identify which libraries are
* identical. This means that libraries loaded through the 'package' scheme will
* resolve to the same library when loaded from within using relative URIs (see
* for instance the test 'standalone/package/package1_test.dart'). But loading a
* platform library using a relative URI will _not_ result in the same library
* as when loaded through the dart URI.
*
* ## Readable URI ##
*
* A 'readable URI' is an absolute URI whose scheme is either 'package' or
* something supported by the input provider, normally 'file'. Dart URIs such as
* 'dart:core' and 'dart:_js_helper' are not readable themselves but are instead
* resolved into a readable URI using the library root URI provided from the
* command line and the list of platform libraries found in
* 'sdk/lib/_internal/libraries.dart'. This is done through the
* [Compiler.translateResolvedUri] method which checks whether a library by that
* name exists and in case of internal libraries whether access is granted.
*
* ## Resource URI ##
*
* A 'resource URI' is an absolute URI with a scheme supported by the input
* provider. For the standard implementation this means a URI with the 'file'
* scheme. Readable URIs are converted into resource URIs as part of the
* [Compiler.readScript] method. In the standard implementation the package URIs
* are converted to file URIs using the package root URI provided on the
* command line as base. If the package root URI is
* 'file:///current/working/dir/' then the package URI 'package:foo/bar.dart'
* will be resolved to the resource URI
* 'file:///current/working/dir/foo/bar.dart'.
*
* The distinction between readable URI and resource URI is necessary to ensure
* that these imports
*
* import 'package:foo.dart' as a;
* import 'packages/foo.dart' as b;
*
* do _not_ resolve to the same library when the package root URI happens to
* point to the 'packages' folder.
*
*/
abstract class LibraryLoaderTask implements CompilerTask {
factory LibraryLoaderTask(Compiler compiler) = _LibraryLoaderTask;
/// Returns all libraries that have been loaded.
Iterable<LibraryElement> get libraries;
/// Looks up the library with the [canonicalUri].
LibraryElement lookupLibrary(Uri canonicalUri);
/// Loads the library specified by the [resolvedUri] and returns its
/// [LibraryElement].
///
/// If the library is not already loaded, the method creates the
/// [LibraryElement] for the library and computes the import/export scope,
/// loading and computing the import/export scopes of all required libraries
/// in the process. The method handles cyclic dependency between libraries.
Future<LibraryElement> loadLibrary(Uri resolvedUri);
/// Reset the library loader task to prepare for compilation. If provided,
/// libraries matching [reuseLibrary] are reused.
///
/// This method is used for incremental compilation.
void reset({bool reuseLibrary(LibraryElement library)});
/// Asynchronous version of [reset].
Future resetAsync(Future<bool> reuseLibrary(LibraryElement library));
}
/// Handle for creating synthesized/patch libraries during library loading.
abstract class LibraryLoader {
/// This method must be called when a new synthesized/patch library has been
/// created to ensure that [library] will part of library dependency graph
/// used for computing import/export scopes.
void registerNewLibrary(LibraryElement library);
/// This method must be called when a new synthesized/patch library has been
/// scanned in order to process the library tags in [library] and thus handle
/// imports/exports/parts in the synthesized/patch library.
Future processLibraryTags(LibraryElement library);
}
/**
* [CombinatorFilter] is a succinct representation of a list of combinators from
* a library dependency tag.
*/
class CombinatorFilter {
const CombinatorFilter();
/**
* Returns [:true:] if [element] is excluded by this filter.
*/
bool exclude(Element element) => false;
/**
* Creates a filter based on the combinators of [tag].
*/
factory CombinatorFilter.fromTag(LibraryDependency tag) {
if (tag == null || tag.combinators == null) {
return const CombinatorFilter();
}
// If the list of combinators contain at least one [:show:] we can create
// a positive list of elements to include, otherwise we create a negative
// list of elements to exclude.
bool show = false;
Set<String> nameSet;
for (Combinator combinator in tag.combinators) {
if (combinator.isShow) {
show = true;
var set = new Set<String>();
for (Identifier identifier in combinator.identifiers) {
set.add(identifier.source);
}
if (nameSet == null) {
nameSet = set;
} else {
nameSet = nameSet.intersection(set);
}
}
}
if (nameSet == null) {
nameSet = new Set<String>();
}
for (Combinator combinator in tag.combinators) {
if (combinator.isHide) {
for (Identifier identifier in combinator.identifiers) {
if (show) {
// We have a positive list => Remove hidden elements.
nameSet.remove(identifier.source);
} else {
// We have no positive list => Accumulate hidden elements.
nameSet.add(identifier.source);
}
}
}
}
return show ? new ShowFilter(nameSet) : new HideFilter(nameSet);
}
}
/**
* A list of combinators represented as a list of element names to include.
*/
class ShowFilter extends CombinatorFilter {
final Set<String> includedNames;
ShowFilter(this.includedNames);
bool exclude(Element element) => !includedNames.contains(element.name);
}
/**
* A list of combinators represented as a list of element names to exclude.
*/
class HideFilter extends CombinatorFilter {
final Set<String> excludedNames;
HideFilter(this.excludedNames);
bool exclude(Element element) => excludedNames.contains(element.name);
}
/**
* Implementation class for [LibraryLoader]. The distinction between
* [LibraryLoader] and [LibraryLoaderTask] is made to hide internal members from
* the [LibraryLoader] interface.
*/
class _LibraryLoaderTask extends CompilerTask implements LibraryLoaderTask {
_LibraryLoaderTask(Compiler compiler) : super(compiler);
String get name => 'LibraryLoader';
final Map<Uri, LibraryElement> libraryCanonicalUriMap =
new Map<Uri, LibraryElement>();
final Map<Uri, LibraryElement> libraryResourceUriMap =
new Map<Uri, LibraryElement>();
final Map<String, LibraryElement> libraryNames =
new Map<String, LibraryElement>();
LibraryDependencyHandler currentHandler;
Iterable<LibraryElement> get libraries => libraryCanonicalUriMap.values;
LibraryElement lookupLibrary(Uri canonicalUri) {
return libraryCanonicalUriMap[canonicalUri];
}
void reset({bool reuseLibrary(LibraryElement library)}) {
measure(() {
assert(currentHandler == null);
Iterable<LibraryElement> reusedLibraries = null;
if (reuseLibrary != null) {
reusedLibraries = compiler.reuseLibraryTask.measure(() {
// Call [toList] to force eager calls to [reuseLibrary].
return libraryCanonicalUriMap.values.where(reuseLibrary).toList();
});
}
resetImplementation(reusedLibraries);
});
}
void resetImplementation(Iterable<LibraryElement> reusedLibraries) {
measure(() {
libraryCanonicalUriMap.clear();
libraryResourceUriMap.clear();
libraryNames.clear();
if (reusedLibraries != null) {
reusedLibraries.forEach(mapLibrary);
}
});
}
Future resetAsync(Future<bool> reuseLibrary(LibraryElement library)) {
return measure(() {
assert(currentHandler == null);
Future<LibraryElement> wrapper(LibraryElement library) {
try {
return reuseLibrary(library).then(
(bool reuse) => reuse ? library : null);
} catch (exception, trace) {
compiler.diagnoseCrashInUserCode(
'Uncaught exception in reuseLibrary', exception, trace);
rethrow;
}
}
List<Future<LibraryElement>> reusedLibrariesFuture =
compiler.reuseLibraryTask.measure(
() => libraryCanonicalUriMap.values.map(wrapper).toList());
return Future.wait(reusedLibrariesFuture).then(
(List<LibraryElement> reusedLibraries) {
resetImplementation(reusedLibraries.where((e) => e != null));
});
});
}
/// Insert [library] in the internal maps. Used for compiler reuse.
void mapLibrary(LibraryElement library) {
libraryCanonicalUriMap[library.canonicalUri] = library;
Uri resourceUri = library.entryCompilationUnit.script.resourceUri;
libraryResourceUriMap[resourceUri] = library;
String name = library.getLibraryOrScriptName();
libraryNames[name] = library;
}
Future<LibraryElement> loadLibrary(Uri resolvedUri) {
return measure(() {
assert(currentHandler == null);
// TODO(johnniwinther): Ensure that currentHandler correctly encloses the
// loading of a library cluster.
currentHandler = new LibraryDependencyHandler(this);
return createLibrary(currentHandler, null, resolvedUri)
.then((LibraryElement library) {
return compiler.withCurrentElement(library, () {
return measure(() {
currentHandler.computeExports();
Map<Uri, LibraryElement> loadedLibraries = <Uri, LibraryElement>{};
currentHandler.loadedLibraries.forEach(
(LibraryElement loadedLibrary) {
loadedLibraries[loadedLibrary.canonicalUri] = loadedLibrary;
});
currentHandler = null;
return compiler.onLibrariesLoaded(loadedLibraries)
.then((_) => library);
});
});
});
});
}
/**
* Processes the library tags in [library].
*
* The imported/exported libraries are loaded and processed recursively but
* the import/export scopes are not set up.
*/
Future processLibraryTags(LibraryDependencyHandler handler,
LibraryElement library) {
int tagState = TagState.NO_TAG_SEEN;
/**
* If [value] is less than [tagState] complain and return
* [tagState]. Otherwise return the new value for [tagState]
* (transition function for state machine).
*/
int checkTag(int value, LibraryTag tag) {
if (tagState > value) {
compiler.reportFatalError(
tag,
MessageKind.GENERIC, {'text': 'Error: Out of order.'});
return tagState;
}
return TagState.NEXT[value];
}
bool importsDartCore = false;
var libraryDependencies = new LinkBuilder<LibraryDependency>();
Uri base = library.entryCompilationUnit.script.readableUri;
return Future.forEach(library.tags, (LibraryTag tag) {
return compiler.withCurrentElement(library, () {
if (tag.isImport) {
Import import = tag;
tagState = checkTag(TagState.IMPORT_OR_EXPORT, import);
if (import.uri.dartString.slowToString() == 'dart:core') {
importsDartCore = true;
}
libraryDependencies.addLast(import);
} else if (tag.isExport) {
tagState = checkTag(TagState.IMPORT_OR_EXPORT, tag);
libraryDependencies.addLast(tag);
} else if (tag.isLibraryName) {
tagState = checkTag(TagState.LIBRARY, tag);
if (library.libraryTag != null) {
compiler.internalError(tag, "Duplicated library declaration.");
} else {
library.libraryTag = tag;
}
} else if (tag.isPart) {
Part part = tag;
StringNode uri = part.uri;
Uri resolvedUri = base.resolve(uri.dartString.slowToString());
tagState = checkTag(TagState.SOURCE, part);
return scanPart(part, resolvedUri, library);
} else {
compiler.internalError(tag, "Unhandled library tag.");
}
});
}).then((_) {
return compiler.onLibraryScanned(library, handler);
}).then((_) {
return compiler.withCurrentElement(library, () {
checkDuplicatedLibraryName(library);
// Import dart:core if not already imported.
if (!importsDartCore && library.canonicalUri != Compiler.DART_CORE) {
return createLibrary(handler, null, Compiler.DART_CORE)
.then((LibraryElement coreLibrary) {
handler.registerDependency(library, null, coreLibrary);
});
}
});
}).then((_) {
return Future.forEach(libraryDependencies.toList(), (tag) {
return compiler.withCurrentElement(library, () {
return registerLibraryFromTag(handler, library, tag);
});
});
});
}
/// True if the uris are pointing to a library that is shared between dart2js
/// and the core libraries. By construction they must be imported into the
/// runtime, and, at the same time, into dart2js. This can lead to
/// duplicated imports, like in the docgen.
// TODO(johnniwinther): is this necessary, or should we change docgen not
// to include both libraries (compiler and lib) at the same time?
bool _isSharedDart2jsLibrary(Uri uri1, Uri uri2) {
bool inJsLibShared(Uri uri) {
List<String> segments = uri.pathSegments;
if (segments.length < 3) return false;
if (segments[segments.length - 2] != 'shared') return false;
return (segments[segments.length - 3] == 'js_lib');
}
return inJsLibShared(uri1) && inJsLibShared(uri2);
}
void checkDuplicatedLibraryName(LibraryElement library) {
Uri resourceUri = library.entryCompilationUnit.script.resourceUri;
LibraryName tag = library.libraryTag;
LibraryElement existing =
libraryResourceUriMap.putIfAbsent(resourceUri, () => library);
if (!identical(existing, library)) {
if (tag != null) {
compiler.withCurrentElement(library, () {
compiler.reportWarning(tag.name,
MessageKind.DUPLICATED_LIBRARY_RESOURCE,
{'libraryName': tag.name,
'resourceUri': resourceUri,
'canonicalUri1': library.canonicalUri,
'canonicalUri2': existing.canonicalUri});
});
} else {
compiler.reportHint(library,
MessageKind.DUPLICATED_RESOURCE,
{'resourceUri': resourceUri,
'canonicalUri1': library.canonicalUri,
'canonicalUri2': existing.canonicalUri});
}
} else if (tag != null) {
String name = library.getLibraryOrScriptName();
existing = libraryNames.putIfAbsent(name, () => library);
if (!identical(existing, library) &&
!_isSharedDart2jsLibrary(resourceUri, existing.canonicalUri)) {
compiler.withCurrentElement(library, () {
compiler.reportWarning(tag.name,
MessageKind.DUPLICATED_LIBRARY_NAME,
{'libraryName': name});
});
compiler.withCurrentElement(existing, () {
compiler.reportWarning(existing.libraryTag.name,
MessageKind.DUPLICATED_LIBRARY_NAME,
{'libraryName': name});
});
}
}
}
/**
* Handle a part tag in the scope of [library]. The [resolvedUri] given is
* used as is, any URI resolution should be done beforehand.
*/
Future scanPart(Part part, Uri resolvedUri, LibraryElement library) {
if (!resolvedUri.isAbsolute) throw new ArgumentError(resolvedUri);
Uri readableUri = compiler.translateResolvedUri(library, resolvedUri, part);
if (readableUri == null) return new Future.value();
return compiler.withCurrentElement(library, () {
return compiler.readScript(part, readableUri).
then((Script sourceScript) {
if (sourceScript == null) return;
CompilationUnitElement unit =
new CompilationUnitElementX(sourceScript, library);
compiler.withCurrentElement(unit, () {
compiler.scanner.scan(unit);
if (unit.partTag == null) {
compiler.reportError(unit, MessageKind.MISSING_PART_OF_TAG);
}
});
});
});
}
/**
* Handle an import/export tag by loading the referenced library and
* registering its dependency in [handler] for the computation of the import/
* export scope.
*/
Future registerLibraryFromTag(LibraryDependencyHandler handler,
LibraryElement library,
LibraryDependency tag) {
Uri base = library.entryCompilationUnit.script.readableUri;
Uri resolvedUri = base.resolve(tag.uri.dartString.slowToString());
return createLibrary(handler, library, resolvedUri, tag.uri)
.then((LibraryElement loadedLibrary) {
if (loadedLibrary == null) return;
compiler.withCurrentElement(library, () {
handler.registerDependency(library, tag, loadedLibrary);
});
});
}
/**
* Create (or reuse) a library element for the library specified by the
* [resolvedUri].
*
* If a new library is created, the [handler] is notified.
*/
Future<LibraryElement> createLibrary(LibraryDependencyHandler handler,
LibraryElement importingLibrary,
Uri resolvedUri,
[Node node]) {
// TODO(johnniwinther): Create erroneous library elements for missing
// libraries.
Uri readableUri =
compiler.translateResolvedUri(importingLibrary, resolvedUri, node);
if (readableUri == null) return new Future.value();
LibraryElement library = libraryCanonicalUriMap[resolvedUri];
if (library != null) {
return new Future.value(library);
}
return compiler.withCurrentElement(importingLibrary, () {
return compiler.readScript(node, readableUri).then((Script script) {
if (script == null) return null;
LibraryElement element =
createLibrarySync(handler, script, resolvedUri);
return processLibraryTags(handler, element).then((_) {
compiler.withCurrentElement(element, () {
handler.registerLibraryExports(element);
});
return element;
});
});
});
}
LibraryElement createLibrarySync(
LibraryDependencyHandler handler,
Script script,
Uri resolvedUri) {
LibraryElement element = new LibraryElementX(script, resolvedUri);
return compiler.withCurrentElement(element, () {
if (handler != null) {
handler.registerNewLibrary(element);
libraryCanonicalUriMap[resolvedUri] = element;
}
native.maybeEnableNative(compiler, element);
compiler.scanner.scanLibrary(element);
return element;
});
}
}
/**
* The fields of this class models a state machine for checking script
* tags come in the correct order.
*/
class TagState {
static const int NO_TAG_SEEN = 0;
static const int LIBRARY = 1;
static const int IMPORT_OR_EXPORT = 2;
static const int SOURCE = 3;
static const int RESOURCE = 4;
/** Next state. */
static const List<int> NEXT =
const <int>[NO_TAG_SEEN,
IMPORT_OR_EXPORT, // Only one library tag is allowed.
IMPORT_OR_EXPORT,
SOURCE,
RESOURCE];
}
/**
* An [import] tag and the [importedLibrary] imported through [import].
*/
class ImportLink {
final Import import;
final LibraryElement importedLibrary;
ImportLink(this.import, this.importedLibrary);
/**
* Imports the library into the [importingLibrary].
*/
void importLibrary(Compiler compiler, LibraryElement importingLibrary) {
assert(invariant(importingLibrary,
importedLibrary.exportsHandled,
message: 'Exports not handled on $importedLibrary'));
var combinatorFilter = new CombinatorFilter.fromTag(import);
if (import != null && import.prefix != null) {
String prefix = import.prefix.source;
Element existingElement = importingLibrary.find(prefix);
PrefixElement prefixElement;
if (existingElement == null || !existingElement.isPrefix) {
prefixElement = new PrefixElementX(prefix,
importingLibrary.entryCompilationUnit, import.getBeginToken());
} else {
prefixElement = existingElement;
}
importingLibrary.addToScope(prefixElement, compiler);
importedLibrary.forEachExport((Element element) {
if (combinatorFilter.exclude(element)) return;
prefixElement.addImport(element, import, compiler);
});
if (import.isDeferred) {
prefixElement.addImport(
new DeferredLoaderGetterElementX(prefixElement),
import, compiler);
// TODO(sigurdm): When we remove support for the annotation based
// syntax the [PrefixElement] constructor should receive this
// information.
prefixElement.markAsDeferred(import);
}
} else {
importedLibrary.forEachExport((Element element) {
compiler.withCurrentElement(importingLibrary, () {
if (combinatorFilter.exclude(element)) return;
importingLibrary.addImport(element, import, compiler);
});
});
}
}
}
/**
* The combinator filter computed from an export tag and the library dependency
* node for the library that declared the export tag. This represents an edge in
* the library dependency graph.
*/
class ExportLink {
final Export export;
final CombinatorFilter combinatorFilter;
final LibraryDependencyNode exportNode;
ExportLink(Export export, LibraryDependencyNode this.exportNode)
: this.export = export,
this.combinatorFilter = new CombinatorFilter.fromTag(export);
/**
* Exports [element] to the dependent library unless [element] is filtered by
* the export combinators. Returns [:true:] if the set pending exports of the
* dependent library was modified.
*/
bool exportElement(Element element) {
if (combinatorFilter.exclude(element)) return false;
return exportNode.addElementToPendingExports(element, export);
}
}
/**
* A node in the library dependency graph.
*
* This class is used to collect the library dependencies expressed through
* import and export tags, and as the work-list entry in computations of library
* exports performed in [LibraryDependencyHandler.computeExports].
*/
class LibraryDependencyNode {
final LibraryElement library;
// TODO(ahe): Remove [hashCodeCounter] and [hashCode] when
// VM implementation of Object.hashCode is not slow.
final int hashCode = ++hashCodeCounter;
static int hashCodeCounter = 0;
/**
* A linked list of the import tags that import [library] mapped to the
* corresponding libraries. This is used to propagate exports into imports
* after the export scopes have been computed.
*/
Link<ImportLink> imports = const Link<ImportLink>();
/**
* A linked list of the export tags the dependent upon this node library.
* This is used to propagate exports during the computation of export scopes.
*/
Link<ExportLink> dependencies = const Link<ExportLink>();
/**
* The export scope for [library] which is gradually computed by the work-list
* computation in [LibraryDependencyHandler.computeExports].
*/
Map<String, Element> exportScope =
new Map<String, Element>();
/// Map from exported elements to the export directives that exported them.
Map<Element, Link<Export>> exporters = new Map<Element, Link<Export>>();
/**
* The set of exported elements that need to be propageted to dependent
* libraries as part of the work-list computation performed in
* [LibraryDependencyHandler.computeExports]. Each export element is mapped
* to a list of exports directives that export it.
*/
Map<Element, Link<Export>> pendingExportMap =
new Map<Element, Link<Export>>();
LibraryDependencyNode(LibraryElement this.library);
/**
* Registers that the library of this node imports [importLibrary] through the
* [import] tag.
*/
void registerImportDependency(Import import,
LibraryElement importedLibrary) {
imports = imports.prepend(new ImportLink(import, importedLibrary));
}
/**
* Registers that the library of this node is exported by
* [exportingLibraryNode] through the [export] tag.
*/
void registerExportDependency(Export export,
LibraryDependencyNode exportingLibraryNode) {
dependencies =
dependencies.prepend(new ExportLink(export, exportingLibraryNode));
}
/**
* Registers all non-private locally declared members of the library of this
* node to be exported. This forms the basis for the work-list computation of
* the export scopes performed in [LibraryDependencyHandler.computeExports].
*/
void registerInitialExports() {
for (Element element in library.getNonPrivateElementsInScope()) {
pendingExportMap[element] = const Link<Export>();
}
}
void registerHandledExports(LibraryElement exportedLibraryElement,
Export export,
CombinatorFilter filter) {
assert(invariant(library, exportedLibraryElement.exportsHandled));
for (Element exportedElement in exportedLibraryElement.exports) {
if (!filter.exclude(exportedElement)) {
Link<Export> exports =
pendingExportMap.putIfAbsent(exportedElement,
() => const Link<Export>());
pendingExportMap[exportedElement] = exports.prepend(export);
}
}
}
/**
* Registers the compute export scope with the node library.
*/
void registerExports() {
library.setExports(exportScope.values.toList());
}
/**
* Registers the imports of the node library.
*/
void registerImports(Compiler compiler) {
for (ImportLink link in imports) {
link.importLibrary(compiler, library);
}
}
/**
* Copies and clears pending export set for this node.
*/
Map<Element, Link<Export>> pullPendingExports() {
Map<Element, Link<Export>> pendingExports =
new Map<Element, Link<Export>>.from(pendingExportMap);
pendingExportMap.clear();
return pendingExports;
}
/**
* Adds [element] to the export scope for this node. If the [element] name
* is a duplicate, an error element is inserted into the export scope.
*/
Element addElementToExportScope(Compiler compiler, Element element,
Link<Export> exports) {
String name = element.name;
void reportDuplicateExport(Element duplicate,
Link<Export> duplicateExports,
{bool reportError: true}) {
assert(invariant(library, !duplicateExports.isEmpty,
message: "No export for $duplicate from ${duplicate.library} "
"in $library."));
compiler.withCurrentElement(library, () {
for (Export export in duplicateExports) {
if (reportError) {
compiler.reportError(export,
MessageKind.DUPLICATE_EXPORT, {'name': name});
reportError = false;
} else {
compiler.reportInfo(export,
MessageKind.DUPLICATE_EXPORT_CONT, {'name': name});
}
}
});
}
void reportDuplicateExportDecl(Element duplicate,
Link<Export> duplicateExports) {
assert(invariant(library, !duplicateExports.isEmpty,
message: "No export for $duplicate from ${duplicate.library} "
"in $library."));
compiler.reportInfo(duplicate, MessageKind.DUPLICATE_EXPORT_DECL,
{'name': name, 'uriString': duplicateExports.head.uri});
}
Element existingElement = exportScope[name];
if (existingElement != null && existingElement != element) {
if (existingElement.isErroneous) {
reportDuplicateExport(element, exports);
reportDuplicateExportDecl(element, exports);
element = existingElement;
} else if (existingElement.library == library) {
// Do nothing. [existingElement] hides [element].
} else if (element.library == library) {
// [element] hides [existingElement].
exportScope[name] = element;
exporters[element] = exports;
} else {
// Declared elements hide exported elements.
Link<Export> existingExports = exporters[existingElement];
reportDuplicateExport(existingElement, existingExports);
reportDuplicateExport(element, exports, reportError: false);
reportDuplicateExportDecl(existingElement, existingExports);
reportDuplicateExportDecl(element, exports);
element = exportScope[name] = new ErroneousElementX(
MessageKind.DUPLICATE_EXPORT, {'name': name}, name, library);
}
} else {
exportScope[name] = element;
exporters[element] = exports;
}
return element;
}
/**
* Propagates the exported [element] to all library nodes that depend upon
* this node. If the propagation updated any pending exports, [:true:] is
* returned.
*/
bool propagateElement(Element element) {
bool change = false;
for (ExportLink link in dependencies) {
if (link.exportElement(element)) {
change = true;
}
}
return change;
}
/**
* Adds [element] to the pending exports of this node and returns [:true:] if
* the pending export set was modified. The combinators of [export] are used
* to filter the element.
*/
bool addElementToPendingExports(Element element, Export export) {
bool changed = false;
if (!identical(exportScope[element.name], element)) {
Link<Export> exports = pendingExportMap.putIfAbsent(element, () {
changed = true;
return const Link<Export>();
});
pendingExportMap[element] = exports.prepend(export);
}
return changed;
}
}
/**
* Helper class used for computing the possibly cyclic import/export scopes of
* a set of libraries.
*
* This class is used by [ScannerTask.scanLibrary] to collect all newly loaded
* libraries and to compute their import/export scopes through a fixed-point
* algorithm.
*/
class LibraryDependencyHandler implements LibraryLoader {
final _LibraryLoaderTask task;
/**
* Newly loaded libraries and their corresponding node in the library
* dependency graph. Libraries that have already been fully loaded are not
* part of the dependency graph of this handler since their export scopes have
* already been computed.
*/
Map<LibraryElement, LibraryDependencyNode> nodeMap =
new Map<LibraryElement, LibraryDependencyNode>();
LibraryDependencyHandler(this.task);
Compiler get compiler => task.compiler;
/// The libraries loaded with this handler.
Iterable<LibraryElement> get loadedLibraries => nodeMap.keys;
/**
* Performs a fixed-point computation on the export scopes of all registered
* libraries and creates the import/export of the libraries based on the
* fixed-point.
*/
void computeExports() {
bool changed = true;
while (changed) {
changed = false;
Map<LibraryDependencyNode, Map<Element, Link<Export>>> tasks =
new Map<LibraryDependencyNode, Map<Element, Link<Export>>>();
// Locally defined elements take precedence over exported
// elements. So we must propagate local elements first. We
// ensure this by pulling the pending exports before
// propagating. This enforces that we handle exports
// breadth-first, with locally defined elements being level 0.
nodeMap.forEach((_, LibraryDependencyNode node) {
Map<Element, Link<Export>> pendingExports = node.pullPendingExports();
tasks[node] = pendingExports;
});
tasks.forEach((LibraryDependencyNode node,
Map<Element, Link<Export>> pendingExports) {
pendingExports.forEach((Element element, Link<Export> exports) {
element = node.addElementToExportScope(compiler, element, exports);
if (node.propagateElement(element)) {
changed = true;
}
});
});
}
// Setup export scopes. These have to be set before computing the import
// scopes to avoid accessing uncomputed export scopes during handling of
// imports.
nodeMap.forEach((LibraryElement library, LibraryDependencyNode node) {
node.registerExports();
});
// Setup import scopes.
nodeMap.forEach((LibraryElement library, LibraryDependencyNode node) {
node.registerImports(compiler);
});
}
/**
* Registers that [library] depends on [loadedLibrary] through [tag].
*/
void registerDependency(LibraryElement library,
LibraryDependency tag,
LibraryElement loadedLibrary) {
if (tag != null) {
library.recordResolvedTag(tag, loadedLibrary);
}
if (tag is Export) {
// [loadedLibrary] is exported by [library].
LibraryDependencyNode exportingNode = nodeMap[library];
if (loadedLibrary.exportsHandled) {
// Export scope already computed on [loadedLibrary].
var combinatorFilter = new CombinatorFilter.fromTag(tag);
exportingNode.registerHandledExports(
loadedLibrary, tag, combinatorFilter);
return;
}
LibraryDependencyNode exportedNode = nodeMap[loadedLibrary];
assert(invariant(loadedLibrary, exportedNode != null,
message: "$loadedLibrary has not been registered"));
assert(invariant(library, exportingNode != null,
message: "$library has not been registered"));
exportedNode.registerExportDependency(tag, exportingNode);
} else if (tag == null || tag is Import) {
// [loadedLibrary] is imported by [library].
LibraryDependencyNode importingNode = nodeMap[library];
assert(invariant(library, importingNode != null,
message: "$library has not been registered"));
importingNode.registerImportDependency(tag, loadedLibrary);
}
}
/**
* Registers [library] for the processing of its import/export scope.
*/
void registerNewLibrary(LibraryElement library) {
nodeMap[library] = new LibraryDependencyNode(library);
compiler.onLibraryCreated(library);
}
/**
* Registers all top-level entities of [library] as starting point for the
* fixed-point computation of the import/export scopes.
*/
void registerLibraryExports(LibraryElement library) {
nodeMap[library].registerInitialExports();
}
Future processLibraryTags(LibraryElement library) {
return task.processLibraryTags(this, library);
}
}