Commit 87e04df2 authored by Christian Kamm's avatar Christian Kamm
Browse files

QmlJS: Refactor LookupContext creation for speed.

* If possible, create LookupContexts through SemanticInfo; it caches the
  linked Context and will be faster.
* Contexts now own their Engine.

Reviewed-by: Lasse Holmstedt
parent af46c3d9
......@@ -157,10 +157,9 @@ public:
Check::Check(Document::Ptr doc, const Snapshot &snapshot, const QStringList &importPaths)
: _doc(doc)
, _snapshot(snapshot)
, _context(&_engine)
, _link(&_context, doc, snapshot, importPaths)
, _scopeBuilder(doc, &_context)
, _ignoreTypeErrors(_context.documentImportsPlugins(_doc.data()))
, _ignoreTypeErrors(false)
{
}
......
......@@ -71,7 +71,6 @@ private:
Document::Ptr _doc;
Snapshot _snapshot;
Interpreter::Engine _engine;
Interpreter::Context _context;
Link _link;
ScopeBuilder _scopeBuilder;
......
......@@ -33,7 +33,7 @@
#include <QObject>
#include "qmljs_global.h"
#include <qmljs/parser/qmljsastfwd_p.h>
#include <qmljs/qmljsdocument.h>
#include <qmljs/qmljslookupcontext.h>
namespace TextEditor {
......@@ -52,9 +52,9 @@ class QMLJS_EXPORT IContextPane : public QObject
public:
IContextPane(QObject *parent = 0) : QObject(parent) {}
virtual ~IContextPane() {}
virtual void apply(TextEditor::BaseTextEditorEditable *editor, Document::Ptr doc, const QmlJS::Snapshot &snapshot, AST::Node *node, bool update, bool force = false) = 0;
virtual void apply(TextEditor::BaseTextEditorEditable *editor, LookupContext::Ptr lookupContext, AST::Node *node, bool update, bool force = false) = 0;
virtual void setEnabled(bool) = 0;
virtual bool isAvailable(TextEditor::BaseTextEditorEditable *editor, Document::Ptr doc, const QmlJS::Snapshot &snapshot, AST::Node *node) = 0;
virtual bool isAvailable(TextEditor::BaseTextEditorEditable *editor, LookupContext::Ptr lookupContext, AST::Node *node) = 0;
virtual QWidget* widget() = 0;
signals:
void closed();
......
......@@ -1419,8 +1419,8 @@ QList<const ObjectValue *> ScopeChain::all() const
}
Context::Context(Engine *engine)
: _engine(engine),
Context::Context()
: _engine(new Engine),
_qmlScopeObjectIndex(-1),
_qmlScopeObjectSet(false)
{
......@@ -1430,9 +1430,10 @@ Context::~Context()
{
}
// the engine is only guaranteed to live as long as the context
Engine *Context::engine() const
{
return _engine;
return _engine.data();
}
const ScopeChain &Context::scopeChain() const
......@@ -1530,7 +1531,7 @@ void Context::setProperty(const ObjectValue *object, const QString &name, const
_properties[object].insert(name, value);
}
QString Context::defaultPropertyName(const ObjectValue *object)
QString Context::defaultPropertyName(const ObjectValue *object) const
{
for (const ObjectValue *o = object; o; o = o->prototype(this)) {
if (const ASTObjectValue *astObjValue = dynamic_cast<const ASTObjectValue *>(o)) {
......@@ -1544,16 +1545,6 @@ QString Context::defaultPropertyName(const ObjectValue *object)
return QString();
}
bool Context::documentImportsPlugins(const QmlJS::Document *doc) const
{
return _documentsImportingPlugins.contains(doc->fileName());
}
void Context::setDocumentImportsPlugins(const QmlJS::Document *doc)
{
_documentsImportingPlugins.insert(doc->fileName());
}
Reference::Reference(Engine *engine)
: _engine(engine)
{
......
......@@ -271,7 +271,7 @@ private:
class QMLJS_EXPORT Context
{
public:
Context(Engine *engine);
Context();
~Context();
Engine *engine() const;
......@@ -289,18 +289,14 @@ public:
const Value *property(const ObjectValue *object, const QString &name) const;
void setProperty(const ObjectValue *object, const QString &name, const Value *value);
QString defaultPropertyName(const ObjectValue *object);
bool documentImportsPlugins(const Document *doc) const;
void setDocumentImportsPlugins(const Document *doc);
QString defaultPropertyName(const ObjectValue *object) const;
private:
typedef QHash<QString, const Value *> Properties;
Engine *_engine;
QSharedPointer<Engine> _engine;
QHash<const ObjectValue *, Properties> _properties;
QHash<QString, const ObjectValue *> _typeEnvironments;
QSet<QString> _documentsImportingPlugins;
ScopeChain _scopeChain;
int _qmlScopeObjectIndex;
bool _qmlScopeObjectSet;
......@@ -628,8 +624,7 @@ protected:
class QMLJS_EXPORT Engine
{
Engine(const Engine &other);
void operator = (const Engine &other);
Q_DISABLE_COPY(Engine)
public:
Engine();
......
......@@ -40,20 +40,30 @@ class QmlJS::LookupContextData
{
public:
LookupContextData(Document::Ptr doc, const Snapshot &snapshot, const QList<AST::Node *> &path)
: context(&interp),
: doc(doc),
snapshot(snapshot)
{
// since we keep the document and snapshot around, we don't need to keep the Link instance
Link link(&context, doc, snapshot, ModelManagerInterface::instance()->importPaths());
ScopeBuilder scopeBuilder(doc, &context);
scopeBuilder.push(path);
}
LookupContextData(Document::Ptr doc, const Snapshot &snapshot,
const Interpreter::Context &linkedContextWithoutScope,
const QList<AST::Node *> &path)
: context(linkedContextWithoutScope),
doc(doc),
snapshot(snapshot),
link(&context, doc, snapshot, ModelManagerInterface::instance()->importPaths())
snapshot(snapshot)
{
ScopeBuilder scopeBuilder(doc, &context);
scopeBuilder.push(path);
}
Interpreter::Engine interp;
Interpreter::Context context;
Document::Ptr doc;
Snapshot snapshot;
Link link;
};
LookupContext::LookupContext(Document::Ptr doc, const Snapshot &snapshot, const QList<AST::Node *> &path)
......@@ -61,6 +71,13 @@ LookupContext::LookupContext(Document::Ptr doc, const Snapshot &snapshot, const
{
}
LookupContext::LookupContext(const Document::Ptr doc, const Snapshot &snapshot,
const Interpreter::Context &linkedContextWithoutScope,
const QList<AST::Node *> &path)
: d(new LookupContextData(doc, snapshot, linkedContextWithoutScope, path))
{
}
LookupContext::~LookupContext()
{
}
......@@ -71,17 +88,37 @@ LookupContext::Ptr LookupContext::create(Document::Ptr doc, const Snapshot &snap
return ptr;
}
LookupContext::Ptr LookupContext::create(const Document::Ptr doc, const Snapshot &snapshot,
const Interpreter::Context &linkedContextWithoutScope,
const QList<AST::Node *> &path)
{
Ptr ptr(new LookupContext(doc, snapshot, linkedContextWithoutScope, path));
return ptr;
}
const Interpreter::Value *LookupContext::evaluate(AST::Node *node) const
{
Evaluate check(&d->context);
return check(node);
}
Document::Ptr LookupContext::document() const
{
return d->doc;
}
Snapshot LookupContext::snapshot() const
{
return d->snapshot;
}
// the engine is only guaranteed to live as long as the LookupContext
Interpreter::Engine *LookupContext::engine() const
{
return &d->interp;
return d->context.engine();
}
// the context is only guaranteed to live as long as the LookupContext
const Interpreter::Context *LookupContext::context() const
{
return &d->context;
......
......@@ -31,6 +31,7 @@
#define QMLJSLOOKUPCONTEXT_H
#include "qmljsdocument.h"
#include "qmljsinterpreter.h"
#include "parser/qmljsastfwd_p.h"
#include <QtCore/QSharedPointer>
......@@ -42,23 +43,34 @@ class LookupContextData;
namespace Interpreter {
class Value;
class Engine;
class Context;
}
class QMLJS_EXPORT LookupContext
{
Q_DISABLE_COPY(LookupContext)
LookupContext(const Document::Ptr doc, const Snapshot &snapshot, const QList<AST::Node *> &path);
LookupContext(const Document::Ptr doc, const Snapshot &snapshot,
const Interpreter::Context &linkedContextWithoutScope,
const QList<AST::Node *> &path);
public:
~LookupContext();
typedef QSharedPointer<LookupContext> Ptr;
// consider using SemanticInfo::lookupContext instead, it's faster
static Ptr create(const Document::Ptr doc, const Snapshot &snapshot,
const QList<AST::Node *> &path);
static Ptr create(const Document::Ptr doc, const Snapshot &snapshot,
const Interpreter::Context &linkedContextWithoutScope,
const QList<AST::Node *> &path);
const Interpreter::Value *evaluate(AST::Node *node) const;
Document::Ptr document() const;
Snapshot snapshot() const;
Interpreter::Engine *engine() const;
const Interpreter::Context *context() const;
......
......@@ -187,15 +187,14 @@ public:
const QStringList importPaths)
: m_snapshot(snapshot)
, m_doc(doc)
, m_engine(new Interpreter::Engine)
, m_context(new Interpreter::Context(m_engine))
, m_context(new Interpreter::Context)
, m_link(m_context, doc, snapshot, importPaths)
, m_scopeBuilder(doc, m_context)
{
}
~ReadingContext()
{ delete m_context; delete m_engine; }
{ delete m_context; }
Document::Ptr doc() const
{ return m_doc; }
......@@ -328,11 +327,11 @@ public:
if (!value)
return false;
const Interpreter::ObjectValue *objectValue = value->asObjectValue();
if (objectValue && objectValue->prototype(m_context) == m_engine->arrayPrototype())
if (objectValue && objectValue->prototype(m_context) == m_context->engine()->arrayPrototype())
return true;
for (const Interpreter::ObjectValue *iter = containingObject; iter; iter = iter->prototype(m_context)) {
if (iter->property(name, m_context) == m_engine->arrayPrototype())
if (iter->property(name, m_context) == m_context->engine()->arrayPrototype())
return true;
if (const Interpreter::QmlObjectValue *qmlIter = dynamic_cast<const Interpreter::QmlObjectValue *>(iter)) {
if (qmlIter->isListProperty(name))
......@@ -451,7 +450,6 @@ public:
private:
Snapshot m_snapshot;
Document::Ptr m_doc;
Interpreter::Engine *m_engine;
Interpreter::Context *m_context;
Link m_link;
ScopeBuilder m_scopeBuilder;
......
......@@ -691,7 +691,7 @@ int CodeCompletion::startCompletion(TextEditor::ITextEditable *editor)
const QIcon keywordIcon = iconForColor(Qt::darkYellow);
const QList<AST::Node *> path = semanticInfo.astPath(editor->position());
LookupContext::Ptr lookupContext = LookupContext::create(document, snapshot, path);
LookupContext::Ptr lookupContext = semanticInfo.lookupContext(path);
const Interpreter::Context *context = lookupContext->context();
// Search for the operator that triggered the completion.
......
......@@ -40,6 +40,7 @@
#include <qmljs/qmljsdocument.h>
#include <qmljs/qmljsicontextpane.h>
#include <qmljs/qmljslookupcontext.h>
#include <qmljs/qmljslink.h>
#include <qmljs/parser/qmljsastvisitor_p.h>
#include <qmljs/parser/qmljsast_p.h>
#include <qmljs/parser/qmljsengine_p.h>
......@@ -553,6 +554,18 @@ QList<AST::Node *> SemanticInfo::astPath(int cursorPosition) const
return path;
}
LookupContext::Ptr SemanticInfo::lookupContext(const QList<QmlJS::AST::Node *> &path) const
{
// create and link context if necessary
if (!m_context) {
Interpreter::Context *ctx = new Interpreter::Context;
Link link(ctx, document, snapshot, ModelManagerInterface::instance()->importPaths());
m_context = QSharedPointer<const QmlJS::Interpreter::Context>(ctx);
}
return LookupContext::create(document, snapshot, *m_context, path);
}
static bool importContainsCursor(UiImport *importAst, unsigned cursorPosition)
{
return cursorPosition >= importAst->firstSourceLocation().begin()
......@@ -962,9 +975,9 @@ void QmlJSTextEditor::updateCursorPositionNow()
Node *oldNode = m_semanticInfo.declaringMemberNoProperties(m_oldCursorPosition);
Node *newNode = m_semanticInfo.declaringMemberNoProperties(position());
if (oldNode != newNode && m_oldCursorPosition != -1)
m_contextPane->apply(editableInterface(), m_semanticInfo.document, m_semanticInfo.snapshot, newNode, false);
m_contextPane->apply(editableInterface(), m_semanticInfo.lookupContext(), newNode, false);
if (oldNode != newNode &&
m_contextPane->isAvailable(editableInterface(), m_semanticInfo.document, m_semanticInfo.snapshot, newNode) &&
m_contextPane->isAvailable(editableInterface(), m_semanticInfo.lookupContext(), newNode) &&
!m_contextPane->widget()->isVisible()) {
QList<TextEditor::Internal::RefactorMarker> markers;
if (UiObjectMember *m = newNode->uiObjectMemberCast()) {
......@@ -1042,22 +1055,19 @@ class SelectedElement: protected Visitor
unsigned m_cursorPositionStart;
unsigned m_cursorPositionEnd;
QList<UiObjectMember *> m_selectedMembers;
Document::Ptr m_document;
Snapshot m_snapshot;
LookupContext::Ptr m_lookupContext;
public:
SelectedElement()
: m_cursorPositionStart(0), m_cursorPositionEnd(0) {}
QList<UiObjectMember *> operator()(Document::Ptr doc, Snapshot snapshot, unsigned startPosition, unsigned endPosition)
QList<UiObjectMember *> operator()(LookupContext::Ptr lookupContext, unsigned startPosition, unsigned endPosition)
{
m_document = doc;
m_snapshot = snapshot;
m_lookupContext = lookupContext;
m_cursorPositionStart = startPosition;
m_cursorPositionEnd = endPosition;
m_selectedMembers.clear();
Node::accept(doc->qmlProgram(), this);
Node::accept(lookupContext->document()->qmlProgram(), this);
return m_selectedMembers;
}
......@@ -1092,14 +1102,10 @@ protected:
inline bool hasVisualPresentation(Node *ast)
{
Bind *bind = m_document->bind();
Bind *bind = m_lookupContext->document()->bind();
const Interpreter::ObjectValue *objValue = bind->findQmlObject(ast);
QStringList prototypes;
if (m_lookupContext.isNull()) {
m_lookupContext = LookupContext::create(m_document, m_snapshot, QList<Node*>());
}
while (objValue) {
prototypes.append(objValue->className());
objValue = objValue->prototype(m_lookupContext->context());
......@@ -1189,7 +1195,7 @@ void QmlJSTextEditor::setSelectedElements()
if (m_semanticInfo.document) {
SelectedElement selectedMembers;
QList<UiObjectMember *> members = selectedMembers(m_semanticInfo.document, m_semanticInfo.snapshot,
QList<UiObjectMember *> members = selectedMembers(m_semanticInfo.lookupContext(),
startPos, endPos);
if (!members.isEmpty()) {
foreach(UiObjectMember *m, members) {
......@@ -1376,7 +1382,7 @@ TextEditor::BaseTextEditor::Link QmlJSTextEditor::findLinkAt(const QTextCursor &
return Link();
}
LookupContext::Ptr lookupContext = LookupContext::create(semanticInfo.document, semanticInfo.snapshot, semanticInfo.astPath(cursorPosition));
LookupContext::Ptr lookupContext = semanticInfo.lookupContext(semanticInfo.astPath(cursorPosition));
const Interpreter::Value *value = lookupContext->evaluate(node);
QString fileName;
......@@ -1422,7 +1428,7 @@ void QmlJSTextEditor::showContextPane()
{
if (m_contextPane) {
Node *newNode = m_semanticInfo.declaringMemberNoProperties(position());
m_contextPane->apply(editableInterface(), m_semanticInfo.document, m_semanticInfo.snapshot, newNode, false, true);
m_contextPane->apply(editableInterface(), m_semanticInfo.lookupContext(), newNode, false, true);
m_oldCursorPosition = position();
QList<TextEditor::Internal::RefactorMarker> markers;
setRefactorMarkers(markers);
......@@ -1481,7 +1487,7 @@ void QmlJSTextEditor::wheelEvent(QWheelEvent *event)
BaseTextEditor::wheelEvent(event);
if (visible)
m_contextPane->apply(editableInterface(), m_semanticInfo.document, m_semanticInfo.snapshot, m_semanticInfo.declaringMemberNoProperties(position()), false, true);
m_contextPane->apply(editableInterface(), m_semanticInfo.lookupContext(), m_semanticInfo.declaringMemberNoProperties(position()), false, true);
}
void QmlJSTextEditor::resizeEvent(QResizeEvent *event)
......@@ -1723,7 +1729,7 @@ void QmlJSTextEditor::updateSemanticInfo(const SemanticInfo &semanticInfo)
if (m_contextPane) {
Node *newNode = m_semanticInfo.declaringMemberNoProperties(position());
if (newNode) {
m_contextPane->apply(editableInterface(), doc, m_semanticInfo.snapshot, newNode, true);
m_contextPane->apply(editableInterface(), m_semanticInfo.lookupContext(), newNode, true);
showTextMarker();
}
}
......@@ -1776,7 +1782,7 @@ bool QmlJSTextEditor::hideContextPane()
{
bool b = (m_contextPane) && m_contextPane->widget()->isVisible();
if (b)
m_contextPane->apply(editableInterface(), m_semanticInfo.document, m_semanticInfo.snapshot, 0, false);
m_contextPane->apply(editableInterface(), m_semanticInfo.lookupContext(), 0, false);
return b;
}
......
......@@ -34,6 +34,7 @@
#include <qmljs/qmljsdocument.h>
#include <qmljs/qmljsscanner.h>
#include <qmljs/qmljsinterpreter.h>
#include <texteditor/basetexteditor.h>
#include <QtCore/QWaitCondition>
......@@ -53,6 +54,7 @@ class ICore;
namespace QmlJS {
class ModelManagerInterface;
class IContextPane;
class LookupContext;
}
/*!
......@@ -129,6 +131,9 @@ public:
// Returns the list of nodes that enclose the given position.
QList<QmlJS::AST::Node *> astPath(int cursorPosition) const;
// Returns a context for the given path
QSharedPointer<QmlJS::LookupContext> lookupContext(const QList<QmlJS::AST::Node *> &path = QList<QmlJS::AST::Node *>()) const;
public: // attributes
QmlJS::Document::Ptr document;
QmlJS::Snapshot snapshot;
......@@ -138,6 +143,10 @@ public: // attributes
// these are in addition to the parser messages in the document
QList<QmlJS::DiagnosticMessage> semanticMessages;
private:
// created lazily
mutable QSharedPointer<const QmlJS::Interpreter::Context> m_context;
};
class SemanticHighlighter: public QThread
......
......@@ -111,9 +111,8 @@ void HoverHandler::identifyMatch(TextEditor::ITextEditor *editor, int pos)
if (astPath.isEmpty())
return;
const Snapshot &snapshot = semanticInfo.snapshot;
const Document::Ptr qmlDocument = semanticInfo.document;
LookupContext::Ptr lookupContext = LookupContext::create(qmlDocument, snapshot, astPath);
LookupContext::Ptr lookupContext = semanticInfo.lookupContext(astPath);
if (!matchColorItem(lookupContext, qmlDocument, astPath, pos))
handleOrdinaryMatch(lookupContext, semanticInfo.nodeUnderCursor(pos));
......
......@@ -42,10 +42,7 @@ QVariant QmlOutlineItem::data(int role) const
return QVariant();
QList<AST::Node *> astPath = m_outlineModel->m_semanticInfo.astPath(location.begin());
Document::Ptr document = m_outlineModel->m_semanticInfo.document;
Snapshot snapshot = m_outlineModel->m_semanticInfo.snapshot;
LookupContext::Ptr lookupContext = LookupContext::create(document, snapshot, astPath);
LookupContext::Ptr lookupContext = m_outlineModel->m_semanticInfo.lookupContext(astPath);
const Interpreter::Value *value = lookupContext->evaluate(uiQualifiedId);
return prettyPrint(value, lookupContext->context());
......@@ -331,10 +328,9 @@ void QmlOutlineModel::update(const SemanticInfo &semanticInfo)
// Set up lookup context once to do the element type lookup
//
// We're simplifying here by using the root context everywhere
// (empty node list). However, creating the LookupContext is quite expensive (about 3ms),
// and there is AFAIK no way to introduce new type names in a sub-context.
m_context = LookupContext::create(semanticInfo.document, semanticInfo.snapshot, QList<AST::Node*>());
// We're simplifying here by using the root context everywhere; should be
// ok since there is AFAIK no way to introduce new type names in a sub-context.
m_context = semanticInfo.lookupContext();
m_typeToIcon.clear();
m_itemToNode.clear();
m_itemToIdNode.clear();
......
......@@ -98,13 +98,16 @@ QuickToolBar::~QuickToolBar()
m_widget.clear();
}
void QuickToolBar::apply(TextEditor::BaseTextEditorEditable *editor, Document::Ptr doc, const QmlJS::Snapshot &snapshot, AST::Node *node, bool update, bool force)
void QuickToolBar::apply(TextEditor::BaseTextEditorEditable *editor, LookupContext::Ptr lookupContext, AST::Node *node, bool update, bool force)
{
if (!QuickToolBarSettings::get().enableContextPane && !force && !update) {
contextWidget()->hide();
return;
}
if (lookupContext.isNull())
return;
Document::Ptr doc = lookupContext->document();
if (doc.isNull())
return;
......@@ -113,7 +116,6 @@ void QuickToolBar::apply(TextEditor::BaseTextEditorEditable *editor, Document::P
m_blockWriting = true;
LookupContext::Ptr lookupContext = LookupContext::create(doc, snapshot, QList<Node*>());
const Interpreter::ObjectValue *scopeObject = doc->bind()->findQmlObject(node);
QStringList prototypes;
......@@ -211,43 +213,45 @@ void QuickToolBar::apply(TextEditor::BaseTextEditorEditable *editor, Document::P
}
bool QuickToolBar::isAvailable(TextEditor::BaseTextEditorEditable *, Document::Ptr doc, const QmlJS::Snapshot &snapshot, AST::Node *node)
bool QuickToolBar::isAvailable(TextEditor::BaseTextEditorEditable *, LookupContext::Ptr lookupContext, AST::Node *node)
{
if (lookupContext.isNull())
return false;
Document::Ptr doc = lookupContext->document();
if (doc.isNull())
return false;
if (!node)
return false;
LookupContext::Ptr lookupContext = LookupContext::create(doc, snapshot, QList<Node*>());
const Interpreter::ObjectValue *scopeObject = doc->bind()->findQmlObject(node);
const Interpreter::ObjectValue *scopeObject = doc->bind()->findQmlObject(node);
QStringList prototypes;
QStringList prototypes;
while (scopeObject) {
prototypes.append(scopeObject->className());
scopeObject = scopeObject->prototype(lookupContext->context());
}
while (scopeObject) {
prototypes.append(scopeObject->className());
scopeObject = scopeObject->prototype(lookupContext->context());
}
if (prototypes.contains("PropertyChanges")) {
const Interpreter::ObjectValue *targetObject = getPropertyChangesTarget(node, lookupContext);
prototypes.clear();
while (targetObject) {
prototypes.append(targetObject->className());
targetObject = targetObject->prototype(lookupContext->context());
}
if (prototypes.contains("PropertyChanges")) {
const Interpreter::ObjectValue *targetObject = getPropertyChangesTarget(node, lookupContext);
prototypes.clear();
while (targetObject) {