From c7b3e3c81c760bce7a15964a1b583b344e0db1b4 Mon Sep 17 00:00:00 2001
From: Christian Kamm <christian.d.kamm@nokia.com>
Date: Thu, 16 Sep 2010 15:29:37 +0200
Subject: [PATCH] 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.
---
 src/libs/qmljs/qmljsbind.cpp                  |  46 ++-
 src/libs/qmljs/qmljsbind.h                    |  26 +-
 src/libs/qmljs/qmljscheck.cpp                 |  23 +-
 src/libs/qmljs/qmljscheck.h                   |  16 +-
 src/libs/qmljs/qmljsdocument.cpp              |   2 +-
 src/libs/qmljs/qmljsinterpreter.cpp           | 136 ++++++++-
 src/libs/qmljs/qmljsinterpreter.h             |  60 +++-
 src/libs/qmljs/qmljslink.cpp                  | 287 ++++++++++--------
 src/libs/qmljs/qmljslink.h                    |  17 +-
 src/plugins/qmljseditor/qmljseditor.cpp       |  12 +-
 src/plugins/qmljseditor/qmljsmodelmanager.cpp |  25 +-
 11 files changed, 436 insertions(+), 214 deletions(-)

diff --git a/src/libs/qmljs/qmljsbind.cpp b/src/libs/qmljs/qmljsbind.cpp
index 5a24ef84e5e..d1f0847c994 100644
--- a/src/libs/qmljs/qmljsbind.cpp
+++ b/src/libs/qmljs/qmljsbind.cpp
@@ -29,6 +29,7 @@
 
 #include "parser/qmljsast_p.h"
 #include "qmljsbind.h"
+#include "qmljscheck.h"
 #include "qmljsdocument.h"
 
 #include <QtCore/QDir>
@@ -54,11 +55,12 @@ using namespace QmlJS::Interpreter;
     It allows AST to code model lookup through findQmlObject() and findFunctionScope().
 */
 
-Bind::Bind(Document *doc)
+Bind::Bind(Document *doc, QList<DiagnosticMessage> *messages)
     : _doc(doc),
       _currentObjectValue(0),
       _idEnvironment(0),
-      _rootObjectValue(0)
+      _rootObjectValue(0),
+      _diagnosticMessages(messages)
 {
     if (_doc)
         accept(_doc->ast());
@@ -68,7 +70,7 @@ Bind::~Bind()
 {
 }
 
-QList<Bind::ImportInfo> Bind::imports() const
+QList<ImportInfo> Bind::imports() const
 {
     return _imports;
 }
@@ -185,32 +187,44 @@ bool Bind::visit(AST::Program *)
 
 bool Bind::visit(UiImport *ast)
 {
-    ImportInfo info;
-    info.ast = ast;
+    ComponentVersion version;
+    ImportInfo::Type type = ImportInfo::InvalidImport;
+    QString name;
 
     if (ast->versionToken.isValid()) {
         const QString versionString = _doc->source().mid(ast->versionToken.offset, ast->versionToken.length);
         const int dotIdx = versionString.indexOf(QLatin1Char('.'));
         if (dotIdx != -1) {
-            info.version = ComponentVersion(versionString.left(dotIdx).toInt(),
-                                            versionString.mid(dotIdx + 1).toInt());
+            version = ComponentVersion(versionString.left(dotIdx).toInt(),
+                                       versionString.mid(dotIdx + 1).toInt());
+        } else {
+            _diagnosticMessages->append(
+                        errorMessage(ast->versionToken, tr("expected two numbers separated by a dot")));
         }
     }
 
     if (ast->importUri) {
-        info.type = ImportInfo::LibraryImport;
-        info.name = toString(ast->importUri, QLatin1Char('/'));
+        type = ImportInfo::LibraryImport;
+        name = toString(ast->importUri, QDir::separator());
+
+        if (!version.isValid()) {
+            _diagnosticMessages->append(
+                        errorMessage(ast, tr("package import requires a version number")));
+        }
     } else if (ast->fileName) {
-        const QFileInfo importFileInfo(_doc->path() + QLatin1Char('/') + ast->fileName->asString());
-        info.name = importFileInfo.absoluteFilePath();
+        const QFileInfo importFileInfo(_doc->path() + QDir::separator() + ast->fileName->asString());
+        name = importFileInfo.absoluteFilePath();
         if (importFileInfo.isFile())
-            info.type = ImportInfo::FileImport;
+            type = ImportInfo::FileImport;
         else if (importFileInfo.isDir())
-            info.type = ImportInfo::DirectoryImport;
-        else
-            info.type = ImportInfo::InvalidFileImport;
+            type = ImportInfo::DirectoryImport;
+        else {
+            _diagnosticMessages->append(
+                        errorMessage(ast, tr("file or directory not found")));
+            type = ImportInfo::UnknownFileImport;
+        }
     }
-    _imports += info;
+    _imports += ImportInfo(type, name, version, ast);
 
     return false;
 }
diff --git a/src/libs/qmljs/qmljsbind.h b/src/libs/qmljs/qmljsbind.h
index ddbfa5b1df3..b5e779616b6 100644
--- a/src/libs/qmljs/qmljsbind.h
+++ b/src/libs/qmljs/qmljsbind.h
@@ -37,6 +37,7 @@
 #include <QtCore/QHash>
 #include <QtCore/QStringList>
 #include <QtCore/QSharedPointer>
+#include <QtCore/QCoreApplication>
 
 namespace QmlJS {
 
@@ -46,28 +47,13 @@ class Document;
 class QMLJS_EXPORT Bind: protected AST::Visitor
 {
     Q_DISABLE_COPY(Bind)
+    Q_DECLARE_TR_FUNCTIONS(QmlJS::Bind)
 
 public:
-    Bind(Document *doc);
+    Bind(Document *doc, QList<DiagnosticMessage> *messages);
     virtual ~Bind();
 
-    struct ImportInfo {
-        enum Type {
-            LibraryImport,
-            FileImport,
-            DirectoryImport,
-            InvalidFileImport // refers a file/directoy that wasn't found
-        };
-
-        Type type;
-        // LibraryImport: uri with '/' separator
-        // Other: absoluteFilePath
-        QString name;
-        ComponentVersion version;
-        AST::UiImport *ast;
-    };
-
-    QList<ImportInfo> imports() const;
+    QList<Interpreter::ImportInfo> imports() const;
 
     Interpreter::ObjectValue *idEnvironment() const;
     Interpreter::ObjectValue *rootObjectValue() const;
@@ -119,7 +105,9 @@ private:
     QHash<AST::FunctionDeclaration *, Interpreter::ObjectValue *> _functionScopes;
     QStringList _includedScripts;
 
-    QList<ImportInfo> _imports;
+    QList<Interpreter::ImportInfo> _imports;
+
+    QList<DiagnosticMessage> *_diagnosticMessages;
 };
 
 } // end of namespace Qml
diff --git a/src/libs/qmljs/qmljscheck.cpp b/src/libs/qmljs/qmljscheck.cpp
index 96d8d5e87cb..bcab73dcb8e 100644
--- a/src/libs/qmljs/qmljscheck.cpp
+++ b/src/libs/qmljs/qmljscheck.cpp
@@ -62,6 +62,20 @@ QColor QmlJS::toQColor(const QString &qmlColorString)
     return color;
 }
 
+SourceLocation QmlJS::locationFromRange(const SourceLocation &start,
+                                        const SourceLocation &end)
+{
+    return SourceLocation(start.offset,
+                          end.end() - start.begin(),
+                          start.startLine,
+                          start.startColumn);
+}
+
+DiagnosticMessage QmlJS::errorMessage(const AST::SourceLocation &loc, const QString &message)
+{
+    return DiagnosticMessage(DiagnosticMessage::Error, loc, message);
+}
+
 namespace {
 
 class AssignmentCheck : public ValueVisitor
@@ -371,12 +385,3 @@ void Check::warning(const AST::SourceLocation &loc, const QString &message)
 {
     _messages.append(DiagnosticMessage(DiagnosticMessage::Warning, loc, message));
 }
-
-SourceLocation Check::locationFromRange(const SourceLocation &start,
-                                        const SourceLocation &end)
-{
-    return SourceLocation(start.offset,
-                          end.end() - start.begin(),
-                          start.startLine,
-                          start.startColumn);
-}
diff --git a/src/libs/qmljs/qmljscheck.h b/src/libs/qmljs/qmljscheck.h
index 3015987dc73..049b197e5f9 100644
--- a/src/libs/qmljs/qmljscheck.h
+++ b/src/libs/qmljs/qmljscheck.h
@@ -64,8 +64,6 @@ private:
 
     void warning(const AST::SourceLocation &loc, const QString &message);
     void error(const AST::SourceLocation &loc, const QString &message);
-    static AST::SourceLocation locationFromRange(const AST::SourceLocation &start,
-                                                 const AST::SourceLocation &end);
 
     Document::Ptr _doc;
     Snapshot _snapshot;
@@ -80,6 +78,20 @@ private:
 
 QMLJS_EXPORT QColor toQColor(const QString &qmlColorString);
 
+QMLJS_EXPORT AST::SourceLocation locationFromRange(const AST::SourceLocation &start,
+                                                   const AST::SourceLocation &end);
+QMLJS_EXPORT DiagnosticMessage errorMessage(const AST::SourceLocation &loc,
+                                            const QString &message);
+
+template <class T>
+DiagnosticMessage errorMessage(const T *node, const QString &message)
+{
+    return DiagnosticMessage(DiagnosticMessage::Error,
+                             locationFromRange(node->firstSourceLocation(),
+                                               node->lastSourceLocation()),
+                             message);
+}
+
 } // namespace QmlJS
 
 #endif // QMLJSCHECK_H
diff --git a/src/libs/qmljs/qmljsdocument.cpp b/src/libs/qmljs/qmljsdocument.cpp
index c1d9d7f3cbe..75379474f2e 100644
--- a/src/libs/qmljs/qmljsdocument.cpp
+++ b/src/libs/qmljs/qmljsdocument.cpp
@@ -236,7 +236,7 @@ bool Document::parse_helper(int startToken)
     _ast = parser.rootNode();
     _diagnosticMessages = parser.diagnosticMessages();
 
-    _bind = new Bind(this);
+    _bind = new Bind(this, &_diagnosticMessages);
 
     return _parsedCorrectly;
 }
diff --git a/src/libs/qmljs/qmljsinterpreter.cpp b/src/libs/qmljs/qmljsinterpreter.cpp
index ddf0ed993bc..ff32bb517bf 100644
--- a/src/libs/qmljs/qmljsinterpreter.cpp
+++ b/src/libs/qmljs/qmljsinterpreter.cpp
@@ -32,7 +32,6 @@
 #include "qmljslink.h"
 #include "qmljsbind.h"
 #include "qmljsscopebuilder.h"
-#include "qmljscomponentversion.h"
 #include "parser/qmljsast_p.h"
 
 #include <QtCore/QFile>
@@ -1448,14 +1447,14 @@ ScopeChain &Context::scopeChain()
     return _scopeChain;
 }
 
-const ObjectValue *Context::typeEnvironment(const QmlJS::Document *doc) const
+const TypeEnvironment *Context::typeEnvironment(const QmlJS::Document *doc) const
 {
     if (!doc)
         return 0;
     return _typeEnvironments.value(doc->fileName(), 0);
 }
 
-void Context::setTypeEnvironment(const QmlJS::Document *doc, const ObjectValue *typeEnvironment)
+void Context::setTypeEnvironment(const QmlJS::Document *doc, const TypeEnvironment *typeEnvironment)
 {
     if (!doc)
         return;
@@ -3097,3 +3096,134 @@ const Value *ASTSignalReference::value(const Context *) const
 {
     return engine()->undefinedValue();
 }
+
+ImportInfo::ImportInfo()
+    : _type(InvalidImport)
+    , _ast(0)
+{
+}
+
+ImportInfo::ImportInfo(Type type, const QString &name,
+                       QmlJS::ComponentVersion version, UiImport *ast)
+    : _type(type)
+    , _name(name)
+    , _version(version)
+    , _ast(ast)
+{
+}
+
+bool ImportInfo::isValid() const
+{
+    return _type != InvalidImport;
+}
+
+ImportInfo::Type ImportInfo::type() const
+{
+    return _type;
+}
+
+QString ImportInfo::name() const
+{
+    return _name;
+}
+
+QString ImportInfo::id() const
+{
+    if (_ast && _ast->importId)
+        return _ast->importId->asString();
+    return QString();
+}
+
+QmlJS::ComponentVersion ImportInfo::version() const
+{
+    return _version;
+}
+
+UiImport *ImportInfo::ast() const
+{
+    return _ast;
+}
+
+TypeEnvironment::TypeEnvironment(Engine *engine)
+    : ObjectValue(engine)
+{
+}
+
+const Value *TypeEnvironment::lookupMember(const QString &name, const Context *context, bool) const
+{
+    QHashIterator<const ObjectValue *, ImportInfo> it(_imports);
+    while (it.hasNext()) {
+        it.next();
+        const ObjectValue *import = it.key();
+        const ImportInfo &info = it.value();
+
+        if (!info.id().isEmpty()) {
+            if (info.id() == name)
+                return import;
+            continue;
+        }
+
+        if (info.type() == ImportInfo::FileImport) {
+            if (import->className() == name)
+                return import;
+        } else {
+            if (const Value *v = import->property(name, context))
+                return v;
+        }
+    }
+    return 0;
+}
+
+void TypeEnvironment::processMembers(MemberProcessor *processor) const
+{
+    QHashIterator<const ObjectValue *, ImportInfo> it(_imports);
+    while (it.hasNext()) {
+        it.next();
+        const ObjectValue *import = it.key();
+        const ImportInfo &info = it.value();
+
+        if (!info.id().isEmpty()) {
+            processor->processProperty(info.id(), import);
+        } else {
+            if (info.type() == ImportInfo::FileImport)
+                processor->processProperty(import->className(), import);
+            else
+                import->processMembers(processor);
+        }
+    }
+}
+
+void TypeEnvironment::addImport(const ObjectValue *import, const ImportInfo &info)
+{
+    _imports.insert(import, info);
+}
+
+ImportInfo TypeEnvironment::importInfo(const QString &name, const Context *context) const
+{
+    QString firstId = name;
+    int dotIdx = firstId.indexOf(QLatin1Char('.'));
+    if (dotIdx != -1)
+        firstId = firstId.left(dotIdx);
+
+    QHashIterator<const ObjectValue *, ImportInfo> it(_imports);
+    while (it.hasNext()) {
+        it.next();
+        const ObjectValue *import = it.key();
+        const ImportInfo &info = it.value();
+
+        if (!info.id().isEmpty()) {
+            if (info.id() == firstId)
+                return info;
+            continue;
+        }
+
+        if (info.type() == ImportInfo::FileImport) {
+            if (import->className() == firstId)
+                return info;
+        } else {
+            if (import->property(firstId, context))
+                return info;
+        }
+    }
+    return ImportInfo();
+}
diff --git a/src/libs/qmljs/qmljsinterpreter.h b/src/libs/qmljs/qmljsinterpreter.h
index 208ed034e00..f6c7086fb18 100644
--- a/src/libs/qmljs/qmljsinterpreter.h
+++ b/src/libs/qmljs/qmljsinterpreter.h
@@ -63,6 +63,7 @@ class FunctionValue;
 class Reference;
 class ColorValue;
 class AnchorLineValue;
+class TypeEnvironment;
 
 typedef QList<const Value *> ValueList;
 
@@ -259,7 +260,7 @@ public:
     const ObjectValue *globalScope;
     QSharedPointer<const QmlComponentChain> qmlComponentScope;
     QList<const ObjectValue *> qmlScopeObjects;
-    const ObjectValue *qmlTypes;
+    const TypeEnvironment *qmlTypes;
     QList<const ObjectValue *> jsScopes;
 
     // rebuilds the flat list of all scopes
@@ -280,8 +281,8 @@ public:
     const ScopeChain &scopeChain() const;
     ScopeChain &scopeChain();
 
-    const ObjectValue *typeEnvironment(const Document *doc) const;
-    void setTypeEnvironment(const Document *doc, const ObjectValue *typeEnvironment);
+    const TypeEnvironment *typeEnvironment(const Document *doc) const;
+    void setTypeEnvironment(const Document *doc, const TypeEnvironment *typeEnvironment);
 
     const Value *lookup(const QString &name) const;
     const ObjectValue *lookupType(const Document *doc, AST::UiQualifiedId *qmlTypeName) const;
@@ -298,7 +299,7 @@ private:
 
     QSharedPointer<Engine> _engine;
     QHash<const ObjectValue *, Properties> _properties;
-    QHash<QString, const ObjectValue *> _typeEnvironments;
+    QHash<QString, const TypeEnvironment *> _typeEnvironments;
     ScopeChain _scopeChain;
     int _qmlScopeObjectIndex;
     bool _qmlScopeObjectSet;
@@ -846,6 +847,57 @@ public:
     QString defaultPropertyName() const;
 };
 
+class QMLJS_EXPORT ImportInfo
+{
+public:
+    enum Type {
+        InvalidImport,
+        ImplicitDirectoryImport,
+        LibraryImport,
+        FileImport,
+        DirectoryImport,
+        UnknownFileImport // refers a file/directoy that wasn't found
+    };
+
+    ImportInfo();
+    ImportInfo(Type type, const QString &name,
+               ComponentVersion version = ComponentVersion(),
+               AST::UiImport *ast = 0);
+
+    bool isValid() const;
+    Type type() const;
+
+    // LibraryImport: uri with '/' separator
+    // Other: absoluteFilePath
+    QString name() const;
+
+    // null if the import has no 'as', otherwise the target id
+    QString id() const;
+
+    ComponentVersion version() const;
+    AST::UiImport *ast() const;
+
+private:
+    Type _type;
+    QString _name;
+    ComponentVersion _version;
+    AST::UiImport *_ast;
+};
+
+class QMLJS_EXPORT TypeEnvironment: public ObjectValue
+{
+    QHash<const ObjectValue *, ImportInfo> _imports;
+
+public:
+    TypeEnvironment(Engine *engine);
+
+    virtual const Value *lookupMember(const QString &name, const Context *context, bool examinePrototypes) const;
+    virtual void processMembers(MemberProcessor *processor) const;
+
+    void addImport(const ObjectValue *import, const ImportInfo &info);
+    ImportInfo importInfo(const QString &name, const Context *context) const;
+};
+
 } } // end of namespace QmlJS::Interpreter
 
 #endif // QMLJS_INTERPRETER_H
diff --git a/src/libs/qmljs/qmljslink.cpp b/src/libs/qmljs/qmljslink.cpp
index 583a884f78f..a8a971308dd 100644
--- a/src/libs/qmljs/qmljslink.cpp
+++ b/src/libs/qmljs/qmljslink.cpp
@@ -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.
-        populateImportedTypes(typeEnv, doc);
+    // 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);
 
-        _context->setTypeEnvironment(doc.data(), typeEnv);
+    foreach (Document::Ptr doc, d->snapshot) {
+        if (doc == d->doc)
+            continue;
+
+        TypeEnvironment *typeEnv = new TypeEnvironment(engine());
+        populateImportedTypes(typeEnv, doc);
+        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);
     }
-
-    // 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);
+    if (directoryImport)
+        typeEnv->addImport(directoryImport, implcitImportInfo);
+
+    // 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)
-
-    if (!import->fileName)
-        return;
-
-    QString path = doc->path();
-    path += QLatin1Char('/');
-    path += import->fileName->asString();
-    path = QDir::cleanPath(path);
+    Q_D(Link);
 
-    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;
-
-    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;
-    }
+    Q_D(Link);
 
-    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));
 }
diff --git a/src/libs/qmljs/qmljslink.h b/src/libs/qmljs/qmljslink.h
index 54e85446aae..d81fcc509a6 100644
--- a/src/libs/qmljs/qmljslink.h
+++ b/src/libs/qmljs/qmljslink.h
@@ -42,12 +42,14 @@
 namespace QmlJS {
 
 class NameId;
+class LinkPrivate;
 
 /*
     Helper for building a context.
 */
 class QMLJS_EXPORT Link
 {
+    Q_DECLARE_PRIVATE(Link)
     Q_DECLARE_TR_FUNCTIONS(QmlJS::Link)
 
 public:
@@ -73,22 +75,15 @@ private:
     void linkImports();
     void initializeScopeChain();
 
-    void populateImportedTypes(Interpreter::ObjectValue *typeEnv, Document::Ptr doc);
-    void importFile(Interpreter::ObjectValue *typeEnv, Document::Ptr doc,
-                    AST::UiImport *import);
-    void importNonFile(Interpreter::ObjectValue *typeEnv, Document::Ptr doc,
-                       AST::UiImport *import);
+    void populateImportedTypes(Interpreter::TypeEnvironment *typeEnv, Document::Ptr doc);
+    Interpreter::ObjectValue *importFile(Document::Ptr doc, const Interpreter::ImportInfo &importInfo);
+    Interpreter::ObjectValue *importNonFile(Document::Ptr doc, const Interpreter::ImportInfo &importInfo);
     void importObject(Bind *bind, const QString &name, Interpreter::ObjectValue *object, NameId *targetNamespace);
 
     void error(const Document::Ptr &doc, const AST::SourceLocation &loc, const QString &message);
 
 private:
-    Document::Ptr _doc;
-    Snapshot _snapshot;
-    Interpreter::Context *_context;
-    const QStringList _importPaths;
-
-    QList<DiagnosticMessage> _diagnosticMessages;
+    QScopedPointer<LinkPrivate> d_ptr;
 };
 
 } // namespace QmlJS
diff --git a/src/plugins/qmljseditor/qmljseditor.cpp b/src/plugins/qmljseditor/qmljseditor.cpp
index ddfd9cfa5ac..de0f29823b4 100644
--- a/src/plugins/qmljseditor/qmljseditor.cpp
+++ b/src/plugins/qmljseditor/qmljseditor.cpp
@@ -579,9 +579,9 @@ AST::Node *SemanticInfo::nodeUnderCursor(int pos) const
 
     const unsigned cursorPosition = pos;
 
-    foreach (const Bind::ImportInfo &import, document->bind()->imports()) {
-        if (importContainsCursor(import.ast, cursorPosition))
-            return import.ast;
+    foreach (const Interpreter::ImportInfo &import, document->bind()->imports()) {
+        if (importContainsCursor(import.ast(), cursorPosition))
+            return import.ast();
     }
 
     CollectASTNodes nodes;
@@ -1381,9 +1381,9 @@ TextEditor::BaseTextEditor::Link QmlJSTextEditor::findLinkAt(const QTextCursor &
 
     if (AST::UiImport *importAst = cast<AST::UiImport *>(node)) {
         // if it's a file import, link to the file
-        foreach (const Bind::ImportInfo &import, semanticInfo.document->bind()->imports()) {
-            if (import.ast == importAst && import.type == Bind::ImportInfo::FileImport) {
-                BaseTextEditor::Link link(import.name);
+        foreach (const Interpreter::ImportInfo &import, semanticInfo.document->bind()->imports()) {
+            if (import.ast() == importAst && import.type() == Interpreter::ImportInfo::FileImport) {
+                BaseTextEditor::Link link(import.name());
                 link.begin = importAst->firstSourceLocation().begin();
                 link.end = importAst->lastSourceLocation().end();
                 return link;
diff --git a/src/plugins/qmljseditor/qmljsmodelmanager.cpp b/src/plugins/qmljseditor/qmljsmodelmanager.cpp
index ae8e9752416..2dd2b256c2b 100644
--- a/src/plugins/qmljseditor/qmljsmodelmanager.cpp
+++ b/src/plugins/qmljseditor/qmljsmodelmanager.cpp
@@ -276,15 +276,16 @@ static void findNewFileImports(const Document::Ptr &doc, const Snapshot &snapsho
                         QStringList *importedFiles, QSet<QString> *scannedPaths)
 {
     // scan files and directories that are explicitly imported
-    foreach (const Bind::ImportInfo &import, doc->bind()->imports()) {
-        if (import.type == Bind::ImportInfo::FileImport) {
-            if (! snapshot.document(import.name))
-                *importedFiles += import.name;
-        } else if (import.type == Bind::ImportInfo::DirectoryImport) {
-            if (snapshot.documentsInDirectory(import.name).isEmpty()) {
-                if (! scannedPaths->contains(import.name)) {
-                    *importedFiles += qmlFilesInDirectory(import.name);
-                    scannedPaths->insert(import.name);
+    foreach (const Interpreter::ImportInfo &import, doc->bind()->imports()) {
+        const QString &importName = import.name();
+        if (import.type() == Interpreter::ImportInfo::FileImport) {
+            if (! snapshot.document(importName))
+                *importedFiles += importName;
+        } else if (import.type() == Interpreter::ImportInfo::DirectoryImport) {
+            if (snapshot.documentsInDirectory(importName).isEmpty()) {
+                if (! scannedPaths->contains(importName)) {
+                    *importedFiles += qmlFilesInDirectory(importName);
+                    scannedPaths->insert(importName);
                 }
             }
         }
@@ -297,12 +298,12 @@ static void findNewLibraryImports(const Document::Ptr &doc, const Snapshot &snap
 {
     // scan library imports
     const QStringList importPaths = modelManager->importPaths();
-    foreach (const Bind::ImportInfo &import, doc->bind()->imports()) {
-        if (import.type != Bind::ImportInfo::LibraryImport)
+    foreach (const Interpreter::ImportInfo &import, doc->bind()->imports()) {
+        if (import.type() != Interpreter::ImportInfo::LibraryImport)
             continue;
         foreach (const QString &importPath, importPaths) {
             QDir dir(importPath);
-            dir.cd(import.name);
+            dir.cd(import.name());
             const QString targetPath = dir.absolutePath();
 
             // if we know there is a library, done
-- 
GitLab