From 03fa188b41e46d9b0f6ad4d4e6e97f2d3711568c Mon Sep 17 00:00:00 2001
From: Christian Kamm <christian.d.kamm@nokia.com>
Date: Tue, 2 Feb 2010 15:55:17 +0100
Subject: [PATCH] Bind each QML document after parsing. Link them before use.

Reviewed-by: Roberto Raggi <roberto.raggi@nokia.com>
---
 src/libs/qmljs/qmljsbind.cpp                  | 203 +----------
 src/libs/qmljs/qmljsbind.h                    |  19 +-
 src/libs/qmljs/qmljsdocument.cpp              |  10 +
 src/libs/qmljs/qmljsdocument.h                |   6 +
 src/libs/qmljs/qmljslink.cpp                  | 329 +++++++++++++-----
 src/libs/qmljs/qmljslink.h                    |  42 ++-
 src/plugins/qmljseditor/qmlcodecompletion.cpp |   3 +-
 src/plugins/qmljseditor/qmlhoverhandler.cpp   |   4 +-
 8 files changed, 311 insertions(+), 305 deletions(-)

diff --git a/src/libs/qmljs/qmljsbind.cpp b/src/libs/qmljs/qmljsbind.cpp
index bdd626e22e5..01f489c336f 100644
--- a/src/libs/qmljs/qmljsbind.cpp
+++ b/src/libs/qmljs/qmljsbind.cpp
@@ -121,76 +121,27 @@ public:
     }
 };
 
-class ProcessSourceElements: protected AST::Visitor
-{
-    Interpreter::Engine *_interp;
-
-public:
-    ProcessSourceElements(Interpreter::Engine *interp)
-        : _interp(interp),
-          typeOfExpression(interp)
-    {
-    }
-
-    void operator()(AST::Node *node)
-    {
-        if (node)
-            node->accept(this);
-    }
-
-protected:
-    using AST::Visitor::visit;
-
-    virtual bool visit(FunctionDeclaration *ast)
-    {
-        if (ast->name)
-            _interp->globalObject()->setProperty(ast->name->asString(), new ASTFunctionValue(ast, _interp));
-
-        return false;
-    }
-
-    virtual bool visit(VariableDeclaration *ast)
-    {
-        if (ast->name) {
-            const Value *value = _interp->undefinedValue();
-
-            if (ast->expression)
-                value = typeOfExpression(ast->expression, _interp->globalObject());
-
-            _interp->globalObject()->setProperty(ast->name->asString(), value);
-        }
-
-        return false;
-    }
-
-    Check typeOfExpression;
-};
-
 } // end of anonymous namespace
 
-Bind::Bind(Document::Ptr doc, Interpreter::Engine *interp)
+Bind::Bind(Document *doc)
     : _doc(doc),
-      _interp(interp),
       _currentObjectValue(0),
-      _typeEnvironment(0),
       _idEnvironment(0),
       _functionEnvironment(0),
       _rootObjectValue(0)
 {
-    if (_doc && _doc->qmlProgram() != 0) {
-        UiProgram *program = _doc->qmlProgram();
-
-        _currentObjectValue = 0;
-        _typeEnvironment = _interp->newObject(/*prototype =*/ 0);
-        _idEnvironment = _interp->newObject(/*prototype =*/ 0);
-        _functionEnvironment = _interp->newObject(/*prototype =*/ 0);
-        _rootObjectValue = 0;
-
-        _qmlObjectDefinitions.clear();
-        _qmlObjectBindings.clear();
+    if (!_doc)
+        return;
 
-        accept(program);
+    if (_doc->qmlProgram()) {
+        _idEnvironment = _interp.newObject(/*prototype =*/ 0);
+        _functionEnvironment = _interp.newObject(/*prototype =*/ 0);
+    } else if (_doc->jsProgram()) {
+        _currentObjectValue = _interp.globalObject();
+        _rootObjectValue = _interp.globalObject();
     }
+
+    accept(_doc->ast());
 }
 
 Bind::~Bind()
@@ -214,77 +165,6 @@ ObjectValue *Bind::switchObjectValue(ObjectValue *newObjectValue)
     return oldObjectValue;
 }
 
-ObjectValue *Bind::scopeChainAt(Document::Ptr currentDocument, const Snapshot &snapshot,
-                                Interpreter::Engine *interp, AST::UiObjectMember *currentObject)
-{
-    Bind *currentBind = 0;
-    QList<Bind *> binds;
-
-    QSet<QString> processed;
-    QStringList todo;
-
-    QMultiHash<QString, Document::Ptr> documentByPath;
-    foreach (Document::Ptr doc, snapshot)
-        documentByPath.insert(doc->path(), doc);
-
-    todo.append(currentDocument->path());
-
-    // Bind the reachable documents.
-    while (! todo.isEmpty()) {
-        const QString path = todo.takeFirst();
-
-        if (processed.contains(path))
-            continue;
-
-        processed.insert(path);
-
-        QStringList localImports;
-        foreach (Document::Ptr doc, documentByPath.values(path)) {
-            Bind *newBind = new Bind(doc, interp);
-            binds += newBind;
-
-            localImports += newBind->localImports();
-
-            if (doc == currentDocument)
-                currentBind = newBind;
-        }
-
-        localImports.removeDuplicates();
-        todo += localImports;
-    }
-
-    LinkImports linkImports;
-    Link link;
-
-    // link the import directives
-    linkImports(binds);
-
-    // link the scripts
-    QStringList includedScriptFiles;
-    foreach (Bind *bind, binds)
-        includedScriptFiles += bind->includedScripts();
-
-    includedScriptFiles.removeDuplicates();
-
-    ProcessSourceElements processSourceElements(interp);
-
-    foreach (const QString &scriptFile, includedScriptFiles) {
-        if (Document::Ptr scriptDoc = snapshot.document(scriptFile)) {
-            if (AST::Program *program = scriptDoc->jsProgram()) {
-                processSourceElements(program);
-            }
-        }
-    }
-
-    ObjectValue *scope = interp->globalObject();
-    if (currentBind)
-        scope = link(binds, currentBind, currentObject);
-
-    qDeleteAll(binds);
-
-    return scope;
-}
-
 QString Bind::toString(UiQualifiedId *qualifiedId, QChar delimiter)
 {
     QString result;
@@ -350,7 +230,7 @@ ObjectValue *Bind::bindObject(UiQualifiedId *qualifiedTypeNameId, UiObjectInitia
     }
 
     // normal component instance
-    ASTObjectValue *objectValue = new ASTObjectValue(qualifiedTypeNameId, initializer, _interp);
+    ASTObjectValue *objectValue = new ASTObjectValue(qualifiedTypeNameId, initializer, &_interp);
     parentObjectValue = switchObjectValue(objectValue);
 
     if (parentObjectValue)
@@ -368,64 +248,9 @@ void Bind::accept(Node *node)
     Node::accept(node, this);
 }
 
-/*
-  import Qt 4.6
-  import Qt 4.6 as Xxx
-  (import com.nokia.qt is the same as the ones above)
-
-  import "content"
-  import "content" as Xxx
-  import "content" 4.6
-  import "content" 4.6 as Xxx
-
-  import "http://www.ovi.com/" as Ovi
- */
-// ### TODO: Move to LinkImports
 bool Bind::visit(UiImport *ast)
 {
-    if (! (ast->importUri || ast->fileName))
-        return false; // nothing to do.
-
-    ObjectValue *namespaceObject = 0;
-
-    if (ast->asToken.isValid()) { // with namespace we insert an object in the type env. to hold the imported types
-        if (!ast->importId)
-            return false; // this should never happen, but better be safe than sorry
-
-        namespaceObject = _interp->newObject(/*prototype */ 0);
-
-        _typeEnvironment->setProperty(ast->importId->asString(), namespaceObject);
-
-    } else { // without namespace we insert all types directly into the type env.
-        namespaceObject = _typeEnvironment;
-    }
-
-    // look at files first
-
-    // else try the metaobject system
-    if (ast->importUri) {
-        const QString package = toString(ast->importUri, '/');
-        int majorVersion = -1; // ### TODO: Check these magic version numbers
-        int minorVersion = -1; // ### TODO: Check these magic version numbers
-
-        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) {
-                // only major (which is probably invalid, but let's handle it anyway)
-                majorVersion = versionString.toInt();
-                minorVersion = 0; // ### TODO: Check with magic version numbers above
-            } else {
-                majorVersion = versionString.left(dotIdx).toInt();
-                minorVersion = versionString.mid(dotIdx + 1).toInt();
-            }
-        }
-#ifndef NO_DECLARATIVE_BACKEND
-        foreach (QmlObjectValue *object, _interp->metaTypeSystem().staticTypesForImport(package, majorVersion, minorVersion)) {
-            namespaceObject->setProperty(object->qmlTypeName(), object);
-        }
-#endif // NO_DECLARATIVE_BACKEND
-    } else if (ast->fileName) {
+    if (ast->fileName) {
         QString path = _doc->path();
         path += QLatin1Char('/');
         path += ast->fileName->asString();
@@ -488,7 +313,7 @@ bool Bind::visit(FunctionDeclaration *ast)
     if (_currentObjectValue->property(ast->name->asString()))
         return false;
 
-    ASTFunctionValue *function = new ASTFunctionValue(ast, _interp);
+    ASTFunctionValue *function = new ASTFunctionValue(ast, &_interp);
     _currentObjectValue->setProperty(ast->name->asString(), function);
     return false; // ### eventually want to visit function bodies
 }
diff --git a/src/libs/qmljs/qmljsbind.h b/src/libs/qmljs/qmljsbind.h
index f9f35cbdc23..d5ce0e6f0af 100644
--- a/src/libs/qmljs/qmljsbind.h
+++ b/src/libs/qmljs/qmljsbind.h
@@ -31,11 +31,12 @@
 #define QMLBIND_H
 
 #include <qmljs/parser/qmljsastvisitor_p.h>
-#include <qmljs/qmljsdocument.h>
 #include <qmljs/qmljsinterpreter.h>
+#include <qmljs/qmljsdocument.h>
 
 #include <QtCore/QHash>
 #include <QtCore/QStringList>
+#include <QtCore/QSharedPointer>
 
 namespace QmlJS {
 
@@ -44,21 +45,13 @@ class Link;
 
 class QMLJS_EXPORT Bind: protected AST::Visitor
 {
-protected:
-    Bind(Document::Ptr doc, Interpreter::Engine *interp);
-
 public:
+    Bind(Document *doc);
     virtual ~Bind();
 
     QStringList includedScripts() const;
     QStringList localImports() const;
 
-    // ### TODO: This methods should go. Bind each document after parsing, link later.
-    static Interpreter::ObjectValue *scopeChainAt(Document::Ptr currentDocument,
-                                                  const Snapshot &snapshot,
-                                                  Interpreter::Engine *interp,
-                                                  AST::UiObjectMember *currentObject);
-
 protected:
     using AST::Visitor::visit;
 
@@ -83,11 +76,10 @@ protected:
     Interpreter::ObjectValue *bindObject(AST::UiQualifiedId *qualifiedTypeNameId, AST::UiObjectInitializer *initializer);
 
 private:
-    Document::Ptr _doc;
-    Interpreter::Engine *_interp;
+    Document *_doc;
+    Interpreter::Engine _interp;
 
     Interpreter::ObjectValue *_currentObjectValue;
-    Interpreter::ObjectValue *_typeEnvironment;
     Interpreter::ObjectValue *_idEnvironment;
     Interpreter::ObjectValue *_functionEnvironment;
     Interpreter::ObjectValue *_rootObjectValue;
@@ -100,6 +92,7 @@ private:
     friend class LinkImports;
     friend class Link;
 };
+typedef QSharedPointer<Bind> BindPtr;
 
 } // end of namespace Qml
 
diff --git a/src/libs/qmljs/qmljsdocument.cpp b/src/libs/qmljs/qmljsdocument.cpp
index 262d7745e31..a215198fe98 100644
--- a/src/libs/qmljs/qmljsdocument.cpp
+++ b/src/libs/qmljs/qmljsdocument.cpp
@@ -28,6 +28,7 @@
 **************************************************************************/
 
 #include "qmljsdocument.h"
+#include "qmljsbind.h"
 #include <qmljs/parser/qmljsast_p.h>
 #include <qmljs/parser/qmljslexer_p.h>
 #include <qmljs/parser/qmljsparser_p.h>
@@ -143,6 +144,8 @@ bool Document::parseQml()
     _ast = parser.ast();
     _diagnosticMessages = parser.diagnosticMessages();
 
+    _bind = BindPtr(new Bind(this));
+
     return _parsedCorrectly;
 }
 
@@ -164,6 +167,8 @@ bool Document::parseJavaScript()
     _ast = cast<Program*>(parser.rootNode());
     _diagnosticMessages = parser.diagnosticMessages();
 
+    _bind = BindPtr(new Bind(this));
+
     return _parsedCorrectly;
 }
 
@@ -190,6 +195,11 @@ bool Document::parseExpression()
     return _parsedCorrectly;
 }
 
+BindPtr Document::bind() const
+{
+    return _bind;
+}
+
 Snapshot::Snapshot()
 {
 }
diff --git a/src/libs/qmljs/qmljsdocument.h b/src/libs/qmljs/qmljsdocument.h
index 4ba69aa7c7a..bef0fd986e4 100644
--- a/src/libs/qmljs/qmljsdocument.h
+++ b/src/libs/qmljs/qmljsdocument.h
@@ -40,6 +40,9 @@
 
 namespace QmlJS {
 
+class Bind;
+typedef QSharedPointer<Bind> BindPtr;
+
 class QMLJS_EXPORT Document
 {
 public:
@@ -71,6 +74,8 @@ public:
     bool isParsedCorrectly() const
     { return _parsedCorrectly; }
 
+    BindPtr bind() const;
+
     int documentRevision() const;
     void setDocumentRevision(int documentRevision);
 
@@ -89,6 +94,7 @@ private:
     QString _path;
     QString _componentName;
     QString _source;
+    BindPtr _bind;
 };
 
 class QMLJS_EXPORT Snapshot
diff --git a/src/libs/qmljs/qmljslink.cpp b/src/libs/qmljs/qmljslink.cpp
index e86db8799a3..57c99b33ab2 100644
--- a/src/libs/qmljs/qmljslink.cpp
+++ b/src/libs/qmljs/qmljslink.cpp
@@ -12,6 +12,124 @@ using namespace QmlJS;
 using namespace QmlJS::Interpreter;
 using namespace QmlJS::AST;
 
+Link::Link(Document::Ptr currentDoc, const Snapshot &snapshot, Interpreter::Engine *interp)
+    : _snapshot(snapshot)
+    , _interp(interp)
+{
+    _docs = reachableDocuments(currentDoc, snapshot);
+
+    linkImports();
+}
+
+Link::~Link()
+{
+    // unset all prototypes and scopes
+    foreach (Document::Ptr doc, _docs) {
+        BindPtr bind = doc->bind();
+
+        if (doc->qmlProgram()) {
+            bind->_idEnvironment->setScope(0);
+            bind->_functionEnvironment->setScope(0);
+
+            foreach (ObjectValue *object, bind->_qmlObjectBindings) {
+                object->setPrototype(0);
+                object->setScope(0);
+            }
+            foreach (ObjectValue *object, bind->_qmlObjectDefinitions) {
+                object->setPrototype(0);
+                object->setScope(0);
+            }
+        } else if (doc->jsProgram()) {
+            bind->_rootObjectValue->setScope(0);
+        }
+    }
+}
+
+static ObjectValue *pushScope(ObjectValue *next, ObjectValue *onto)
+{
+    onto->setScope(next);
+    return next;
+}
+
+ObjectValue *Link::scopeChainAt(Document::Ptr doc, UiObjectMember *currentObject)
+{
+    BindPtr bind = doc->bind();
+
+    ObjectValue *scopeObject;
+    if (UiObjectDefinition *definition = cast<UiObjectDefinition *>(currentObject))
+        scopeObject = bind->_qmlObjectDefinitions.value(definition);
+    else if (UiObjectBinding *binding = cast<UiObjectBinding *>(currentObject))
+        scopeObject = bind->_qmlObjectBindings.value(binding);
+    else
+        return bind->_interp.globalObject();
+
+    if (!scopeObject)
+        return bind->_interp.globalObject();
+
+    // Build the scope chain.
+    ObjectValue *scopeStart = _typeEnvironments.value(doc.data());
+    ObjectValue *scope = scopeStart;
+    scope = pushScope(bind->_idEnvironment, scope);
+    scope = pushScope(bind->_functionEnvironment, scope);
+
+    foreach (const QString &scriptFile, doc->bind()->includedScripts()) {
+        if (Document::Ptr scriptDoc = _snapshot.document(scriptFile)) {
+            if (scriptDoc->jsProgram()) {
+                scope = pushScope(scriptDoc->bind()->_rootObjectValue, scope);
+            }
+        }
+    }
+
+    scope = pushScope(scopeObject, scope);
+    if (scopeObject != bind->_rootObjectValue)
+        scope = pushScope(bind->_rootObjectValue, scope);
+
+    scope = pushScope(bind->_interp.globalObject(), scope);
+
+    // May want to link to instantiating components from here.
+
+    return scopeStart;
+}
+
+void Link::linkImports()
+{
+    foreach (Document::Ptr doc, _docs) {
+        BindPtr bind = doc->bind();
+
+        ObjectValue *typeEnv = _interp->newObject(/*prototype =*/0);
+        _typeEnvironments.insert(doc.data(), typeEnv);
+
+        // Populate the _typeEnvironment with imports.
+        populateImportedTypes(typeEnv, doc);
+
+        // Set the prototypes.
+        {
+            QHash<UiObjectDefinition *, ObjectValue *>::iterator it = bind->_qmlObjectDefinitions.begin();
+            QHash<UiObjectDefinition *, ObjectValue *>::iterator end = bind->_qmlObjectDefinitions.end();
+            for (; it != end; ++it) {
+                UiObjectDefinition *key = it.key();
+                ObjectValue *value = it.value();
+                if (!key->qualifiedTypeNameId)
+                    continue;
+
+                value->setPrototype(lookupType(typeEnv, key->qualifiedTypeNameId));
+            }
+        }
+        {
+            QHash<UiObjectBinding *, ObjectValue *>::iterator it = bind->_qmlObjectBindings.begin();
+            QHash<UiObjectBinding *, ObjectValue *>::iterator end = bind->_qmlObjectBindings.end();
+            for (; it != end; ++it) {
+                UiObjectBinding *key = it.key();
+                ObjectValue *value = it.value();
+                if (!key->qualifiedTypeNameId)
+                    continue;
+
+                value->setPrototype(lookupType(typeEnv, key->qualifiedTypeNameId));
+            }
+        }
+    }
+}
+
 static QString componentName(const QString &fileName)
 {
     QString componentName = fileName;
@@ -22,11 +140,8 @@ static QString componentName(const QString &fileName)
     return componentName;
 }
 
-void LinkImports::linkImports(Bind *bind, const QList<Bind *> &binds)
+void Link::populateImportedTypes(Interpreter::ObjectValue *typeEnv, Document::Ptr doc)
 {
-    // ### TODO: remove all properties from _typeEnv
-
-    Document::Ptr doc = bind->_doc;
     if (! (doc->qmlProgram() && doc->qmlProgram()->imports))
         return;
 
@@ -35,65 +150,127 @@ void LinkImports::linkImports(Bind *bind, const QList<Bind *> &binds)
 
     // implicit imports:
     // qml files in the same directory are available without explicit imports
-    foreach (Bind *otherBind, binds) {
-        if (otherBind == bind)
+    foreach (Document::Ptr otherDoc, _docs) {
+        if (otherDoc == doc)
             continue;
 
-        Document::Ptr otherDoc = otherBind->_doc;
         QFileInfo otherFileInfo(otherDoc->fileName());
         const QString otherAbsolutePath = otherFileInfo.absolutePath();
 
         if (otherAbsolutePath != absolutePath)
             continue;
 
-        bind->_typeEnvironment->setProperty(componentName(otherFileInfo.fileName()),
-                                            otherBind->_rootObjectValue);
+        typeEnv->setProperty(componentName(otherFileInfo.fileName()),
+                             otherDoc->bind()->_rootObjectValue);
     }
 
     // explicit imports, whether directories or files
     for (UiImportList *it = doc->qmlProgram()->imports; it; it = it->next) {
-        if (! (it->import && it->import->fileName))
+        if (! it->import)
             continue;
 
-        QString path = absolutePath;
-        path += QLatin1Char('/');
-        path += it->import->fileName->asString();
-        path = QDir::cleanPath(path);
+        if (it->import->fileName) {
+            importFile(typeEnv, doc, it->import, absolutePath);
+        } else if (it->import->importUri) {
+            importNonFile(typeEnv, doc, it->import);
+        }
+    }
+}
 
-        ObjectValue *importNamespace = 0;
+/*
+    import "content"
+    import "content" as Xxx
+    import "content" 4.6
+    import "content" 4.6 as Xxx
 
-        foreach (Bind *otherBind, binds) {
-            Document::Ptr otherDoc = otherBind->_doc;
-            QFileInfo otherFileInfo(otherDoc->fileName());
-            const QString otherAbsolutePath = otherFileInfo.absolutePath();
+    import "http://www.ovi.com/" as Ovi
+*/
+void Link::importFile(Interpreter::ObjectValue *typeEnv, Document::Ptr doc,
+                      AST::UiImport *import, const QString &startPath)
+{
+    if (!import->fileName)
+        return;
 
-            bool directoryImport = (path == otherAbsolutePath);
-            bool fileImport = (path == otherDoc->fileName());
-            if (!directoryImport && !fileImport)
-                continue;
+    QString path = startPath;
+    path += QLatin1Char('/');
+    path += import->fileName->asString();
+    path = QDir::cleanPath(path);
 
-            if (directoryImport && it->import->importId && !importNamespace) {
-                importNamespace = bind->_interp->newObject(/*prototype =*/0);
-                bind->_typeEnvironment->setProperty(it->import->importId->asString(), importNamespace);
-            }
+    ObjectValue *importNamespace = 0;
 
-            QString targetName;
-            if (fileImport && it->import->importId) {
-                targetName = it->import->importId->asString();
-            } else {
-                targetName = componentName(otherFileInfo.fileName());
-            }
+    foreach (Document::Ptr otherDoc, _docs) {
+        QFileInfo otherFileInfo(otherDoc->fileName());
+        const QString otherAbsolutePath = otherFileInfo.absolutePath();
+
+        bool directoryImport = (path == otherAbsolutePath);
+        bool fileImport = (path == otherDoc->fileName());
+        if (!directoryImport && !fileImport)
+            continue;
 
-            ObjectValue *importInto = bind->_typeEnvironment;
-            if (importNamespace)
-                importInto = importNamespace;
+        if (directoryImport && import->importId && !importNamespace) {
+            importNamespace = _interp->newObject(/*prototype =*/0);
+            typeEnv->setProperty(import->importId->asString(), importNamespace);
+        }
 
-            importInto->setProperty(targetName, otherBind->_rootObjectValue);
+        QString targetName;
+        if (fileImport && import->importId) {
+            targetName = import->importId->asString();
+        } else {
+            targetName = componentName(otherFileInfo.fileName());
         }
+
+        ObjectValue *importInto = typeEnv;
+        if (importNamespace)
+            importInto = importNamespace;
+
+        importInto->setProperty(targetName, otherDoc->bind()->_rootObjectValue);
     }
 }
 
-static const ObjectValue *lookupType(ObjectValue *env, UiQualifiedId *id)
+/*
+  import Qt 4.6
+  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 *namespaceObject = 0;
+
+    if (import->importId) { // with namespace we insert an object in the type env. to hold the imported types
+        namespaceObject = _interp->newObject(/*prototype */ 0);
+        typeEnv->setProperty(import->importId->asString(), namespaceObject);
+
+    } else { // without namespace we insert all types directly into the type env.
+        namespaceObject = typeEnv;
+    }
+
+    // try the metaobject system
+    if (import->importUri) {
+        const QString package = Bind::toString(import->importUri, '/');
+        int majorVersion = -1; // ### TODO: Check these magic version numbers
+        int minorVersion = -1; // ### TODO: Check these magic version numbers
+
+        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) {
+                // only major (which is probably invalid, but let's handle it anyway)
+                majorVersion = versionString.toInt();
+                minorVersion = 0; // ### TODO: Check with magic version numbers above
+            } else {
+                majorVersion = versionString.left(dotIdx).toInt();
+                minorVersion = versionString.mid(dotIdx + 1).toInt();
+            }
+        }
+#ifndef NO_DECLARATIVE_BACKEND
+        foreach (QmlObjectValue *object, _interp->metaTypeSystem().staticTypesForImport(package, majorVersion, minorVersion)) {
+            namespaceObject->setProperty(object->qmlTypeName(), object);
+        }
+#endif // NO_DECLARATIVE_BACKEND
+    }
+}
+
+const ObjectValue *Link::lookupType(ObjectValue *env, UiQualifiedId *id)
 {
     const ObjectValue *objectValue = env;
 
@@ -111,65 +288,37 @@ static const ObjectValue *lookupType(ObjectValue *env, UiQualifiedId *id)
     return objectValue;
 }
 
-void LinkImports::operator()(const QList<Bind *> &binds)
+QList<Document::Ptr> Link::reachableDocuments(Document::Ptr startDoc, const Snapshot &snapshot)
 {
-    foreach (Bind *bind, binds) {
-        // Populate the _typeEnvironment with imports.
-        linkImports(bind, binds);
+    QList<Document::Ptr> docs;
 
-        // Set the prototypes.
-        {
-            QHash<UiObjectDefinition *, ObjectValue *>::iterator it = bind->_qmlObjectDefinitions.begin();
-            QHash<UiObjectDefinition *, ObjectValue *>::iterator end = bind->_qmlObjectDefinitions.end();
-            for (; it != end; ++it) {
-                UiObjectDefinition *key = it.key();
-                ObjectValue *value = it.value();
-                if (!key->qualifiedTypeNameId)
-                    continue;
+    QSet<QString> processed;
+    QStringList todo;
 
-                value->setPrototype(lookupType(bind->_typeEnvironment, key->qualifiedTypeNameId));
-            }
-        }
-        {
-            QHash<UiObjectBinding *, ObjectValue *>::iterator it = bind->_qmlObjectBindings.begin();
-            QHash<UiObjectBinding *, ObjectValue *>::iterator end = bind->_qmlObjectBindings.end();
-            for (; it != end; ++it) {
-                UiObjectBinding *key = it.key();
-                ObjectValue *value = it.value();
-                if (!key->qualifiedTypeNameId)
-                    continue;
+    QMultiHash<QString, Document::Ptr> documentByPath;
+    foreach (Document::Ptr doc, snapshot)
+        documentByPath.insert(doc->path(), doc);
 
-                value->setPrototype(lookupType(bind->_typeEnvironment, key->qualifiedTypeNameId));
-            }
-        }
-    }
-}
+    todo.append(startDoc->path());
 
-ObjectValue *Link::operator()(const QList<Bind *> &binds, Bind *currentBind, UiObjectMember *currentObject)
-{
-    Q_UNUSED(binds);
-    ObjectValue *scopeObject;
-    if (UiObjectDefinition *definition = cast<UiObjectDefinition *>(currentObject))
-        scopeObject = currentBind->_qmlObjectDefinitions.value(definition);
-    else if (UiObjectBinding *binding = cast<UiObjectBinding *>(currentObject))
-        scopeObject = currentBind->_qmlObjectBindings.value(binding);
-    else
-        return currentBind->_interp->globalObject();
+    // Find the reachable documents.
+    while (! todo.isEmpty()) {
+        const QString path = todo.takeFirst();
 
-    if (!scopeObject)
-        return currentBind->_interp->globalObject();
+        if (processed.contains(path))
+            continue;
 
-    // Build the scope chain.
-    currentBind->_typeEnvironment->setScope(currentBind->_idEnvironment);
-    currentBind->_idEnvironment->setScope(currentBind->_functionEnvironment);
-    currentBind->_functionEnvironment->setScope(scopeObject);
-    if (scopeObject != currentBind->_rootObjectValue) {
-        scopeObject->setScope(currentBind->_rootObjectValue);
-        currentBind->_rootObjectValue->setScope(currentBind->_interp->globalObject());
-    } else {
-        scopeObject->setScope(currentBind->_interp->globalObject());
+        processed.insert(path);
+
+        QStringList localImports;
+        foreach (Document::Ptr doc, documentByPath.values(path)) {
+            docs += doc;
+            localImports += doc->bind()->localImports();
+        }
+
+        localImports.removeDuplicates();
+        todo += localImports;
     }
-    // May want to link to instantiating components from here.
 
-    return currentBind->_typeEnvironment;
+    return docs;
 }
diff --git a/src/libs/qmljs/qmljslink.h b/src/libs/qmljs/qmljslink.h
index 799d1f967f4..dc37c106700 100644
--- a/src/libs/qmljs/qmljslink.h
+++ b/src/libs/qmljs/qmljslink.h
@@ -1,29 +1,49 @@
 #ifndef QMLJSLINK_H
 #define QMLJSLINK_H
 
+#include <qmljs/qmljsdocument.h>
+#include <qmljs/qmljsbind.h>
 #include <qmljs/qmljsinterpreter.h>
 #include <qmljs/parser/qmljsastfwd_p.h>
 #include <qmljs/parser/qmljsengine_p.h>
 
 #include <QtCore/QList>
+#include <QtCore/QHash>
 
 namespace QmlJS {
 
-class Bind;
-
-class LinkImports
+/*
+    Temporarily links a set of bound documents together to allow resolving cross-document
+    dependencies. The link is undone when this object is destoyed.
+*/
+class QMLJS_EXPORT Link
 {
 public:
-    void operator()(const QList<Bind *> &binds);
+    // Link all documents in snapshot reachable from doc.
+    Link(Document::Ptr doc, const Snapshot &snapshot, Interpreter::Engine *interp);
+    ~Link();
+
+    // Get the scope chain for the currentObject inside doc.
+    Interpreter::ObjectValue *scopeChainAt(Document::Ptr doc, AST::UiObjectMember *currentObject);
+
 private:
-    void importObject(Bind *bind, const QString &name, Interpreter::ObjectValue *object, NameId* targetNamespace);
-    void linkImports(Bind *bind, const QList<Bind *> &binds);
-};
+    static QList<Document::Ptr> reachableDocuments(Document::Ptr startDoc, const Snapshot &snapshot);
+    static const Interpreter::ObjectValue *lookupType(Interpreter::ObjectValue *env, AST::UiQualifiedId *id);
 
-class Link
-{
-public:
-    Interpreter::ObjectValue *operator()(const QList<Bind *> &binds, Bind *currentBind, AST::UiObjectMember *currentObject);
+    void linkImports();
+
+    void populateImportedTypes(Interpreter::ObjectValue *typeEnv, Document::Ptr doc);
+    void importFile(Interpreter::ObjectValue *typeEnv, Document::Ptr doc,
+                    AST::UiImport *import, const QString &startPath);
+    void importNonFile(Interpreter::ObjectValue *typeEnv, Document::Ptr doc,
+                       AST::UiImport *import);
+    void importObject(BindPtr bind, const QString &name, Interpreter::ObjectValue *object, NameId* targetNamespace);
+
+private:
+    Snapshot _snapshot;
+    Interpreter::Engine *_interp;
+    QList<Document::Ptr> _docs;
+    QHash<Document *, Interpreter::ObjectValue *> _typeEnvironments;
 };
 
 } // namespace QmlJS
diff --git a/src/plugins/qmljseditor/qmlcodecompletion.cpp b/src/plugins/qmljseditor/qmlcodecompletion.cpp
index 2d6340de713..043bdd28fa9 100644
--- a/src/plugins/qmljseditor/qmlcodecompletion.cpp
+++ b/src/plugins/qmljseditor/qmlcodecompletion.cpp
@@ -632,10 +632,11 @@ int QmlCodeCompletion::startCompletion(TextEditor::ITextEditable *editor)
 
     // Set up the current scope chain.
     Interpreter::ObjectValue *scope = interp.globalObject();
+    Link link(qmlDocument, snapshot, &interp);
 
     if (isQmlFile && qmlDocument) {
         AST::UiObjectMember *declaringMember = semanticInfo.declaringMember(editor->position());
-        scope = Bind::scopeChainAt(qmlDocument, snapshot, &interp, declaringMember);
+        scope = link.scopeChainAt(qmlDocument, declaringMember);
     }
 
     // Search for the operator that triggered the completion.
diff --git a/src/plugins/qmljseditor/qmlhoverhandler.cpp b/src/plugins/qmljseditor/qmlhoverhandler.cpp
index 71e7012bfc3..fc2dd793d1f 100644
--- a/src/plugins/qmljseditor/qmlhoverhandler.cpp
+++ b/src/plugins/qmljseditor/qmlhoverhandler.cpp
@@ -37,6 +37,7 @@
 #include <debugger/debuggerconstants.h>
 #include <extensionsystem/pluginmanager.h>
 #include <qmljs/qmljsbind.h>
+#include <qmljs/qmljslink.h>
 #include <qmljs/qmljscheck.h>
 #include <qmljs/qmljsinterpreter.h>
 #include <qmljs/parser/qmljsast_p.h>
@@ -171,7 +172,8 @@ void QmlHoverHandler::updateHelpIdAndTooltip(TextEditor::ITextEditor *editor, in
             AST::UiObjectMember *declaringMember = semanticInfo.declaringMember(pos);
 
             Interpreter::Engine interp;
-            const Interpreter::ObjectValue *scope = Bind::scopeChainAt(qmlDocument, snapshot, &interp, declaringMember);
+            Link link(qmlDocument, snapshot, &interp);
+            const Interpreter::ObjectValue *scope = link.scopeChainAt(qmlDocument, declaringMember);
 
             Check check(&interp);
             const Interpreter::Value *value = check(node, scope);
-- 
GitLab