blob: 0f5ab1682dec97f3cb2ee1800b008a8971f9d654 [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.
library kernel.library_index;
import 'ast.dart';
/// Provides name-based access to library, class, and member AST nodes.
///
/// When constructed, a given set of libraries are indexed immediately, and
/// will not be up-to-date with changes made after it was created.
class LibraryIndex {
static const String getterPrefix = 'get:';
static const String setterPrefix = 'set:';
/// A special class name that can be used to access the top-level members
/// of a library.
static const String topLevel = '::';
final Map<String, _ClassTable> _libraries = <String, _ClassTable>{};
/// Indexes the libraries with the URIs given in [libraryUris].
LibraryIndex(Component component, Iterable<String> libraryUris) {
var libraryUriSet = libraryUris.toSet();
for (var library in component.libraries) {
var uri = '${library.importUri}';
if (libraryUriSet.contains(uri)) {
_libraries[uri] = new _ClassTable(library);
}
}
}
/// Indexes the libraries with the URIs given in [libraryUris].
LibraryIndex.byUri(Component component, Iterable<Uri> libraryUris)
: this(component, libraryUris.map((uri) => '$uri'));
/// Indexes `dart:` libraries.
LibraryIndex.coreLibraries(Component component) {
for (var library in component.libraries) {
if (library.importUri.scheme == 'dart') {
_libraries['${library.importUri}'] = new _ClassTable(library);
}
}
}
/// Indexes the entire component.
///
/// Consider using another constructor to only index the libraries that
/// are needed.
LibraryIndex.all(Component component) {
for (var library in component.libraries) {
_libraries['${library.importUri}'] = new _ClassTable(library);
}
}
_ClassTable _getLibraryIndex(String uri) {
_ClassTable libraryIndex = _libraries[uri];
if (libraryIndex == null) {
throw "The library '$uri' has not been indexed";
}
return libraryIndex;
}
/// Returns the library with the given URI.
///
/// Throws an error if it does not exist.
Library getLibrary(String uri) => _getLibraryIndex(uri).library;
/// Like [getLibrary] but returns `null` if not found.
Library tryGetLibrary(String uri) => _libraries[uri]?.library;
/// True if the library with the given URI exists and was indexed.
bool containsLibrary(String uri) => _libraries.containsKey(uri);
/// Returns the class with the given name in the given library.
///
/// An error is thrown if the class is not found.
Class getClass(String library, String className) {
return _getLibraryIndex(library).getClass(className);
}
/// Like [getClass] but returns `null` if not found.
Class tryGetClass(String library, String className) {
return _libraries[library]?.tryGetClass(className);
}
/// Returns the member with the given name, in the given class, in the
/// given library.
///
/// If a getter or setter is wanted, the `get:` or `set:` prefix must be
/// added in front of the member name.
///
/// The special class name `::` can be used to access top-level members.
///
/// If the member name is private it is considered private to [library].
/// It is not possible with this class to lookup members whose name is private
/// to a library other than the one containing it.
///
/// An error is thrown if the member is not found.
Member getMember(String library, String className, String memberName) {
return _getLibraryIndex(library).getMember(className, memberName);
}
/// Like [getMember] but returns `null` if not found.
Member tryGetMember(String library, String className, String memberName) {
return _libraries[library]?.tryGetMember(className, memberName);
}
/// Returns the top-level member with the given name, in the given library.
///
/// If a getter or setter is wanted, the `get:` or `set:` prefix must be
/// added in front of the member name.
///
/// If the member name is private it is considered private to [library].
/// It is not possible with this class to lookup members whose name is private
/// to a library other than the one containing it.
///
/// An error is thrown if the member is not found.
Member getTopLevelMember(String library, String memberName) {
return getMember(library, topLevel, memberName);
}
/// Like [getTopLevelMember] but returns `null` if not found.
Member tryGetTopLevelMember(
String library, String className, String memberName) {
return tryGetMember(library, topLevel, memberName);
}
}
class _ClassTable {
final Library library;
Map<String, _MemberTable> _classes;
_ClassTable(this.library);
Map<String, _MemberTable> get classes {
if (_classes == null) {
_classes = <String, _MemberTable>{};
_classes[LibraryIndex.topLevel] = new _MemberTable.topLevel(this);
for (var class_ in library.classes) {
_classes[class_.name] = new _MemberTable(this, class_);
}
}
return _classes;
}
String get containerName {
// For useful error messages, it can be helpful to indicate if the library
// is external. If a class or member was not found in an external library,
// it might be that it exists in the actual library, but its interface was
// not included in this build unit.
return library.isExternal
? "external library '${library.importUri}'"
: "library '${library.importUri}'";
}
_MemberTable _getClassIndex(String name) {
var indexer = classes[name];
if (indexer == null) {
throw "Class '$name' not found in $containerName";
}
return indexer;
}
Class getClass(String name) {
return _getClassIndex(name).class_;
}
Class tryGetClass(String name) {
return classes[name]?.class_;
}
Member getMember(String className, String memberName) {
return _getClassIndex(className).getMember(memberName);
}
Member tryGetMember(String className, String memberName) {
return classes[className]?.tryGetMember(memberName);
}
}
class _MemberTable {
final _ClassTable parent;
final Class class_; // Null for top-level.
Map<String, Member> _members;
Library get library => parent.library;
_MemberTable(this.parent, this.class_);
_MemberTable.topLevel(this.parent) : class_ = null;
Map<String, Member> get members {
if (_members == null) {
_members = <String, Member>{};
if (class_ != null) {
class_.procedures.forEach(addMember);
class_.fields.forEach(addMember);
class_.constructors.forEach(addMember);
} else {
library.procedures.forEach(addMember);
library.fields.forEach(addMember);
}
}
return _members;
}
String getDisambiguatedName(Member member) {
if (member is Procedure) {
if (member.isGetter) return LibraryIndex.getterPrefix + member.name.name;
if (member.isSetter) return LibraryIndex.setterPrefix + member.name.name;
}
return member.name.name;
}
void addMember(Member member) {
if (member.name.isPrivate && member.name.library != library) {
// Members whose name is private to other libraries cannot currently
// be found with the LibraryIndex class.
return;
}
_members[getDisambiguatedName(member)] = member;
}
String get containerName {
if (class_ == null) {
return "top-level of ${parent.containerName}";
} else {
return "class '${class_.name}' in ${parent.containerName}";
}
}
Member getMember(String name) {
var member = members[name];
if (member == null) {
String message = "A member with disambiguated name '$name' was not found "
"in $containerName";
var getter = LibraryIndex.getterPrefix + name;
var setter = LibraryIndex.setterPrefix + name;
if (members[getter] != null || members[setter] != null) {
throw "$message. Did you mean '$getter' or '$setter'?";
}
throw message;
}
return member;
}
Member tryGetMember(String name) => members[name];
}