Commit c2898973 authored by Christian Kamm's avatar Christian Kamm

Make QmlJS scope building more flexible.

Instead of only maintaining a flat list of scopes, actually store the
global, component chain, root object, scope object, function, id and js
scopes separately.
parent b0e7b15b
......@@ -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));
......
......@@ -75,7 +75,6 @@ private:
QList<DiagnosticMessage> _messages;
const Interpreter::ObjectValue *_extraScope;
bool _allowAnyProperty;
};
......
......@@ -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)) {
......
......@@ -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);
......
......@@ -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()
......
......@@ -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);
......
......@@ -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;
......
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