diff --git a/src/libs/qmljs/qmljscheck.cpp b/src/libs/qmljs/qmljscheck.cpp index 7a794cb03baa7e56a61e577e3793fb1b0c86846c..33392da27c454d075ccfb986f6e4aeacf1b530f0 100644 --- a/src/libs/qmljs/qmljscheck.cpp +++ b/src/libs/qmljs/qmljscheck.cpp @@ -61,7 +61,6 @@ Check::Check(Document::Ptr doc, const Snapshot &snapshot) , _snapshot(snapshot) , _context(&_engine) , _link(&_context, doc, snapshot) - , _extraScope(0) , _allowAnyProperty(false) { } @@ -118,10 +117,12 @@ void Check::visitQmlObject(Node *ast, UiQualifiedId *typeId, _allowAnyProperty = true; // suppress subsequent "unknown property" errors } - const ObjectValue *oldScopeObject = _context.qmlScopeObject(); - const ObjectValue *oldExtraScope = _extraScope; + QList<const ObjectValue *> oldScopeObjects = _context.scopeChain().qmlScopeObjects; + + _context.scopeChain().qmlScopeObjects.clear(); const ObjectValue *scopeObject = _doc->bind()->findQmlObject(ast); - _context.setQmlScopeObject(scopeObject); + _context.scopeChain().qmlScopeObjects += scopeObject; + _context.scopeChain().update(); #ifndef NO_DECLARATIVE_BACKEND // check if the object has a Qt.ListElement ancestor @@ -159,7 +160,7 @@ void Check::visitQmlObject(Node *ast, UiQualifiedId *typeId, const Value *targetValue = evaluator(expStmt->expression); if (const ObjectValue *target = value_cast<const ObjectValue *>(targetValue)) { - _extraScope = target; + _context.scopeChain().qmlScopeObjects.prepend(target); } else { _allowAnyProperty = true; } @@ -172,8 +173,7 @@ void Check::visitQmlObject(Node *ast, UiQualifiedId *typeId, Node::accept(initializer, this); - _context.setQmlScopeObject(oldScopeObject); - _extraScope = oldExtraScope; + _context.scopeChain().qmlScopeObjects = oldScopeObjects; _allowAnyProperty = oldAllowAnyProperty; } @@ -235,7 +235,7 @@ const Value *Check::checkScopeObjectMember(const UiQualifiedId *id) if (_allowAnyProperty) return 0; - const ObjectValue *scopeObject = _context.qmlScopeObject(); + QList<const ObjectValue *> scopeObjects = _context.scopeChain().qmlScopeObjects; if (! id) return 0; // ### error? @@ -249,16 +249,19 @@ const Value *Check::checkScopeObjectMember(const UiQualifiedId *id) bool isAttachedProperty = false; if (! propertyName.isEmpty() && propertyName[0].isUpper()) { isAttachedProperty = true; - scopeObject = _context.typeEnvironment(_doc.data()); + scopeObjects += _context.scopeChain().qmlTypes; } - if (! scopeObject) + if (scopeObjects.isEmpty()) return 0; // global lookup for first part of id - const Value *value = scopeObject->lookupMember(propertyName, &_context); - if (_extraScope && !value) - value = _extraScope->lookupMember(propertyName, &_context); + const Value *value = 0; + for (int i = scopeObjects.size() - 1; i >= 0; --i) { + value = scopeObjects[i]->lookupMember(propertyName, &_context); + if (value) + break; + } if (!value) { error(id->identifierToken, tr(Messages::invalid_property_name).arg(propertyName)); diff --git a/src/libs/qmljs/qmljscheck.h b/src/libs/qmljs/qmljscheck.h index 56ec5c0c83cae93e2abb6d0a83456f08e6355768..8810865f46ea4260f5f5a1ca359111135621e223 100644 --- a/src/libs/qmljs/qmljscheck.h +++ b/src/libs/qmljs/qmljscheck.h @@ -75,7 +75,6 @@ private: QList<DiagnosticMessage> _messages; - const Interpreter::ObjectValue *_extraScope; bool _allowAnyProperty; }; diff --git a/src/libs/qmljs/qmljsinterpreter.cpp b/src/libs/qmljs/qmljsinterpreter.cpp index 20b1f47dbe7545e28318a317c7e0fd5816eb22a7..f7dc0c5393ddbcadaed2aa8aa6f584919f711ab4 100644 --- a/src/libs/qmljs/qmljsinterpreter.cpp +++ b/src/libs/qmljs/qmljsinterpreter.cpp @@ -725,6 +725,45 @@ void StringValue::accept(ValueVisitor *visitor) const } +ScopeChain::ScopeChain() + : globalScope(0) + , qmlTypes(0) +{ +} + +void ScopeChain::QmlComponentChain::add(QList<const ObjectValue *> *list) const +{ + foreach (QmlComponentChain *parent, instantiatingComponents) + parent->add(list); + + if (rootObject) + list->append(rootObject); + list->append(functionScopes); + if (ids) + list->append(ids); +} + +void ScopeChain::update() +{ + all.clear(); + + all += globalScope; + + foreach (QmlComponentChain *parent, qmlComponentScope.instantiatingComponents) + parent->add(&all); + + if (qmlComponentScope.rootObject) + all += qmlComponentScope.rootObject; + all += qmlScopeObjects; + all += qmlComponentScope.functionScopes; + if (qmlComponentScope.ids) + all += qmlComponentScope.ids; + if (qmlTypes) + all += qmlTypes; + all += jsScopes; +} + + Context::Context(Engine *engine) : _engine(engine), _lookupMode(JSLookup), @@ -748,7 +787,12 @@ Engine *Context::engine() const return _engine; } -Context::ScopeChain Context::scopeChain() const +const ScopeChain &Context::scopeChain() const +{ + return _scopeChain; +} + +ScopeChain &Context::scopeChain() { return _scopeChain; } @@ -773,54 +817,11 @@ void Context::setTypeEnvironment(const QmlJS::Document *doc, const ObjectValue * _typeEnvironments[doc] = typeEnvironment; } -void Context::pushScope(const ObjectValue *object) -{ - if (object != 0) - _scopeChain.append(object); -} - -void Context::popScope() -{ - _scopeChain.removeLast(); - if (_scopeChain.length() <= _qmlScopeObjectIndex) - _qmlScopeObjectSet = false; -} - -// Marks this to be the location where a scope object can be inserted. -void Context::markQmlScopeObject() -{ - _qmlScopeObjectIndex = _scopeChain.length(); -} - -// Sets or inserts the scope object if scopeObject != 0, removes it otherwise. -void Context::setQmlScopeObject(const ObjectValue *scopeObject) -{ - if (_qmlScopeObjectSet) { - if (scopeObject == 0) { - _scopeChain.removeAt(_qmlScopeObjectIndex); - _qmlScopeObjectSet = false; - } else { - _scopeChain[_qmlScopeObjectIndex] = scopeObject; - } - } else if (scopeObject != 0 && _scopeChain.length() >= _qmlScopeObjectIndex) { - _scopeChain.insert(_qmlScopeObjectIndex, scopeObject); - _qmlScopeObjectSet = true; - } -} - -// Gets the scope object, if set. Returns 0 otherwise. -const ObjectValue *Context::qmlScopeObject() const -{ - if (!_qmlScopeObjectSet) - return 0; - else - return _scopeChain[_qmlScopeObjectIndex]; -} - const Value *Context::lookup(const QString &name) { - for (int index = _scopeChain.size() - 1; index != -1; --index) { - const ObjectValue *scope = _scopeChain.at(index); + QList<const ObjectValue *> scopes = _scopeChain.all; + for (int index = scopes.size() - 1; index != -1; --index) { + const ObjectValue *scope = scopes.at(index); if (const Value *member = scope->lookupMember(name, this)) { if (_lookupMode == JSLookup || ! dynamic_cast<const ASTVariableReference *>(member)) { diff --git a/src/libs/qmljs/qmljsinterpreter.h b/src/libs/qmljs/qmljsinterpreter.h index a1fac700ec85f2dcdc740948d51709b00687f88e..f91add8847e5c0bf5a50584b7b538cb6e926e65b 100644 --- a/src/libs/qmljs/qmljsinterpreter.h +++ b/src/libs/qmljs/qmljsinterpreter.h @@ -222,11 +222,39 @@ public: virtual bool processGeneratedSlot(const QString &name, const Value *value); }; -class QMLJS_EXPORT Context +class QMLJS_EXPORT ScopeChain { public: - typedef QList<const ObjectValue *> ScopeChain; + ScopeChain(); + + struct QmlComponentChain + { + QmlComponentChain() + : rootObject(0), ids(0) + {} + QList<QmlComponentChain *> instantiatingComponents; + const ObjectValue *rootObject; + QList<const ObjectValue *> functionScopes; + const ObjectValue *ids; + + void add(QList<const ObjectValue *> *list) const; + }; + + const ObjectValue *globalScope; + QmlComponentChain qmlComponentScope; + QList<const ObjectValue *> qmlScopeObjects; + const ObjectValue *qmlTypes; + QList<const ObjectValue *> jsScopes; + + // rebuilds the flat list of all scopes + void update(); + QList<const ObjectValue *> all; +}; + +class QMLJS_EXPORT Context +{ +public: enum LookupMode { JSLookup, QmlLookup @@ -239,7 +267,8 @@ public: void build(AST::Node *node, const Document::Ptr doc, const Snapshot &snapshot); Engine *engine() const; - ScopeChain scopeChain() const; + const ScopeChain &scopeChain() const; + ScopeChain &scopeChain(); LookupMode lookupMode() const; void setLookupMode(LookupMode lookupMode); @@ -247,13 +276,6 @@ public: const ObjectValue *typeEnvironment(const Document *doc) const; void setTypeEnvironment(const Document *doc, const ObjectValue *typeEnvironment); - void pushScope(const ObjectValue *object); - void popScope(); - - void markQmlScopeObject(); - void setQmlScopeObject(const ObjectValue *scopeObject); - const ObjectValue *qmlScopeObject() const; - const Value *lookup(const QString &name); const ObjectValue *lookupType(const Document *doc, AST::UiQualifiedId *qmlTypeName); diff --git a/src/libs/qmljs/qmljslink.cpp b/src/libs/qmljs/qmljslink.cpp index f67c428d37c9f739f7ca7688da59db01ec80cb01..6080ff1168c15aec681043c3d63705a79983ddc3 100644 --- a/src/libs/qmljs/qmljslink.cpp +++ b/src/libs/qmljs/qmljslink.cpp @@ -31,41 +31,50 @@ Interpreter::Engine *Link::engine() void Link::scopeChainAt(Document::Ptr doc, Node *currentObject) { + ScopeChain &scopeChain = _context->scopeChain(); + // ### TODO: This object ought to contain the global namespace additions by QML. - _context->pushScope(engine()->globalObject()); + scopeChain.globalScope = engine()->globalObject(); if (! doc) return; Bind *bind = doc->bind(); - QStringList linkedDocs; // to prevent cycles + QHash<Document *, ScopeChain::QmlComponentChain *> componentScopes; if (doc->qmlProgram()) { _context->setLookupMode(Context::QmlLookup); + makeComponentChain(doc, &scopeChain.qmlComponentScope, &componentScopes); + 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 (scopeObject && scopeObject != scopeChain.qmlComponentScope.rootObject) + scopeChain.qmlScopeObjects += scopeObject; if (const ObjectValue *typeEnvironment = _context->typeEnvironment(doc.data())) - _context->pushScope(typeEnvironment); + scopeChain.qmlTypes = typeEnvironment; } else { // the global scope of a js file does not see the instantiating component if (currentObject != 0) { // 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); + if (otherDoc->bind()->includedScripts().contains(doc->fileName())) { + ScopeChain::QmlComponentChain *component = new ScopeChain::QmlComponentChain; + componentScopes.insert(otherDoc.data(), component); + scopeChain.qmlComponentScope.instantiatingComponents += component; + makeComponentChain(otherDoc, component, &componentScopes); + } } // ### TODO: Which type environment do scripts see? } - _context->pushScope(bind->rootObjectValue()); + scopeChain.jsScopes += bind->rootObjectValue(); } if (FunctionDeclaration *fun = cast<FunctionDeclaration *>(currentObject)) { @@ -74,53 +83,55 @@ void Link::scopeChainAt(Document::Ptr doc, Node *currentObject) if (it->name) activation->setProperty(it->name->asString(), engine()->undefinedValue()); } - _context->pushScope(activation); + scopeChain.jsScopes += activation; } + + scopeChain.update(); } -void Link::pushScopeChainForComponent(Document::Ptr doc, QStringList *linkedDocs, - ObjectValue *scopeObject) +void Link::makeComponentChain( + Document::Ptr doc, + ScopeChain::QmlComponentChain *target, + QHash<Document *, ScopeChain::QmlComponentChain *> *components) { if (!doc->qmlProgram()) return; - linkedDocs->append(doc->fileName()); - Bind *bind = doc->bind(); // add scopes for all components instantiating this one foreach (Document::Ptr otherDoc, _docs) { - if (linkedDocs->contains(otherDoc->fileName())) + if (otherDoc == doc) continue; - if (otherDoc->bind()->usesQmlPrototype(bind->rootObjectValue(), _context)) { - // ### TODO: Depth-first insertion doesn't give us correct name shadowing. - pushScopeChainForComponent(otherDoc, linkedDocs); + if (components->contains(otherDoc.data())) { + target->instantiatingComponents += components->value(otherDoc.data()); + } else { + ScopeChain::QmlComponentChain *component = new ScopeChain::QmlComponentChain; + components->insert(otherDoc.data(), component); + target->instantiatingComponents += component; + + makeComponentChain(otherDoc, component, components); + } } } + // build this component scope if (bind->rootObjectValue()) - _context->pushScope(bind->rootObjectValue()); - - if (scopeObject) { - _context->markQmlScopeObject(); - if (scopeObject != bind->rootObjectValue()) - _context->setQmlScopeObject(scopeObject); - } + target->rootObject = bind->rootObjectValue(); const QStringList &includedScripts = bind->includedScripts(); for (int index = includedScripts.size() - 1; index != -1; --index) { const QString &scriptFile = includedScripts.at(index); if (Document::Ptr scriptDoc = _snapshot.document(scriptFile)) { - if (scriptDoc->jsProgram()) { - _context->pushScope(scriptDoc->bind()->rootObjectValue()); - } + if (scriptDoc->jsProgram()) + target->functionScopes += scriptDoc->bind()->rootObjectValue(); } } - _context->pushScope(bind->functionEnvironment()); - _context->pushScope(bind->idEnvironment()); + target->functionScopes += bind->functionEnvironment(); + target->ids = bind->idEnvironment(); } void Link::linkImports() diff --git a/src/libs/qmljs/qmljslink.h b/src/libs/qmljs/qmljslink.h index 936851dac54c0274e84a76baa6eb66604edc7b3e..ba767523e20d734e67aaf201fdbc600e465bddcd 100644 --- a/src/libs/qmljs/qmljslink.h +++ b/src/libs/qmljs/qmljslink.h @@ -28,8 +28,10 @@ public: private: Interpreter::Engine *engine(); - void pushScopeChainForComponent(Document::Ptr doc, QStringList *linkedDocs, - Interpreter::ObjectValue *scopeObject = 0); + void makeComponentChain( + Document::Ptr doc, + Interpreter::ScopeChain::QmlComponentChain *target, + QHash<Document *, Interpreter::ScopeChain::QmlComponentChain *> *components); static QList<Document::Ptr> reachableDocuments(Document::Ptr startDoc, const Snapshot &snapshot); static AST::UiQualifiedId *qualifiedTypeNameId(AST::Node *node); diff --git a/src/plugins/qmljseditor/qmljscodecompletion.cpp b/src/plugins/qmljseditor/qmljscodecompletion.cpp index f8905f689b1b6c507e704f71956d9c379c20f28b..5d00f7a65ac7bb7842059e8150c52a7026d164a8 100644 --- a/src/plugins/qmljseditor/qmljscodecompletion.cpp +++ b/src/plugins/qmljseditor/qmljscodecompletion.cpp @@ -189,7 +189,7 @@ public: _properties.clear(); _currentObject = 0; - foreach (const Interpreter::ObjectValue *scope, _context->scopeChain()) + foreach (const Interpreter::ObjectValue *scope, _context->scopeChain().all) enumerateProperties(scope); return _properties;