forked from qt-creator/qt-creator
QmlJS: Speed up Link significantly, provide more info on imports.
Link now caches imports. That means importing the same library (say, Qt) from more than one file no longer creates an importing namespace for each one. Instead, a single one is created for the instance of Link. To make this work, the type environment in ScopeChain has been given its own type: Interpreter::TypeEnvironment. That has the added benefit of being able to carry meta-information about imports. You can use TypeEnvironment::importInfo(qmlComponentName) to get information about the import node that caused the import of the component.
This commit is contained in:
@@ -32,6 +32,7 @@
|
||||
#include "parser/qmljsast_p.h"
|
||||
#include "qmljsdocument.h"
|
||||
#include "qmljsbind.h"
|
||||
#include "qmljscheck.h"
|
||||
#include "qmljsscopebuilder.h"
|
||||
#include "qmljsmodelmanagerinterface.h"
|
||||
|
||||
@@ -43,6 +44,52 @@ using namespace QmlJS;
|
||||
using namespace QmlJS::Interpreter;
|
||||
using namespace QmlJS::AST;
|
||||
|
||||
namespace {
|
||||
class ImportCacheKey
|
||||
{
|
||||
public:
|
||||
explicit ImportCacheKey(const Interpreter::ImportInfo &info)
|
||||
: type(info.type())
|
||||
, name(info.name())
|
||||
, majorVersion(info.version().majorVersion())
|
||||
, minorVersion(info.version().minorVersion())
|
||||
{}
|
||||
|
||||
int type;
|
||||
QString name;
|
||||
int majorVersion;
|
||||
int minorVersion;
|
||||
};
|
||||
|
||||
uint qHash(const ImportCacheKey &info)
|
||||
{
|
||||
return ::qHash(info.type) ^ ::qHash(info.name) ^
|
||||
::qHash(info.majorVersion) ^ ::qHash(info.minorVersion);
|
||||
}
|
||||
|
||||
bool operator==(const ImportCacheKey &i1, const ImportCacheKey &i2)
|
||||
{
|
||||
return i1.type == i2.type
|
||||
&& i1.name == i2.name
|
||||
&& i1.majorVersion == i2.majorVersion
|
||||
&& i1.minorVersion == i2.minorVersion;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class QmlJS::LinkPrivate
|
||||
{
|
||||
public:
|
||||
Document::Ptr doc;
|
||||
Snapshot snapshot;
|
||||
Interpreter::Context *context;
|
||||
QStringList importPaths;
|
||||
|
||||
QHash<ImportCacheKey, Interpreter::ObjectValue *> importCache;
|
||||
|
||||
QList<DiagnosticMessage> diagnosticMessages;
|
||||
};
|
||||
|
||||
/*!
|
||||
\class QmlJS::Link
|
||||
\brief Initializes the Context for a Document.
|
||||
@@ -58,11 +105,14 @@ using namespace QmlJS::AST;
|
||||
|
||||
Link::Link(Context *context, const Document::Ptr &doc, const Snapshot &snapshot,
|
||||
const QStringList &importPaths)
|
||||
: _doc(doc)
|
||||
, _snapshot(snapshot)
|
||||
, _context(context)
|
||||
, _importPaths(importPaths)
|
||||
: d_ptr(new LinkPrivate)
|
||||
{
|
||||
Q_D(Link);
|
||||
d->context = context;
|
||||
d->doc = doc;
|
||||
d->snapshot = snapshot;
|
||||
d->importPaths = importPaths;
|
||||
|
||||
linkImports();
|
||||
initializeScopeChain();
|
||||
}
|
||||
@@ -73,42 +123,46 @@ Link::~Link()
|
||||
|
||||
Interpreter::Engine *Link::engine()
|
||||
{
|
||||
return _context->engine();
|
||||
Q_D(Link);
|
||||
return d->context->engine();
|
||||
}
|
||||
|
||||
QList<DiagnosticMessage> Link::diagnosticMessages() const
|
||||
{
|
||||
return _diagnosticMessages;
|
||||
Q_D(const Link);
|
||||
return d->diagnosticMessages;
|
||||
}
|
||||
|
||||
void Link::initializeScopeChain()
|
||||
{
|
||||
ScopeChain &scopeChain = _context->scopeChain();
|
||||
Q_D(Link);
|
||||
|
||||
ScopeChain &scopeChain = d->context->scopeChain();
|
||||
|
||||
// ### TODO: This object ought to contain the global namespace additions by QML.
|
||||
scopeChain.globalScope = engine()->globalObject();
|
||||
|
||||
if (! _doc) {
|
||||
if (! d->doc) {
|
||||
scopeChain.update();
|
||||
return;
|
||||
}
|
||||
|
||||
Bind *bind = _doc->bind();
|
||||
Bind *bind = d->doc->bind();
|
||||
QHash<Document *, ScopeChain::QmlComponentChain *> componentScopes;
|
||||
|
||||
ScopeChain::QmlComponentChain *chain = new ScopeChain::QmlComponentChain;
|
||||
scopeChain.qmlComponentScope = QSharedPointer<const ScopeChain::QmlComponentChain>(chain);
|
||||
if (_doc->qmlProgram()) {
|
||||
componentScopes.insert(_doc.data(), chain);
|
||||
makeComponentChain(_doc, chain, &componentScopes);
|
||||
if (d->doc->qmlProgram()) {
|
||||
componentScopes.insert(d->doc.data(), chain);
|
||||
makeComponentChain(d->doc, chain, &componentScopes);
|
||||
|
||||
if (const ObjectValue *typeEnvironment = _context->typeEnvironment(_doc.data()))
|
||||
if (const TypeEnvironment *typeEnvironment = d->context->typeEnvironment(d->doc.data()))
|
||||
scopeChain.qmlTypes = typeEnvironment;
|
||||
} else {
|
||||
// add scope chains for all components that import this file
|
||||
foreach (Document::Ptr otherDoc, _snapshot) {
|
||||
foreach (const Bind::ImportInfo &fileImport, otherDoc->bind()->fileImports()) {
|
||||
if (_doc->fileName() == fileImport.name) {
|
||||
foreach (Document::Ptr otherDoc, d->snapshot) {
|
||||
foreach (const ImportInfo &import, otherDoc->bind()->imports()) {
|
||||
if (import.type() == ImportInfo::FileImport && d->doc->fileName() == import.name()) {
|
||||
ScopeChain::QmlComponentChain *component = new ScopeChain::QmlComponentChain;
|
||||
componentScopes.insert(otherDoc.data(), component);
|
||||
chain->instantiatingComponents += component;
|
||||
@@ -131,16 +185,18 @@ void Link::makeComponentChain(
|
||||
ScopeChain::QmlComponentChain *target,
|
||||
QHash<Document *, ScopeChain::QmlComponentChain *> *components)
|
||||
{
|
||||
Q_D(Link);
|
||||
|
||||
if (!doc->qmlProgram())
|
||||
return;
|
||||
|
||||
Bind *bind = doc->bind();
|
||||
|
||||
// add scopes for all components instantiating this one
|
||||
foreach (Document::Ptr otherDoc, _snapshot) {
|
||||
foreach (Document::Ptr otherDoc, d->snapshot) {
|
||||
if (otherDoc == doc)
|
||||
continue;
|
||||
if (otherDoc->bind()->usesQmlPrototype(bind->rootObjectValue(), _context)) {
|
||||
if (otherDoc->bind()->usesQmlPrototype(bind->rootObjectValue(), d->context)) {
|
||||
if (components->contains(otherDoc.data())) {
|
||||
// target->instantiatingComponents += components->value(otherDoc.data());
|
||||
} else {
|
||||
@@ -159,41 +215,62 @@ void Link::makeComponentChain(
|
||||
|
||||
void Link::linkImports()
|
||||
{
|
||||
foreach (Document::Ptr doc, _snapshot) {
|
||||
ObjectValue *typeEnv = engine()->newObject(/*prototype =*/0); // ### FIXME
|
||||
Q_D(Link);
|
||||
|
||||
// Populate the _typeEnvironment with imports.
|
||||
// do it on d->doc first, to make sure import errors are shown
|
||||
TypeEnvironment *typeEnv = new TypeEnvironment(engine());
|
||||
populateImportedTypes(typeEnv, d->doc);
|
||||
d->context->setTypeEnvironment(d->doc.data(), typeEnv);
|
||||
|
||||
foreach (Document::Ptr doc, d->snapshot) {
|
||||
if (doc == d->doc)
|
||||
continue;
|
||||
|
||||
TypeEnvironment *typeEnv = new TypeEnvironment(engine());
|
||||
populateImportedTypes(typeEnv, doc);
|
||||
|
||||
_context->setTypeEnvironment(doc.data(), typeEnv);
|
||||
d->context->setTypeEnvironment(doc.data(), typeEnv);
|
||||
}
|
||||
}
|
||||
|
||||
void Link::populateImportedTypes(Interpreter::ObjectValue *typeEnv, Document::Ptr doc)
|
||||
void Link::populateImportedTypes(TypeEnvironment *typeEnv, Document::Ptr doc)
|
||||
{
|
||||
Q_D(Link);
|
||||
|
||||
if (! (doc->qmlProgram() && doc->qmlProgram()->imports))
|
||||
return;
|
||||
|
||||
// implicit imports:
|
||||
// qml files in the same directory are available without explicit imports
|
||||
foreach (Document::Ptr otherDoc, _snapshot.documentsInDirectory(doc->path())) {
|
||||
if (otherDoc == doc || !otherDoc->bind()->rootObjectValue())
|
||||
continue;
|
||||
|
||||
typeEnv->setProperty(otherDoc->componentName(),
|
||||
otherDoc->bind()->rootObjectValue());
|
||||
ImportInfo implcitImportInfo(ImportInfo::ImplicitDirectoryImport, doc->path());
|
||||
ObjectValue *directoryImport = d->importCache.value(ImportCacheKey(implcitImportInfo));
|
||||
if (!directoryImport) {
|
||||
directoryImport = importFile(doc, implcitImportInfo);
|
||||
if (directoryImport)
|
||||
d->importCache.insert(ImportCacheKey(implcitImportInfo), directoryImport);
|
||||
}
|
||||
if (directoryImport)
|
||||
typeEnv->addImport(directoryImport, implcitImportInfo);
|
||||
|
||||
// explicit imports, whether directories or files
|
||||
for (UiImportList *it = doc->qmlProgram()->imports; it; it = it->next) {
|
||||
if (! it->import)
|
||||
continue;
|
||||
|
||||
if (it->import->fileName) {
|
||||
importFile(typeEnv, doc, it->import);
|
||||
} else if (it->import->importUri) {
|
||||
importNonFile(typeEnv, doc, it->import);
|
||||
// explicit imports, whether directories, files or libraries
|
||||
foreach (const ImportInfo &info, doc->bind()->imports()) {
|
||||
ObjectValue *import = d->importCache.value(ImportCacheKey(info));
|
||||
if (!import) {
|
||||
switch (info.type()) {
|
||||
case ImportInfo::FileImport:
|
||||
case ImportInfo::DirectoryImport:
|
||||
import = importFile(doc, info);
|
||||
break;
|
||||
case ImportInfo::LibraryImport:
|
||||
import = importNonFile(doc, info);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (import)
|
||||
d->importCache.insert(ImportCacheKey(info), import);
|
||||
}
|
||||
if (import)
|
||||
typeEnv->addImport(import, info);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -205,61 +282,29 @@ void Link::populateImportedTypes(Interpreter::ObjectValue *typeEnv, Document::Pt
|
||||
|
||||
import "http://www.ovi.com/" as Ovi
|
||||
*/
|
||||
void Link::importFile(Interpreter::ObjectValue *typeEnv, Document::Ptr doc,
|
||||
AST::UiImport *import)
|
||||
ObjectValue *Link::importFile(Document::Ptr, const ImportInfo &importInfo)
|
||||
{
|
||||
Q_UNUSED(doc)
|
||||
Q_D(Link);
|
||||
|
||||
if (!import->fileName)
|
||||
return;
|
||||
|
||||
QString path = doc->path();
|
||||
path += QLatin1Char('/');
|
||||
path += import->fileName->asString();
|
||||
path = QDir::cleanPath(path);
|
||||
|
||||
ObjectValue *importNamespace = typeEnv;
|
||||
|
||||
// directory import
|
||||
QList<Document::Ptr> documentsInDirectory = _snapshot.documentsInDirectory(path);
|
||||
if (! documentsInDirectory.isEmpty()) {
|
||||
if (import->importId) {
|
||||
importNamespace = engine()->newObject(/*prototype =*/0);
|
||||
typeEnv->setProperty(import->importId->asString(), importNamespace);
|
||||
}
|
||||
ObjectValue *import = 0;
|
||||
const QString &path = importInfo.name();
|
||||
|
||||
if (importInfo.type() == ImportInfo::DirectoryImport
|
||||
|| importInfo.type() == ImportInfo::ImplicitDirectoryImport) {
|
||||
import = new ObjectValue(engine());
|
||||
const QList<Document::Ptr> &documentsInDirectory = d->snapshot.documentsInDirectory(path);
|
||||
foreach (Document::Ptr importedDoc, documentsInDirectory) {
|
||||
if (importedDoc->bind()->rootObjectValue()) {
|
||||
const QString targetName = importedDoc->componentName();
|
||||
importNamespace->setProperty(targetName, importedDoc->bind()->rootObjectValue());
|
||||
import->setProperty(targetName, importedDoc->bind()->rootObjectValue());
|
||||
}
|
||||
}
|
||||
} else if (importInfo.type() == ImportInfo::FileImport) {
|
||||
Document::Ptr importedDoc = d->snapshot.document(path);
|
||||
import = importedDoc->bind()->rootObjectValue();
|
||||
}
|
||||
// file import
|
||||
else if (Document::Ptr importedDoc = _snapshot.document(path)) {
|
||||
if (importedDoc->bind()->rootObjectValue()) {
|
||||
QString targetName;
|
||||
if (import->importId) {
|
||||
targetName = import->importId->asString();
|
||||
} else {
|
||||
targetName = importedDoc->componentName();
|
||||
}
|
||||
|
||||
importNamespace->setProperty(targetName, importedDoc->bind()->rootObjectValue());
|
||||
}
|
||||
} else {
|
||||
error(doc, import->fileNameToken,
|
||||
tr("could not find file or directory"));
|
||||
}
|
||||
}
|
||||
|
||||
static SourceLocation locationFromRange(const SourceLocation &start,
|
||||
const SourceLocation &end)
|
||||
{
|
||||
return SourceLocation(start.offset,
|
||||
end.end() - start.begin(),
|
||||
start.startLine,
|
||||
start.startColumn);
|
||||
return import;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -267,52 +312,24 @@ static SourceLocation locationFromRange(const SourceLocation &start,
|
||||
import Qt 4.6 as Xxx
|
||||
(import com.nokia.qt is the same as the ones above)
|
||||
*/
|
||||
void Link::importNonFile(Interpreter::ObjectValue *typeEnv, Document::Ptr doc, AST::UiImport *import)
|
||||
ObjectValue *Link::importNonFile(Document::Ptr doc, const ImportInfo &importInfo)
|
||||
{
|
||||
if (! import->importUri)
|
||||
return;
|
||||
Q_D(Link);
|
||||
|
||||
ObjectValue *namespaceObject = 0;
|
||||
|
||||
if (import->importId) { // with namespace we insert an object in the type env. to hold the imported types
|
||||
namespaceObject = engine()->newObject(/*prototype */ 0);
|
||||
typeEnv->setProperty(import->importId->asString(), namespaceObject);
|
||||
|
||||
} else { // without namespace we insert all types directly into the type env.
|
||||
namespaceObject = typeEnv;
|
||||
}
|
||||
|
||||
const QString packageName = Bind::toString(import->importUri, '.');
|
||||
ComponentVersion version;
|
||||
|
||||
if (import->versionToken.isValid()) {
|
||||
const QString versionString = doc->source().mid(import->versionToken.offset, import->versionToken.length);
|
||||
const int dotIdx = versionString.indexOf(QLatin1Char('.'));
|
||||
if (dotIdx == -1) {
|
||||
error(doc, import->versionToken,
|
||||
tr("expected two numbers separated by a dot"));
|
||||
return;
|
||||
} else {
|
||||
const int majorVersion = versionString.left(dotIdx).toInt();
|
||||
const int minorVersion = versionString.mid(dotIdx + 1).toInt();
|
||||
version = ComponentVersion(majorVersion, minorVersion);
|
||||
}
|
||||
} else {
|
||||
error(doc, locationFromRange(import->firstSourceLocation(), import->lastSourceLocation()),
|
||||
tr("package import requires a version number"));
|
||||
return;
|
||||
}
|
||||
ObjectValue *import = new ObjectValue(engine());
|
||||
const QString packageName = Bind::toString(importInfo.ast()->importUri, '.');
|
||||
const ComponentVersion version = importInfo.version();
|
||||
|
||||
bool importFound = false;
|
||||
|
||||
// check the filesystem
|
||||
const QString packagePath = Bind::toString(import->importUri, QDir::separator());
|
||||
foreach (const QString &importPath, _importPaths) {
|
||||
const QString &packagePath = importInfo.name();
|
||||
foreach (const QString &importPath, d->importPaths) {
|
||||
QString libraryPath = importPath;
|
||||
libraryPath += QDir::separator();
|
||||
libraryPath += packagePath;
|
||||
|
||||
const LibraryInfo libraryInfo = _snapshot.libraryInfo(libraryPath);
|
||||
const LibraryInfo libraryInfo = d->snapshot.libraryInfo(libraryPath);
|
||||
if (!libraryInfo.isValid())
|
||||
continue;
|
||||
|
||||
@@ -322,7 +339,7 @@ void Link::importNonFile(Interpreter::ObjectValue *typeEnv, Document::Ptr doc, A
|
||||
if (libraryInfo.metaObjects().isEmpty()) {
|
||||
ModelManagerInterface *modelManager = ModelManagerInterface::instance();
|
||||
if (modelManager)
|
||||
modelManager->loadPluginTypes(libraryPath, importPath, Bind::toString(import->importUri, QLatin1Char('.')));
|
||||
modelManager->loadPluginTypes(libraryPath, importPath, packageName);
|
||||
} else {
|
||||
engine()->cppQmlTypes().load(engine(), libraryInfo.metaObjects());
|
||||
}
|
||||
@@ -333,14 +350,16 @@ void Link::importNonFile(Interpreter::ObjectValue *typeEnv, Document::Ptr doc, A
|
||||
if (importedTypes.contains(component.typeName))
|
||||
continue;
|
||||
|
||||
ComponentVersion componentVersion(component.majorVersion, component.minorVersion);
|
||||
ComponentVersion componentVersion(component.majorVersion,
|
||||
component.minorVersion);
|
||||
if (version < componentVersion)
|
||||
continue;
|
||||
|
||||
importedTypes.insert(component.typeName);
|
||||
if (Document::Ptr importedDoc = _snapshot.document(libraryPath + QDir::separator() + component.fileName)) {
|
||||
if (importedDoc->bind()->rootObjectValue())
|
||||
namespaceObject->setProperty(component.typeName, importedDoc->bind()->rootObjectValue());
|
||||
if (Document::Ptr importedDoc = d->snapshot.document(
|
||||
libraryPath + QDir::separator() + component.fileName)) {
|
||||
if (ObjectValue *v = importedDoc->bind()->rootObjectValue())
|
||||
import->setProperty(component.typeName, v);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -350,15 +369,19 @@ void Link::importNonFile(Interpreter::ObjectValue *typeEnv, Document::Ptr doc, A
|
||||
// if there are cpp-based types for this package, use them too
|
||||
if (engine()->cppQmlTypes().hasPackage(packageName)) {
|
||||
importFound = true;
|
||||
foreach (QmlObjectValue *object, engine()->cppQmlTypes().typesForImport(packageName, version)) {
|
||||
namespaceObject->setProperty(object->className(), object);
|
||||
foreach (QmlObjectValue *object,
|
||||
engine()->cppQmlTypes().typesForImport(packageName, version)) {
|
||||
import->setProperty(object->className(), object);
|
||||
}
|
||||
}
|
||||
|
||||
if (!importFound) {
|
||||
error(doc, locationFromRange(import->firstSourceLocation(), import->lastSourceLocation()),
|
||||
error(doc, locationFromRange(importInfo.ast()->firstSourceLocation(),
|
||||
importInfo.ast()->lastSourceLocation()),
|
||||
tr("package not found"));
|
||||
}
|
||||
|
||||
return import;
|
||||
}
|
||||
|
||||
UiQualifiedId *Link::qualifiedTypeNameId(Node *node)
|
||||
@@ -373,6 +396,8 @@ UiQualifiedId *Link::qualifiedTypeNameId(Node *node)
|
||||
|
||||
void Link::error(const Document::Ptr &doc, const AST::SourceLocation &loc, const QString &message)
|
||||
{
|
||||
if (doc->fileName() == _doc->fileName())
|
||||
_diagnosticMessages.append(DiagnosticMessage(DiagnosticMessage::Error, loc, message));
|
||||
Q_D(Link);
|
||||
|
||||
if (doc->fileName() == d->doc->fileName())
|
||||
d->diagnosticMessages.append(DiagnosticMessage(DiagnosticMessage::Error, loc, message));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user