Qml-C++: Find C++ qmlRegisterType calls and populate QML code model.

Reviewed-by: Erik Verbruggen
This commit is contained in:
Christian Kamm
2010-12-03 10:13:15 +01:00
parent 16542241c9
commit 0194da7300
12 changed files with 359 additions and 2 deletions

View File

@@ -585,6 +585,122 @@ void Document::check(CheckMode mode)
} }
} }
class FindExposedQmlTypes : protected ASTVisitor
{
Document *_doc;
QList<Document::ExportedQmlType> _exportedTypes;
public:
FindExposedQmlTypes(Document *doc)
: ASTVisitor(doc->translationUnit())
, _doc(doc)
{}
QList<Document::ExportedQmlType> operator()()
{
_exportedTypes.clear();
accept(translationUnit()->ast());
return _exportedTypes;
}
protected:
virtual bool visit(CallAST *ast)
{
IdExpressionAST *idExp = ast->base_expression->asIdExpression();
if (!idExp || !idExp->name)
return false;
TemplateIdAST *templateId = idExp->name->asTemplateId();
if (!templateId || !templateId->identifier_token)
return false;
// check the name
const Identifier *templateIdentifier = translationUnit()->identifier(templateId->identifier_token);
if (!templateIdentifier)
return false;
const QString callName = QString::fromUtf8(templateIdentifier->chars());
if (callName != QLatin1String("qmlRegisterType"))
return false;
// must have a single typeid template argument
if (!templateId->template_argument_list || !templateId->template_argument_list->value
|| templateId->template_argument_list->next)
return false;
TypeIdAST *typeId = templateId->template_argument_list->value->asTypeId();
if (!typeId)
return false;
// must have four arguments
if (!ast->expression_list
|| !ast->expression_list->value || !ast->expression_list->next
|| !ast->expression_list->next->value || !ast->expression_list->next->next
|| !ast->expression_list->next->next->value || !ast->expression_list->next->next->next
|| !ast->expression_list->next->next->next->value
|| ast->expression_list->next->next->next->next)
return false;
// first and last arguments must be string literals
const StringLiteral *packageLit = 0;
const StringLiteral *nameLit = 0;
if (StringLiteralAST *packageAst = ast->expression_list->value->asStringLiteral())
packageLit = translationUnit()->stringLiteral(packageAst->literal_token);
if (StringLiteralAST *nameAst = ast->expression_list->next->next->next->value->asStringLiteral())
nameLit = translationUnit()->stringLiteral(nameAst->literal_token);
if (!nameLit) {
translationUnit()->warning(ast->expression_list->next->next->next->value->firstToken(),
"The type will only be available in Qt Creator's QML editors when the type name is a string literal");
return false;
}
// second and third argument must be integer literals
const NumericLiteral *majorLit = 0;
const NumericLiteral *minorLit = 0;
if (NumericLiteralAST *majorAst = ast->expression_list->next->value->asNumericLiteral())
majorLit = translationUnit()->numericLiteral(majorAst->literal_token);
if (NumericLiteralAST *minorAst = ast->expression_list->next->next->value->asNumericLiteral())
minorLit = translationUnit()->numericLiteral(minorAst->literal_token);
// build the descriptor
Document::ExportedQmlType exportedType;
exportedType.typeName = QString::fromUtf8(nameLit->chars(), nameLit->size());
if (packageLit && majorLit && minorLit && majorLit->isInt() && minorLit->isInt()) {
exportedType.packageName = QString::fromUtf8(packageLit->chars(), packageLit->size());
exportedType.majorVersion = QString::fromUtf8(majorLit->chars(), majorLit->size()).toInt();
exportedType.minorVersion = QString::fromUtf8(minorLit->chars(), minorLit->size()).toInt();
} else {
translationUnit()->warning(ast->base_expression->firstToken(),
"The package will only be available in Qt Creator's QML editors when the package name is a string literal and\n"
"the versions are integer literals. The type will be available globally.");
exportedType.packageName = QLatin1String("<default>");
}
// we want to do lookup later, so also store the surrounding scope
unsigned line, column;
translationUnit()->getTokenStartPosition(ast->firstToken(), &line, &column);
exportedType.scope = _doc->scopeAt(line, column);
// and the expression
const Token begin = translationUnit()->tokenAt(typeId->firstToken());
const Token last = translationUnit()->tokenAt(typeId->lastToken() - 1);
exportedType.typeExpression = _doc->source().mid(begin.begin(), last.end() - begin.begin());
_exportedTypes += exportedType;
return false;
}
};
void Document::findExposedQmlTypes()
{
if (! _translationUnit->ast())
return;
QByteArray token("qmlRegisterType");
if (! _translationUnit->control()->findIdentifier(token.constData(), token.size()))
return;
FindExposedQmlTypes finder(this);
_exportedQmlTypes = finder();
}
void Document::releaseSource() void Document::releaseSource()
{ {
_source.clear(); _source.clear();

View File

@@ -125,6 +125,8 @@ public:
void check(CheckMode mode = FullCheck); void check(CheckMode mode = FullCheck);
void findExposedQmlTypes();
void releaseSource(); void releaseSource();
void releaseTranslationUnit(); void releaseTranslationUnit();
@@ -318,6 +320,19 @@ public:
const MacroUse *findMacroUseAt(unsigned offset) const; const MacroUse *findMacroUseAt(unsigned offset) const;
const UndefinedMacroUse *findUndefinedMacroUseAt(unsigned offset) const; const UndefinedMacroUse *findUndefinedMacroUseAt(unsigned offset) const;
class ExportedQmlType {
public:
QString packageName;
QString typeName;
int majorVersion;
int minorVersion;
Scope *scope;
QString typeExpression;
};
QList<ExportedQmlType> exportedQmlTypes() const
{ return _exportedQmlTypes; }
private: private:
QString _fileName; QString _fileName;
Control *_control; Control *_control;
@@ -329,6 +344,7 @@ private:
QList<Block> _skippedBlocks; QList<Block> _skippedBlocks;
QList<MacroUse> _macroUses; QList<MacroUse> _macroUses;
QList<UndefinedMacroUse> _undefinedMacroUses; QList<UndefinedMacroUse> _undefinedMacroUses;
QList<ExportedQmlType> _exportedQmlTypes;
QByteArray _source; QByteArray _source;
QDateTime _lastModified; QDateTime _lastModified;
unsigned _revision; unsigned _revision;

View File

@@ -35,6 +35,7 @@
#define CPPMODELMANAGERINTERFACE_H #define CPPMODELMANAGERINTERFACE_H
#include <cplusplus/CppDocument.h> #include <cplusplus/CppDocument.h>
#include <languageutils/fakemetaobject.h>
#include <QtCore/QObject> #include <QtCore/QObject>
#include <QtCore/QHash> #include <QtCore/QHash>
#include <QtCore/QPointer> #include <QtCore/QPointer>
@@ -146,6 +147,8 @@ public:
virtual void findMacroUsages(const CPlusPlus::Macro &macro) = 0; virtual void findMacroUsages(const CPlusPlus::Macro &macro) = 0;
virtual QList<LanguageUtils::FakeMetaObject *> exportedQmlObjects() const = 0;
Q_SIGNALS: Q_SIGNALS:
void documentUpdated(CPlusPlus::Document::Ptr doc); void documentUpdated(CPlusPlus::Document::Ptr doc);

View File

@@ -1947,6 +1947,7 @@ const Value *Function::invoke(const Activation *activation) const
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
QList<const FakeMetaObject *> CppQmlTypesLoader::builtinObjects; QList<const FakeMetaObject *> CppQmlTypesLoader::builtinObjects;
QList<const FakeMetaObject *> CppQmlTypesLoader::cppObjects;
QStringList CppQmlTypesLoader::load(const QFileInfoList &xmlFiles) QStringList CppQmlTypesLoader::load(const QFileInfoList &xmlFiles)
{ {
@@ -2440,6 +2441,7 @@ Engine::Engine()
initializePrototypes(); initializePrototypes();
_cppQmlTypes.load(this, CppQmlTypesLoader::builtinObjects); _cppQmlTypes.load(this, CppQmlTypesLoader::builtinObjects);
_cppQmlTypes.load(this, CppQmlTypesLoader::cppObjects);
// the 'Qt' object is dumped even though it is not exported // the 'Qt' object is dumped even though it is not exported
// it contains useful information, in particular on enums - add the // it contains useful information, in particular on enums - add the

View File

@@ -593,6 +593,7 @@ public:
/** \return an empty list when successful, error messages otherwise. */ /** \return an empty list when successful, error messages otherwise. */
static QStringList load(const QFileInfoList &xmlFiles); static QStringList load(const QFileInfoList &xmlFiles);
static QList<const LanguageUtils::FakeMetaObject *> builtinObjects; static QList<const LanguageUtils::FakeMetaObject *> builtinObjects;
static QList<const LanguageUtils::FakeMetaObject *> cppObjects;
// parses the xml string and fills the newObjects map // parses the xml string and fills the newObjects map
static QString parseQmlTypeXml(const QByteArray &xml, static QString parseQmlTypeXml(const QByteArray &xml,

View File

@@ -162,9 +162,26 @@ void Link::populateImportedTypes(TypeEnvironment *typeEnv, Document::Ptr doc)
{ {
Q_D(Link); Q_D(Link);
if (! (doc->qmlProgram() && doc->qmlProgram()->imports)) if (! doc->qmlProgram())
return; return;
// implicit imports: the <default> package is always available
const QLatin1String defaultPackage("<default>");
if (engine()->cppQmlTypes().hasPackage(defaultPackage)) {
ImportInfo info(ImportInfo::LibraryImport, defaultPackage);
ObjectValue *import = d->importCache.value(ImportCacheKey(info));
if (!import) {
import = new ObjectValue(engine());
foreach (QmlObjectValue *object,
engine()->cppQmlTypes().typesForImport(defaultPackage, ComponentVersion())) {
import->setProperty(object->className(), object);
}
d->importCache.insert(ImportCacheKey(info), import);
}
typeEnv->addImport(import, info);
}
// implicit imports: // implicit imports:
// qml files in the same directory are available without explicit imports // qml files in the same directory are available without explicit imports
ImportInfo implcitImportInfo(ImportInfo::ImplicitDirectoryImport, doc->path()); ImportInfo implcitImportInfo(ImportInfo::ImplicitDirectoryImport, doc->path());

View File

@@ -77,6 +77,7 @@
#include <Token.h> #include <Token.h>
#include <Parser.h> #include <Parser.h>
#include <Control.h> #include <Control.h>
#include <CoreTypes.h>
#include <QtCore/QCoreApplication> #include <QtCore/QCoreApplication>
#include <QtCore/QDebug> #include <QtCore/QDebug>
@@ -290,6 +291,8 @@ public:
void operator()() void operator()()
{ {
_doc->check(_mode); _doc->check(_mode);
_doc->findExposedQmlTypes();
_doc->releaseSource();
_doc->releaseTranslationUnit(); _doc->releaseTranslationUnit();
if (_mode == Document::FastCheck) if (_mode == Document::FastCheck)
@@ -589,7 +592,6 @@ void CppPreprocessor::sourceNeeded(QString &fileName, IncludeType type, unsigned
doc->setSource(preprocessedCode); doc->setSource(preprocessedCode);
doc->tokenize(); doc->tokenize();
doc->releaseSource();
snapshot.insert(doc); snapshot.insert(doc);
m_todo.remove(fileName); m_todo.remove(fileName);
@@ -601,6 +603,7 @@ void CppPreprocessor::sourceNeeded(QString &fileName, IncludeType type, unsigned
(void) switchDocument(previousDoc); (void) switchDocument(previousDoc);
#else #else
doc->releaseSource();
Document::CheckMode mode = Document::FastCheck; Document::CheckMode mode = Document::FastCheck;
mode = Document::FullCheck; mode = Document::FullCheck;
doc->parse(); doc->parse();
@@ -1418,5 +1421,162 @@ void CppModelManager::GC()
protectSnapshot.unlock(); protectSnapshot.unlock();
} }
static FullySpecifiedType stripPointerAndReference(const FullySpecifiedType &type)
{
Type *t = type.type();
while (t) {
if (PointerType *ptr = t->asPointerType())
t = ptr->elementType().type();
else if (ReferenceType *ref = t->asReferenceType())
t = ref->elementType().type();
else
break;
}
return FullySpecifiedType(t);
}
static QString toQmlType(const FullySpecifiedType &type)
{
Overview overview;
QString result = overview(stripPointerAndReference(type));
if (result == QLatin1String("QString"))
result = QLatin1String("string");
return result;
}
static Class *lookupClass(const QString &expression, Scope *scope, TypeOfExpression &typeOf)
{
QList<LookupItem> results = typeOf(expression, scope);
Class *klass = 0;
foreach (const LookupItem &item, results) {
if (item.declaration()) {
klass = item.declaration()->asClass();
if (klass)
return klass;
}
}
return 0;
}
static void populate(LanguageUtils::FakeMetaObject *fmo, Class *klass,
QHash<Class *, LanguageUtils::FakeMetaObject *> *classes,
TypeOfExpression &typeOf)
{
using namespace LanguageUtils;
Overview namePrinter;
classes->insert(klass, fmo);
for (unsigned i = 0; i < klass->memberCount(); ++i) {
Symbol *member = klass->memberAt(i);
if (!member->name())
continue;
if (Function *func = member->type()->asFunctionType()) {
if (!func->isSlot() && !func->isInvokable() && !func->isSignal())
continue;
FakeMetaMethod method(namePrinter(func->name()), toQmlType(func->returnType()));
if (func->isSignal())
method.setMethodType(FakeMetaMethod::Signal);
else
method.setMethodType(FakeMetaMethod::Slot);
for (unsigned a = 0; a < func->argumentCount(); ++a) {
Symbol *arg = func->argumentAt(a);
QString name(CppModelManager::tr("unnamed"));
if (arg->name())
name = namePrinter(arg->name());
method.addParameter(name, toQmlType(arg->type()));
}
fmo->addMethod(method);
}
if (QtPropertyDeclaration *propDecl = member->asQtPropertyDeclaration()) {
const FullySpecifiedType &type = propDecl->type();
const bool isList = false; // ### fixme
const bool isWritable = propDecl->flags() & QtPropertyDeclaration::WriteFunction;
const bool isPointer = type.type() && type.type()->isPointerType();
FakeMetaProperty property(
namePrinter(propDecl->name()),
toQmlType(type),
isList, isWritable, isPointer);
fmo->addProperty(property);
}
if (QtEnum *qtEnum = member->asQtEnum()) {
// find the matching enum
Enum *e = 0;
QList<LookupItem> result = typeOf(namePrinter(qtEnum->name()), klass);
foreach (const LookupItem &item, result) {
if (item.declaration()) {
e = item.declaration()->asEnum();
if (e)
break;
}
}
if (!e)
continue;
FakeMetaEnum metaEnum(namePrinter(e->name()));
for (unsigned j = 0; j < e->memberCount(); ++j) {
Symbol *enumMember = e->memberAt(j);
if (!enumMember->name())
continue;
metaEnum.addKey(namePrinter(enumMember->name()), 0);
}
fmo->addEnum(metaEnum);
}
}
// only single inheritance is supported
if (klass->baseClassCount() > 0) {
BaseClass *base = klass->baseClassAt(0);
if (!base->name())
return;
const QString baseClassName = namePrinter(base->name());
fmo->setSuperclassName(baseClassName);
Class *baseClass = lookupClass(baseClassName, klass, typeOf);
if (!baseClass)
return;
FakeMetaObject *baseFmo = classes->value(baseClass);
if (!baseFmo) {
baseFmo = new FakeMetaObject;
populate(baseFmo, baseClass, classes, typeOf);
}
fmo->setSuperclass(baseFmo);
}
}
QList<LanguageUtils::FakeMetaObject *> CppModelManager::exportedQmlObjects() const
{
using namespace LanguageUtils;
QList<FakeMetaObject *> exportedObjects;
QHash<Class *, FakeMetaObject *> classes;
const Snapshot currentSnapshot = snapshot();
foreach (Document::Ptr doc, currentSnapshot) {
TypeOfExpression typeOf;
typeOf.init(doc, currentSnapshot);
foreach (const Document::ExportedQmlType &exportedType, doc->exportedQmlTypes()) {
FakeMetaObject *fmo = new FakeMetaObject;
fmo->addExport(exportedType.typeName, exportedType.packageName,
ComponentVersion(exportedType.majorVersion, exportedType.minorVersion));
exportedObjects += fmo;
Class *klass = lookupClass(exportedType.typeExpression, exportedType.scope, typeOf);
if (!klass)
continue;
// add the no-package export, so the cpp name can be used in properties
Overview overview;
fmo->addExport(overview(klass->name()), QString(), ComponentVersion());
populate(fmo, klass, &classes, typeOf);
}
}
return exportedObjects;
}
#endif #endif

View File

@@ -131,6 +131,8 @@ public:
virtual void findMacroUsages(const CPlusPlus::Macro &macro); virtual void findMacroUsages(const CPlusPlus::Macro &macro);
virtual QList<LanguageUtils::FakeMetaObject *> exportedQmlObjects() const;
void setHeaderSuffixes(const QStringList &suffixes) void setHeaderSuffixes(const QStringList &suffixes)
{ m_headerSuffixes = suffixes; } { m_headerSuffixes = suffixes; }

View File

@@ -39,6 +39,7 @@
#include <coreplugin/editormanager/editormanager.h> #include <coreplugin/editormanager/editormanager.h>
#include <coreplugin/progressmanager/progressmanager.h> #include <coreplugin/progressmanager/progressmanager.h>
#include <coreplugin/mimedatabase.h> #include <coreplugin/mimedatabase.h>
#include <cplusplus/ModelManagerInterface.h>
#include <qmljs/qmljsinterpreter.h> #include <qmljs/qmljsinterpreter.h>
#include <qmljs/qmljsbind.h> #include <qmljs/qmljsbind.h>
#include <qmljs/parser/qmldirparser_p.h> #include <qmljs/parser/qmldirparser_p.h>
@@ -56,6 +57,7 @@
#include <qtconcurrent/runextensions.h> #include <qtconcurrent/runextensions.h>
#include <QTextStream> #include <QTextStream>
#include <QCoreApplication> #include <QCoreApplication>
#include <QTimer>
#include <QDebug> #include <QDebug>
@@ -72,6 +74,11 @@ ModelManager::ModelManager(QObject *parent):
{ {
m_synchronizer.setCancelOnWait(true); m_synchronizer.setCancelOnWait(true);
m_updateCppQmlTypesTimer = new QTimer(this);
m_updateCppQmlTypesTimer->setInterval(1000);
m_updateCppQmlTypesTimer->setSingleShot(true);
connect(m_updateCppQmlTypesTimer, SIGNAL(timeout()), SLOT(updateCppQmlTypes()));
qRegisterMetaType<QmlJS::Document::Ptr>("QmlJS::Document::Ptr"); qRegisterMetaType<QmlJS::Document::Ptr>("QmlJS::Document::Ptr");
qRegisterMetaType<QmlJS::LibraryInfo>("QmlJS::LibraryInfo"); qRegisterMetaType<QmlJS::LibraryInfo>("QmlJS::LibraryInfo");
@@ -81,6 +88,16 @@ ModelManager::ModelManager(QObject *parent):
updateImportPaths(); updateImportPaths();
} }
void ModelManager::delayedInitialization()
{
CPlusPlus::CppModelManagerInterface *cppModelManager =
CPlusPlus::CppModelManagerInterface::instance();
if (cppModelManager) {
connect(cppModelManager, SIGNAL(documentUpdated(CPlusPlus::Document::Ptr)),
m_updateCppQmlTypesTimer, SLOT(start()));
}
}
void ModelManager::loadQmlTypeDescriptions() void ModelManager::loadQmlTypeDescriptions()
{ {
if (Core::ICore::instance()) { if (Core::ICore::instance()) {
@@ -537,3 +554,16 @@ void ModelManager::loadPluginTypes(const QString &libraryPath, const QString &im
{ {
m_pluginDumper->loadPluginTypes(libraryPath, importPath, importUri); m_pluginDumper->loadPluginTypes(libraryPath, importPath, importUri);
} }
void ModelManager::updateCppQmlTypes()
{
CPlusPlus::CppModelManagerInterface *cppModelManager =
CPlusPlus::CppModelManagerInterface::instance();
if (!cppModelManager)
return;
QList<const LanguageUtils::FakeMetaObject *> constFMOs;
foreach (LanguageUtils::FakeMetaObject *fmo, cppModelManager->exportedQmlObjects())
constFMOs.append(fmo);
Interpreter::CppQmlTypesLoader::cppObjects = constFMOs;
}

View File

@@ -44,6 +44,8 @@
#include <QMutex> #include <QMutex>
#include <QProcess> #include <QProcess>
QT_FORWARD_DECLARE_CLASS(QTimer)
namespace Core { namespace Core {
class ICore; class ICore;
class MimeType; class MimeType;
@@ -61,6 +63,8 @@ class QMLJSTOOLS_EXPORT ModelManager: public QmlJS::ModelManagerInterface
public: public:
ModelManager(QObject *parent = 0); ModelManager(QObject *parent = 0);
void delayedInitialization();
virtual WorkingCopy workingCopy() const; virtual WorkingCopy workingCopy() const;
virtual QmlJS::Snapshot snapshot() const; virtual QmlJS::Snapshot snapshot() const;
@@ -99,6 +103,9 @@ protected:
void updateImportPaths(); void updateImportPaths();
private slots:
void updateCppQmlTypes();
private: private:
static bool matchesMimeType(const Core::MimeType &fileMimeType, const Core::MimeType &knownMimeType); static bool matchesMimeType(const Core::MimeType &fileMimeType, const Core::MimeType &knownMimeType);
@@ -109,6 +116,7 @@ private:
QStringList m_defaultImportPaths; QStringList m_defaultImportPaths;
QFutureSynchronizer<void> m_synchronizer; QFutureSynchronizer<void> m_synchronizer;
QTimer *m_updateCppQmlTypesTimer;
// project integration // project integration
QMap<ProjectExplorer::Project *, ProjectInfo> m_projects; QMap<ProjectExplorer::Project *, ProjectInfo> m_projects;

View File

@@ -1,4 +1,5 @@
include($$IDE_SOURCE_TREE/src/libs/languageutils/languageutils.pri) include($$IDE_SOURCE_TREE/src/libs/languageutils/languageutils.pri)
include($$IDE_SOURCE_TREE/src/libs/cplusplus/cplusplus.pri)
include($$IDE_SOURCE_TREE/src/libs/qmljs/qmljs.pri) include($$IDE_SOURCE_TREE/src/libs/qmljs/qmljs.pri)
include($$IDE_SOURCE_TREE/src/plugins/projectexplorer/projectexplorer.pri) include($$IDE_SOURCE_TREE/src/plugins/projectexplorer/projectexplorer.pri)
include($$IDE_SOURCE_TREE/src/plugins/texteditor/texteditor.pri) include($$IDE_SOURCE_TREE/src/plugins/texteditor/texteditor.pri)

View File

@@ -89,6 +89,7 @@ bool QmlJSToolsPlugin::initialize(const QStringList &arguments, QString *error)
void QmlJSToolsPlugin::extensionsInitialized() void QmlJSToolsPlugin::extensionsInitialized()
{ {
m_modelManager->delayedInitialization();
} }
ExtensionSystem::IPlugin::ShutdownFlag QmlJSToolsPlugin::aboutToShutdown() ExtensionSystem::IPlugin::ShutdownFlag QmlJSToolsPlugin::aboutToShutdown()