From 0194da7300d3fd76594e337c785566c4498e12f4 Mon Sep 17 00:00:00 2001 From: Christian Kamm <christian.d.kamm@nokia.com> Date: Fri, 3 Dec 2010 10:13:15 +0100 Subject: [PATCH] Qml-C++: Find C++ qmlRegisterType calls and populate QML code model. Reviewed-by: Erik Verbruggen --- src/libs/cplusplus/CppDocument.cpp | 116 +++++++++++++ src/libs/cplusplus/CppDocument.h | 16 ++ src/libs/cplusplus/ModelManagerInterface.h | 3 + src/libs/qmljs/qmljsinterpreter.cpp | 2 + src/libs/qmljs/qmljsinterpreter.h | 1 + src/libs/qmljs/qmljslink.cpp | 19 +- src/plugins/cpptools/cppmodelmanager.cpp | 162 +++++++++++++++++- src/plugins/cpptools/cppmodelmanager.h | 2 + src/plugins/qmljstools/qmljsmodelmanager.cpp | 30 ++++ src/plugins/qmljstools/qmljsmodelmanager.h | 8 + .../qmljstools/qmljstools_dependencies.pri | 1 + src/plugins/qmljstools/qmljstoolsplugin.cpp | 1 + 12 files changed, 359 insertions(+), 2 deletions(-) diff --git a/src/libs/cplusplus/CppDocument.cpp b/src/libs/cplusplus/CppDocument.cpp index ef159e93708..c2f6af3882e 100644 --- a/src/libs/cplusplus/CppDocument.cpp +++ b/src/libs/cplusplus/CppDocument.cpp @@ -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() { _source.clear(); diff --git a/src/libs/cplusplus/CppDocument.h b/src/libs/cplusplus/CppDocument.h index bbb7aaddb61..a00eeefa1d9 100644 --- a/src/libs/cplusplus/CppDocument.h +++ b/src/libs/cplusplus/CppDocument.h @@ -125,6 +125,8 @@ public: void check(CheckMode mode = FullCheck); + void findExposedQmlTypes(); + void releaseSource(); void releaseTranslationUnit(); @@ -318,6 +320,19 @@ public: const MacroUse *findMacroUseAt(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: QString _fileName; Control *_control; @@ -329,6 +344,7 @@ private: QList<Block> _skippedBlocks; QList<MacroUse> _macroUses; QList<UndefinedMacroUse> _undefinedMacroUses; + QList<ExportedQmlType> _exportedQmlTypes; QByteArray _source; QDateTime _lastModified; unsigned _revision; diff --git a/src/libs/cplusplus/ModelManagerInterface.h b/src/libs/cplusplus/ModelManagerInterface.h index c58925e6134..0a1f3217c4c 100644 --- a/src/libs/cplusplus/ModelManagerInterface.h +++ b/src/libs/cplusplus/ModelManagerInterface.h @@ -35,6 +35,7 @@ #define CPPMODELMANAGERINTERFACE_H #include <cplusplus/CppDocument.h> +#include <languageutils/fakemetaobject.h> #include <QtCore/QObject> #include <QtCore/QHash> #include <QtCore/QPointer> @@ -146,6 +147,8 @@ public: virtual void findMacroUsages(const CPlusPlus::Macro ¯o) = 0; + virtual QList<LanguageUtils::FakeMetaObject *> exportedQmlObjects() const = 0; + Q_SIGNALS: void documentUpdated(CPlusPlus::Document::Ptr doc); diff --git a/src/libs/qmljs/qmljsinterpreter.cpp b/src/libs/qmljs/qmljsinterpreter.cpp index e4a0d785c2e..da0616cf0a4 100644 --- a/src/libs/qmljs/qmljsinterpreter.cpp +++ b/src/libs/qmljs/qmljsinterpreter.cpp @@ -1947,6 +1947,7 @@ const Value *Function::invoke(const Activation *activation) const //////////////////////////////////////////////////////////////////////////////// QList<const FakeMetaObject *> CppQmlTypesLoader::builtinObjects; +QList<const FakeMetaObject *> CppQmlTypesLoader::cppObjects; QStringList CppQmlTypesLoader::load(const QFileInfoList &xmlFiles) { @@ -2440,6 +2441,7 @@ Engine::Engine() initializePrototypes(); _cppQmlTypes.load(this, CppQmlTypesLoader::builtinObjects); + _cppQmlTypes.load(this, CppQmlTypesLoader::cppObjects); // the 'Qt' object is dumped even though it is not exported // it contains useful information, in particular on enums - add the diff --git a/src/libs/qmljs/qmljsinterpreter.h b/src/libs/qmljs/qmljsinterpreter.h index 60fbcababaa..758ca27cce4 100644 --- a/src/libs/qmljs/qmljsinterpreter.h +++ b/src/libs/qmljs/qmljsinterpreter.h @@ -593,6 +593,7 @@ public: /** \return an empty list when successful, error messages otherwise. */ static QStringList load(const QFileInfoList &xmlFiles); static QList<const LanguageUtils::FakeMetaObject *> builtinObjects; + static QList<const LanguageUtils::FakeMetaObject *> cppObjects; // parses the xml string and fills the newObjects map static QString parseQmlTypeXml(const QByteArray &xml, diff --git a/src/libs/qmljs/qmljslink.cpp b/src/libs/qmljs/qmljslink.cpp index d36dbbb0218..0c4f85a1747 100644 --- a/src/libs/qmljs/qmljslink.cpp +++ b/src/libs/qmljs/qmljslink.cpp @@ -162,9 +162,26 @@ void Link::populateImportedTypes(TypeEnvironment *typeEnv, Document::Ptr doc) { Q_D(Link); - if (! (doc->qmlProgram() && doc->qmlProgram()->imports)) + if (! doc->qmlProgram()) 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: // qml files in the same directory are available without explicit imports ImportInfo implcitImportInfo(ImportInfo::ImplicitDirectoryImport, doc->path()); diff --git a/src/plugins/cpptools/cppmodelmanager.cpp b/src/plugins/cpptools/cppmodelmanager.cpp index efe8175dcbc..19d3530346f 100644 --- a/src/plugins/cpptools/cppmodelmanager.cpp +++ b/src/plugins/cpptools/cppmodelmanager.cpp @@ -77,6 +77,7 @@ #include <Token.h> #include <Parser.h> #include <Control.h> +#include <CoreTypes.h> #include <QtCore/QCoreApplication> #include <QtCore/QDebug> @@ -290,6 +291,8 @@ public: void operator()() { _doc->check(_mode); + _doc->findExposedQmlTypes(); + _doc->releaseSource(); _doc->releaseTranslationUnit(); if (_mode == Document::FastCheck) @@ -589,7 +592,6 @@ void CppPreprocessor::sourceNeeded(QString &fileName, IncludeType type, unsigned doc->setSource(preprocessedCode); doc->tokenize(); - doc->releaseSource(); snapshot.insert(doc); m_todo.remove(fileName); @@ -601,6 +603,7 @@ void CppPreprocessor::sourceNeeded(QString &fileName, IncludeType type, unsigned (void) switchDocument(previousDoc); #else + doc->releaseSource(); Document::CheckMode mode = Document::FastCheck; mode = Document::FullCheck; doc->parse(); @@ -1418,5 +1421,162 @@ void CppModelManager::GC() 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 diff --git a/src/plugins/cpptools/cppmodelmanager.h b/src/plugins/cpptools/cppmodelmanager.h index 865421a16e5..bd0bef09622 100644 --- a/src/plugins/cpptools/cppmodelmanager.h +++ b/src/plugins/cpptools/cppmodelmanager.h @@ -131,6 +131,8 @@ public: virtual void findMacroUsages(const CPlusPlus::Macro ¯o); + virtual QList<LanguageUtils::FakeMetaObject *> exportedQmlObjects() const; + void setHeaderSuffixes(const QStringList &suffixes) { m_headerSuffixes = suffixes; } diff --git a/src/plugins/qmljstools/qmljsmodelmanager.cpp b/src/plugins/qmljstools/qmljsmodelmanager.cpp index 69f43c5579b..0477f9ed01c 100644 --- a/src/plugins/qmljstools/qmljsmodelmanager.cpp +++ b/src/plugins/qmljstools/qmljsmodelmanager.cpp @@ -39,6 +39,7 @@ #include <coreplugin/editormanager/editormanager.h> #include <coreplugin/progressmanager/progressmanager.h> #include <coreplugin/mimedatabase.h> +#include <cplusplus/ModelManagerInterface.h> #include <qmljs/qmljsinterpreter.h> #include <qmljs/qmljsbind.h> #include <qmljs/parser/qmldirparser_p.h> @@ -56,6 +57,7 @@ #include <qtconcurrent/runextensions.h> #include <QTextStream> #include <QCoreApplication> +#include <QTimer> #include <QDebug> @@ -72,6 +74,11 @@ ModelManager::ModelManager(QObject *parent): { 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::LibraryInfo>("QmlJS::LibraryInfo"); @@ -81,6 +88,16 @@ ModelManager::ModelManager(QObject *parent): 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() { if (Core::ICore::instance()) { @@ -537,3 +554,16 @@ void ModelManager::loadPluginTypes(const QString &libraryPath, const QString &im { 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; +} diff --git a/src/plugins/qmljstools/qmljsmodelmanager.h b/src/plugins/qmljstools/qmljsmodelmanager.h index a80f18cdca3..1a0b4c2833d 100644 --- a/src/plugins/qmljstools/qmljsmodelmanager.h +++ b/src/plugins/qmljstools/qmljsmodelmanager.h @@ -44,6 +44,8 @@ #include <QMutex> #include <QProcess> +QT_FORWARD_DECLARE_CLASS(QTimer) + namespace Core { class ICore; class MimeType; @@ -61,6 +63,8 @@ class QMLJSTOOLS_EXPORT ModelManager: public QmlJS::ModelManagerInterface public: ModelManager(QObject *parent = 0); + void delayedInitialization(); + virtual WorkingCopy workingCopy() const; virtual QmlJS::Snapshot snapshot() const; @@ -99,6 +103,9 @@ protected: void updateImportPaths(); +private slots: + void updateCppQmlTypes(); + private: static bool matchesMimeType(const Core::MimeType &fileMimeType, const Core::MimeType &knownMimeType); @@ -109,6 +116,7 @@ private: QStringList m_defaultImportPaths; QFutureSynchronizer<void> m_synchronizer; + QTimer *m_updateCppQmlTypesTimer; // project integration QMap<ProjectExplorer::Project *, ProjectInfo> m_projects; diff --git a/src/plugins/qmljstools/qmljstools_dependencies.pri b/src/plugins/qmljstools/qmljstools_dependencies.pri index d63c784082d..c94524e75c6 100644 --- a/src/plugins/qmljstools/qmljstools_dependencies.pri +++ b/src/plugins/qmljstools/qmljstools_dependencies.pri @@ -1,4 +1,5 @@ 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/plugins/projectexplorer/projectexplorer.pri) include($$IDE_SOURCE_TREE/src/plugins/texteditor/texteditor.pri) diff --git a/src/plugins/qmljstools/qmljstoolsplugin.cpp b/src/plugins/qmljstools/qmljstoolsplugin.cpp index c1f0e91e6b1..3a456eed752 100644 --- a/src/plugins/qmljstools/qmljstoolsplugin.cpp +++ b/src/plugins/qmljstools/qmljstoolsplugin.cpp @@ -89,6 +89,7 @@ bool QmlJSToolsPlugin::initialize(const QStringList &arguments, QString *error) void QmlJSToolsPlugin::extensionsInitialized() { + m_modelManager->delayedInitialization(); } ExtensionSystem::IPlugin::ShutdownFlag QmlJSToolsPlugin::aboutToShutdown() -- GitLab