blob: ad4834f1f738e8bb0270d00393349ea484afc398 [file] [log] [blame]
// 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.
import 'package:analyzer/src/summary/format.dart';
import 'package:analyzer/src/summary/name_filter.dart';
/**
* Create a [PrelinkedLibraryBuilder] corresponding to the given
* [definingUnit], which should be the defining compilation unit for a library.
* Compilation units referenced by the defining compilation unit via `part`
* declarations will be retrieved using [getPart]. Public namespaces for
* libraries referenced by the defining compilation unit via `import`
* declarations (and files reachable from them via `part` and `export`
* declarations) will be retrieved using [getImport].
*/
PrelinkedLibraryBuilder prelink(UnlinkedUnit definingUnit,
GetPartCallback getPart, GetImportCallback getImport) {
return new _Prelinker(definingUnit, getPart, getImport).prelink();
}
/**
* Type of the callback used by the prelinker to obtain public namespace
* information about libraries imported by the library to be prelinked (and
* the transitive closure of parts and exports reachable from those libraries).
* [relativeUri] should be interpreted relative to the defining compilation
* unit of the library being prelinked.
*
* If no file exists at the given uri, `null` should be returned.
*/
typedef UnlinkedPublicNamespace GetImportCallback(String relativeUri);
/**
* Type of the callback used by the prelinker to obtain unlinked summaries of
* part files of the library to be prelinked. [relaviteUri] should be
* interpreted relative to the defining compilation unit of the library being
* prelinked.
*
* If no file exists at the given uri, `null` should be returned.
*/
typedef UnlinkedUnit GetPartCallback(String relativeUri);
/**
* A [_Meaning] stores all the information necessary to find the declaration
* referred to by a name in a namespace.
*/
class _Meaning {
/**
* Which unit in the dependent library contains the declared entity.
*/
final int unit;
/**
* The kind of entity being referred to.
*/
final PrelinkedReferenceKind kind;
/**
* Which of the dependencies of the library being prelinked contains the
* declared entity.
*/
final int dependency;
/**
* If the entity being referred to is generic, the number of type parameters
* it accepts. Otherwise zero.
*/
final int numTypeParameters;
_Meaning(this.unit, this.kind, this.dependency, this.numTypeParameters);
/**
* Encode this [_Meaning] as a [PrelinkedReference].
*/
PrelinkedReferenceBuilder encode() {
return encodePrelinkedReference(
unit: unit,
kind: kind,
dependency: dependency,
numTypeParameters: numTypeParameters);
}
}
/**
* A [_Meaning] representing a prefix introduced by an import directive.
*/
class _PrefixMeaning extends _Meaning {
final Map<String, _Meaning> namespace = <String, _Meaning>{};
_PrefixMeaning() : super(0, PrelinkedReferenceKind.prefix, 0, 0);
}
/**
* Helper class containing temporary data structures needed to prelink a single
* library.
*
* Note: throughout this class, a `null` value for a relative URI represents
* the defining compilation unit of the library being prelinked.
*/
class _Prelinker {
final UnlinkedUnit definingUnit;
final GetPartCallback getPart;
final GetImportCallback getImport;
/**
* Cache of values returned by [getImport].
*/
final Map<String, UnlinkedPublicNamespace> importCache =
<String, UnlinkedPublicNamespace>{};
/**
* Cache of values returned by [getPart].
*/
final Map<String, UnlinkedUnit> partCache = <String, UnlinkedUnit>{};
/**
* Names defined inside the library being prelinked.
*/
final Map<String, _Meaning> privateNamespace = <String, _Meaning>{
'': new _Meaning(0, PrelinkedReferenceKind.classOrEnum, 0, 0)
};
/**
* List of dependencies of the library being prelinked. This will be output
* to [PrelinkedLibrary.dependencies].
*/
final List<PrelinkedDependencyBuilder> dependencies =
<PrelinkedDependencyBuilder>[encodePrelinkedDependency()];
/**
* Map from the relative URI of a dependent library to the index of the
* corresponding entry in [dependencies].
*/
final Map<String, int> uriToDependency = <String, int>{null: 0};
/**
* List of public namespaces corresponding to each entry in [dependencies].
*/
final List<Map<String, _Meaning>> dependencyToPublicNamespace =
<Map<String, _Meaning>>[null];
_Prelinker(this.definingUnit, this.getPart, this.getImport) {
partCache[null] = definingUnit;
importCache[null] = definingUnit.publicNamespace;
}
/**
* Compute the public namespace for the library whose URI is reachable from
* [definingUnit] via [relativeUri], by aggregating together public namespace
* information from all of its parts.
*/
Map<String, _Meaning> aggregatePublicNamespace(String relativeUri) {
if (uriToDependency.containsKey(relativeUri)) {
return dependencyToPublicNamespace[uriToDependency[relativeUri]];
}
assert(dependencies.length == dependencyToPublicNamespace.length);
int dependency = dependencies.length;
uriToDependency[relativeUri] = dependency;
List<String> unitUris = getUnitUris(relativeUri);
PrelinkedDependencyBuilder prelinkedDependency =
encodePrelinkedDependency(uri: relativeUri, parts: unitUris.sublist(1));
dependencies.add(prelinkedDependency);
Map<String, _Meaning> aggregated = <String, _Meaning>{};
for (int unitNum = 0; unitNum < unitUris.length; unitNum++) {
String unitUri = unitUris[unitNum];
UnlinkedPublicNamespace importedNamespace = getImportCached(unitUri);
if (importedNamespace == null) {
continue;
}
for (UnlinkedPublicName name in importedNamespace.names) {
aggregated.putIfAbsent(
name.name,
() => new _Meaning(
unitNum, name.kind, dependency, name.numTypeParameters));
}
}
dependencyToPublicNamespace.add(aggregated);
return aggregated;
}
/**
* Compute the export namespace for the library whose URI is reachable from
* [definingUnit] via [relativeUri], by aggregating together public namespace
* information from the library and the transitive closure of its exports.
*/
Map<String, _Meaning> computeExportNamespace(String relativeUri) {
Map<String, _Meaning> exportNamespace =
aggregatePublicNamespace(relativeUri);
void chaseExports(
NameFilter filter, String relativeUri, Set<String> seenUris) {
if (seenUris.add(relativeUri)) {
UnlinkedPublicNamespace exportedNamespace =
getImportCached(relativeUri);
if (exportedNamespace != null) {
for (UnlinkedExportPublic export in exportedNamespace.exports) {
String exportUri = resolveUri(relativeUri, export.uri);
aggregatePublicNamespace(exportUri)
.forEach((String name, _Meaning meaning) {
if (filter.accepts(name) && !exportNamespace.containsKey(name)) {
exportNamespace[name] = meaning;
}
});
chaseExports(
filter.merge(
new NameFilter.forUnlinkedCombinators(export.combinators)),
exportUri,
seenUris);
}
}
seenUris.remove(relativeUri);
}
}
chaseExports(NameFilter.identity, relativeUri, new Set<String>());
return exportNamespace;
}
/**
* Extract all the names defined in [unit] (which is the [unitNum]th unit in
* the library being prelinked) and store them in [privateNamespace].
* Excludes names introduced by `import` statements.
*/
void extractPrivateNames(UnlinkedUnit unit, int unitNum) {
for (UnlinkedClass cls in unit.classes) {
privateNamespace.putIfAbsent(
cls.name,
() => new _Meaning(unitNum, PrelinkedReferenceKind.classOrEnum, 0,
cls.typeParameters.length));
}
for (UnlinkedEnum enm in unit.enums) {
privateNamespace.putIfAbsent(
enm.name,
() =>
new _Meaning(unitNum, PrelinkedReferenceKind.classOrEnum, 0, 0));
}
for (UnlinkedExecutable executable in unit.executables) {
privateNamespace.putIfAbsent(
executable.name,
() => new _Meaning(unitNum, PrelinkedReferenceKind.other, 0,
executable.typeParameters.length));
}
for (UnlinkedTypedef typedef in unit.typedefs) {
privateNamespace.putIfAbsent(
typedef.name,
() => new _Meaning(unitNum, PrelinkedReferenceKind.typedef, 0,
typedef.typeParameters.length));
}
for (UnlinkedVariable variable in unit.variables) {
privateNamespace.putIfAbsent(variable.name,
() => new _Meaning(unitNum, PrelinkedReferenceKind.other, 0, 0));
}
}
/**
* Filter the export namespace for the library whose URI is reachable from
* [definingUnit] via [relativeUri], retaining only those names accepted by
* [combinators], and store the resulting names in [result]. Names that
* already exist in [result] are not overwritten.
*/
void filterExportNamespace(String relativeUri,
List<UnlinkedCombinator> combinators, Map<String, _Meaning> result) {
Map<String, _Meaning> exportNamespace = computeExportNamespace(relativeUri);
NameFilter filter = new NameFilter.forUnlinkedCombinators(combinators);
exportNamespace.forEach((String name, _Meaning meaning) {
if (filter.accepts(name) && !result.containsKey(name)) {
result[name] = meaning;
}
});
}
/**
* Wrapper around [getImport] that caches the return value in [importCache].
*/
UnlinkedPublicNamespace getImportCached(String relativeUri) {
return importCache.putIfAbsent(relativeUri, () => getImport(relativeUri));
}
/**
* Wrapper around [getPart] that caches the return value in [partCache] and
* updates [importCache] appropriately.
*/
UnlinkedUnit getPartCached(String relativeUri) {
return partCache.putIfAbsent(relativeUri, () {
UnlinkedUnit unit = getPart(relativeUri);
importCache[relativeUri] = unit?.publicNamespace;
return unit;
});
}
/**
* Compute the set of relative URIs of all the compilation units in the
* library whose URI is reachable from [definingUnit] via [relativeUri].
*/
List<String> getUnitUris(String relativeUri) {
List<String> result = <String>[relativeUri];
UnlinkedPublicNamespace publicNamespace = getImportCached(relativeUri);
if (publicNamespace != null) {
result.addAll(publicNamespace.parts
.map((String uri) => resolveUri(relativeUri, uri)));
}
return result;
}
/**
* Process a single `import` declaration in the library being prelinked. The
* return value is the index of the imported library in [dependencies].
*/
int handleImport(UnlinkedImport import) {
String uri = import.isImplicit ? 'dart:core' : import.uri;
Map<String, _Meaning> targetNamespace = null;
if (import.prefixReference != 0) {
// The name introduced by an import declaration can't have a prefix of
// its own.
assert(
definingUnit.references[import.prefixReference].prefixReference == 0);
String prefix = definingUnit.references[import.prefixReference].name;
_Meaning prefixMeaning = privateNamespace[prefix];
if (prefixMeaning is _PrefixMeaning) {
targetNamespace = prefixMeaning.namespace;
}
} else {
targetNamespace = privateNamespace;
}
filterExportNamespace(uri, import.combinators, targetNamespace);
return uriToDependency[uri];
}
/**
* Produce a [PrelinkedUnit] for the given [unit], by resolving every one of
* its references.
*/
PrelinkedUnitBuilder linkUnit(UnlinkedUnit unit) {
if (unit == null) {
return encodePrelinkedUnit();
}
Map<int, Map<String, _Meaning>> prefixNamespaces =
<int, Map<String, _Meaning>>{};
List<PrelinkedReferenceBuilder> references = <PrelinkedReferenceBuilder>[];
for (int i = 0; i < unit.references.length; i++) {
UnlinkedReference reference = unit.references[i];
Map<String, _Meaning> namespace;
if (reference.prefixReference != 0) {
// Prefix references must always point backward.
assert(reference.prefixReference < i);
namespace = prefixNamespaces[reference.prefixReference];
// Prefix references must always point to proper prefixes.
assert(namespace != null);
} else {
namespace = privateNamespace;
}
_Meaning meaning = namespace[reference.name];
if (meaning != null) {
if (meaning is _PrefixMeaning) {
prefixNamespaces[i] = meaning.namespace;
}
references.add(meaning.encode());
} else {
references.add(
encodePrelinkedReference(kind: PrelinkedReferenceKind.unresolved));
}
}
return encodePrelinkedUnit(references: references);
}
/**
* Form the [PrelinkedLibrary] for the [definingUnit] that was passed to the
* constructor.
*/
PrelinkedLibraryBuilder prelink() {
// Gather up the unlinked summaries for all the compilation units in the
// library.
List<UnlinkedUnit> units = getUnitUris(null).map(getPartCached).toList();
// Create the private namespace for the library by gathering all the names
// defined in its compilation units.
for (int unitNum = 0; unitNum < units.length; unitNum++) {
UnlinkedUnit unit = units[unitNum];
if (unit != null) {
extractPrivateNames(unit, unitNum);
}
}
// Fill in prefixes defined in import declarations.
for (UnlinkedImport import in units[0].imports) {
if (import.prefixReference != 0) {
privateNamespace.putIfAbsent(
units[0].references[import.prefixReference].name,
() => new _PrefixMeaning());
}
}
// Fill in imported names.
List<int> importDependencies =
definingUnit.imports.map(handleImport).toList();
// Link each compilation unit.
List<PrelinkedUnitBuilder> linkedUnits = units.map(linkUnit).toList();
return encodePrelinkedLibrary(
units: linkedUnits,
dependencies: dependencies,
importDependencies: importDependencies);
}
/**
* Resolve [relativeUri] relative to [sourceUri]. Works correctly if
* [sourceUri] is also relative.
*/
String resolveUri(String sourceUri, String relativeUri) {
if (sourceUri == null) {
return relativeUri;
} else {
return Uri.parse(sourceUri).resolve(relativeUri).toString();
}
}
}