Commit 2b1adbf6 authored by Christian Kamm's avatar Christian Kamm
Browse files

Improve building of the Qml/JS scope chain.

* For JS files: Add each Qml component that sources the file in a Script
  tag to the scope chain.
* For Qml components: Add each component that instantiates the component
  to the scope chain.
* Generate the full list of documents required for completion in a
  given file. Previously, files that included the file were missing.
parent 62dd4938
......@@ -92,6 +92,19 @@ Interpreter::ObjectValue *Bind::findQmlObject(AST::Node *node) const
return _qmlObjects.value(node);
}
bool Bind::usesQmlPrototype(ObjectValue *prototype,
Context *context) const
{
foreach (ObjectValue *object, _qmlObjects.values()) {
const ObjectValue *resolvedPrototype = object->prototype(context);
if (resolvedPrototype == prototype)
return true;
}
return false;
}
ObjectValue *Bind::switchObjectValue(ObjectValue *newObjectValue)
{
ObjectValue *oldObjectValue = _currentObjectValue;
......@@ -195,8 +208,8 @@ bool Bind::visit(AST::UiProgram *)
bool Bind::visit(AST::Program *)
{
_currentObjectValue = _engine.globalObject();
_rootObjectValue = _engine.globalObject();
_currentObjectValue = _engine.newObject(/*prototype =*/ 0);
_rootObjectValue = _currentObjectValue;
return true;
}
......
......@@ -59,6 +59,8 @@ public:
Interpreter::ObjectValue *rootObjectValue() const;
Interpreter::ObjectValue *findQmlObject(AST::Node *node) const;
bool usesQmlPrototype(Interpreter::ObjectValue *prototype,
Interpreter::Context *context) const;
static QString toString(AST::UiQualifiedId *qualifiedId, QChar delimiter = QChar('.'));
......
......@@ -31,28 +31,41 @@ Interpreter::Engine *Link::engine()
void Link::scopeChainAt(Document::Ptr doc, Node *currentObject)
{
// ### TODO: This object ought to contain the global namespace additions by QML.
_context->pushScope(engine()->globalObject());
if (! doc)
return;
if (doc->qmlProgram() != 0)
_context->setLookupMode(Context::QmlLookup);
Bind *bind = doc->bind();
QStringList linkedDocs; // to prevent cycles
// Build the scope chain.
if (doc->qmlProgram()) {
_context->setLookupMode(Context::QmlLookup);
ObjectValue *scopeObject = 0;
if (UiObjectDefinition *definition = cast<UiObjectDefinition *>(currentObject))
scopeObject = bind->findQmlObject(definition);
else if (UiObjectBinding *binding = cast<UiObjectBinding *>(currentObject))
scopeObject = bind->findQmlObject(binding);
pushScopeChainForComponent(doc, &linkedDocs, scopeObject);
if (const ObjectValue *typeEnvironment = _context->typeEnvironment(doc.data()))
_context->pushScope(typeEnvironment);
} else {
// add scope chains for all components that source this document
foreach (Document::Ptr otherDoc, _docs) {
if (otherDoc->bind()->includedScripts().contains(doc->fileName()))
pushScopeChainForComponent(otherDoc, &linkedDocs);
}
// ### FIXME: May want to link to instantiating components from here.
// ### TODO: Which type environment do scripts see?
ObjectValue *scopeObject = 0;
if (UiObjectDefinition *definition = cast<UiObjectDefinition *>(currentObject))
scopeObject = bind->findQmlObject(definition);
else if (UiObjectBinding *binding = cast<UiObjectBinding *>(currentObject))
scopeObject = bind->findQmlObject(binding);
else if (FunctionDeclaration *fun = cast<FunctionDeclaration *>(currentObject)) {
_context->pushScope(bind->rootObjectValue());
}
if (FunctionDeclaration *fun = cast<FunctionDeclaration *>(currentObject)) {
ObjectValue *activation = engine()->newObject(/*prototype = */ 0);
for (FormalParameterList *it = fun->formals; it; it = it->next) {
if (it->name)
......@@ -60,15 +73,35 @@ void Link::scopeChainAt(Document::Ptr doc, Node *currentObject)
}
_context->pushScope(activation);
}
}
void Link::pushScopeChainForComponent(Document::Ptr doc, QStringList *linkedDocs,
ObjectValue *scopeObject)
{
if (!doc->qmlProgram())
return;
if (scopeObject) {
if (bind->rootObjectValue())
_context->pushScope(bind->rootObjectValue());
linkedDocs->append(doc->fileName());
if (scopeObject != bind->rootObjectValue())
_context->pushScope(scopeObject);
Bind *bind = doc->bind();
// add scopes for all components instantiating this one
foreach (Document::Ptr otherDoc, _docs) {
if (linkedDocs->contains(otherDoc->fileName()))
continue;
if (otherDoc->bind()->usesQmlPrototype(bind->rootObjectValue(), _context)) {
// ### TODO: Depth-first insertion doesn't give us correct name shadowing.
pushScopeChainForComponent(otherDoc, linkedDocs);
}
}
if (bind->rootObjectValue())
_context->pushScope(bind->rootObjectValue());
if (scopeObject && scopeObject != bind->rootObjectValue())
_context->pushScope(scopeObject);
const QStringList &includedScripts = bind->includedScripts();
for (int index = includedScripts.size() - 1; index != -1; --index) {
const QString &scriptFile = includedScripts.at(index);
......@@ -82,9 +115,6 @@ void Link::scopeChainAt(Document::Ptr doc, Node *currentObject)
_context->pushScope(bind->functionEnvironment());
_context->pushScope(bind->idEnvironment());
if (const ObjectValue *typeEnvironment = _context->typeEnvironment(doc.data()))
_context->pushScope(typeEnvironment);
}
void Link::linkImports()
......@@ -251,40 +281,85 @@ UiQualifiedId *Link::qualifiedTypeNameId(Node *node)
return 0;
}
static uint qHash(Document::Ptr doc) {
return qHash(doc.data());
}
QList<Document::Ptr> Link::reachableDocuments(Document::Ptr startDoc, const Snapshot &snapshot)
{
QList<Document::Ptr> docs;
QSet<Document::Ptr> docs;
if (! startDoc)
return docs;
QSet<QString> processed;
QStringList todo;
return docs.values();
QMultiHash<QString, Document::Ptr> documentByPath;
foreach (Document::Ptr doc, snapshot)
documentByPath.insert(doc->path(), doc);
todo.append(startDoc->path());
// Find the reachable documents.
while (! todo.isEmpty()) {
const QString path = todo.takeFirst();
// ### TODO: This doesn't scale well. Maybe just use the whole snapshot?
// Find all documents that (indirectly) include startDoc
{
QList<Document::Ptr> todo;
todo += startDoc;
if (processed.contains(path))
continue;
processed.insert(path);
while (! todo.isEmpty()) {
Document::Ptr doc = todo.takeFirst();
QStringList localImports;
foreach (Document::Ptr doc, documentByPath.values(path)) {
docs += doc;
localImports += doc->bind()->localImports();
Snapshot::const_iterator it, end = snapshot.end();
for (it = snapshot.begin(); it != end; ++it) {
Document::Ptr otherDoc = *it;
if (docs.contains(otherDoc))
continue;
QStringList localImports = otherDoc->bind()->localImports();
if (localImports.contains(doc->fileName())
|| localImports.contains(doc->path())
|| otherDoc->bind()->includedScripts().contains(doc->fileName())
) {
todo += otherDoc;
}
}
}
}
localImports.removeDuplicates();
todo += localImports;
// Find all documents that are included by these (even if indirectly).
{
QSet<QString> processed;
QStringList todo;
foreach (Document::Ptr doc, docs)
todo.append(doc->fileName());
while (! todo.isEmpty()) {
QString path = todo.takeFirst();
if (processed.contains(path))
continue;
processed.insert(path);
if (Document::Ptr doc = snapshot.document(path)) {
docs += doc;
if (doc->qmlProgram())
path = doc->path();
else
continue;
}
QStringList localImports;
foreach (Document::Ptr doc, documentByPath.values(path)) {
if (doc->qmlProgram()) {
docs += doc;
localImports += doc->bind()->localImports();
localImports += doc->bind()->includedScripts();
}
}
localImports.removeDuplicates();
todo += localImports;
}
}
return docs;
return docs.values();
}
......@@ -28,6 +28,9 @@ public:
private:
Interpreter::Engine *engine();
void pushScopeChainForComponent(Document::Ptr doc, QStringList *linkedDocs,
Interpreter::ObjectValue *scopeObject = 0);
static QList<Document::Ptr> reachableDocuments(Document::Ptr startDoc, const Snapshot &snapshot);
static AST::UiQualifiedId *qualifiedTypeNameId(AST::Node *node);
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment