From 27d08306986c72aeab1b1f06e96e7072c53d3622 Mon Sep 17 00:00:00 2001 From: Christian Kamm <christian.d.kamm@nokia.com> Date: Tue, 23 Aug 2011 12:02:29 +0200 Subject: [PATCH] QmlJS: Move the exported-C++-type detection out of C++ code. It now lives in qmljstools/qmljsfindexportedcpptypes, all in one place. Also ensures that the source code is available when a file is being scanned for QML exports. This will enable checking comments for annotations about the URI a plugin is usually imported as. Change-Id: I1da36d0678e0a8d34b171dbe0f6b5690d89eb18b Reviewed-on: http://codereview.qt.nokia.com/3392 Reviewed-by: Fawzi Mohamed <fawzi.mohamed@nokia.com> --- src/libs/cplusplus/CppDocument.cpp | 259 +--------- src/libs/cplusplus/CppDocument.h | 23 +- src/libs/cplusplus/ModelManagerInterface.h | 2 - src/libs/qmljs/qmljslink.cpp | 4 +- src/plugins/cpptools/cppmodelmanager.cpp | 169 +----- src/plugins/cpptools/cppmodelmanager.h | 2 - .../qmljstools/qmljsfindexportedcpptypes.cpp | 486 ++++++++++++++++++ .../qmljstools/qmljsfindexportedcpptypes.h | 55 ++ src/plugins/qmljstools/qmljsmodelmanager.cpp | 51 +- src/plugins/qmljstools/qmljsmodelmanager.h | 9 +- src/plugins/qmljstools/qmljstools-lib.pri | 6 +- 11 files changed, 616 insertions(+), 450 deletions(-) create mode 100644 src/plugins/qmljstools/qmljsfindexportedcpptypes.cpp create mode 100644 src/plugins/qmljstools/qmljsfindexportedcpptypes.h diff --git a/src/libs/cplusplus/CppDocument.cpp b/src/libs/cplusplus/CppDocument.cpp index a72648b8ba2..169ec2b694c 100644 --- a/src/libs/cplusplus/CppDocument.cpp +++ b/src/libs/cplusplus/CppDocument.cpp @@ -256,7 +256,8 @@ Document::Document(const QString &fileName) : _fileName(QDir::cleanPath(fileName)), _globalNamespace(0), _revision(0), - _editorRevision(0) + _editorRevision(0), + _fastCheck(false) { _control = new Control(); @@ -574,8 +575,10 @@ void Document::check(CheckMode mode) _globalNamespace = _control->newNamespace(0); Bind semantic(_translationUnit); - if (mode == FastCheck) + if (mode == FastCheck) { + _fastCheck = true; semantic.setSkipFunctionBodies(true); + } if (! _translationUnit->ast()) return; // nothing to do. @@ -589,253 +592,19 @@ void Document::check(CheckMode mode) } } -class FindExposedQmlTypes : protected ASTVisitor +void Document::keepSourceAndAST() { - Document *_doc; - QList<Document::ExportedQmlType> _exportedTypes; - CompoundStatementAST *_compound; - ASTMatcher _matcher; - ASTPatternBuilder _builder; - Overview _overview; - -public: - FindExposedQmlTypes(Document *doc) - : ASTVisitor(doc->translationUnit()) - , _doc(doc) - , _compound(0) - {} - - QList<Document::ExportedQmlType> operator()() - { - _exportedTypes.clear(); - accept(translationUnit()->ast()); - return _exportedTypes; - } - -protected: - virtual bool visit(CompoundStatementAST *ast) - { - CompoundStatementAST *old = _compound; - _compound = ast; - accept(ast->statement_list); - _compound = old; - return false; - } - - 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; - - // last argument must be a string literal - const StringLiteral *nameLit = 0; - if (StringLiteralAST *nameAst = ast->expression_list->next->next->next->value->asStringLiteral()) - nameLit = translationUnit()->stringLiteral(nameAst->literal_token); - if (!nameLit) { - // disable this warning for now, we don't want to encourage using string literals if they don't mean to - // in the future, we will also accept annotations for the qmlRegisterType arguments in comments -// 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; - } - - // if the first argument is a string literal, things are easy - QString packageName; - if (StringLiteralAST *packageAst = ast->expression_list->value->asStringLiteral()) { - const StringLiteral *packageLit = translationUnit()->stringLiteral(packageAst->literal_token); - packageName = QString::fromUtf8(packageLit->chars(), packageLit->size()); - } - // as a special case, allow an identifier package argument if there's a - // Q_ASSERT(QLatin1String(uri) == QLatin1String("actual uri")); - // in the enclosing compound statement - IdExpressionAST *uriName = ast->expression_list->value->asIdExpression(); - if (packageName.isEmpty() && uriName && _compound) { - for (StatementListAST *it = _compound->statement_list; it; it = it->next) { - StatementAST *stmt = it->value; - - packageName = nameOfUriAssert(stmt, uriName); - if (!packageName.isEmpty()) - break; - } - } - - // 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 (!packageName.isEmpty() && majorLit && minorLit && majorLit->isInt() && minorLit->isInt()) { - exportedType.packageName = packageName; - exportedType.majorVersion = QString::fromUtf8(majorLit->chars(), majorLit->size()).toInt(); - exportedType.minorVersion = QString::fromUtf8(minorLit->chars(), minorLit->size()).toInt(); - } else { - // disable this warning, see above for details -// translationUnit()->warning(ast->base_expression->firstToken(), -// "The module will not be available in Qt Creator's QML editors because the uri and version numbers\n" -// "cannot be determined by static analysis. The type will still 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; - } - -private: - QString stringOf(AST *ast) - { - const Token begin = translationUnit()->tokenAt(ast->firstToken()); - const Token last = translationUnit()->tokenAt(ast->lastToken() - 1); - return _doc->source().mid(begin.begin(), last.end() - begin.begin()); - } - - ExpressionAST *skipStringCall(ExpressionAST *exp) - { - if (!exp) - return 0; - - IdExpressionAST *callName = _builder.IdExpression(); - CallAST *call = _builder.Call(callName); - if (!exp->match(call, &_matcher)) - return exp; - - const QString name = stringOf(callName); - if (name != QLatin1String("QLatin1String") - && name != QLatin1String("QString")) - return exp; - - if (!call->expression_list || call->expression_list->next) - return exp; - - return call->expression_list->value; - } - - QString nameOfUriAssert(StatementAST *stmt, IdExpressionAST *uriName) - { - QString null; - - IdExpressionAST *outerCallName = _builder.IdExpression(); - BinaryExpressionAST *binary = _builder.BinaryExpression(); - // assert(... == ...); - ExpressionStatementAST *pattern = _builder.ExpressionStatement( - _builder.Call(outerCallName, _builder.ExpressionList( - binary))); - - if (!stmt->match(pattern, &_matcher)) { - outerCallName = _builder.IdExpression(); - binary = _builder.BinaryExpression(); - // the expansion of Q_ASSERT(...), - // ((!(... == ...)) ? qt_assert(...) : ...); - pattern = _builder.ExpressionStatement( - _builder.NestedExpression( - _builder.ConditionalExpression( - _builder.NestedExpression( - _builder.UnaryExpression( - _builder.NestedExpression( - binary))), - _builder.Call(outerCallName)))); - - if (!stmt->match(pattern, &_matcher)) - return null; - } - - const QString outerCall = stringOf(outerCallName); - if (outerCall != QLatin1String("qt_assert") - && outerCall != QLatin1String("assert") - && outerCall != QLatin1String("Q_ASSERT")) - return null; - - if (translationUnit()->tokenAt(binary->binary_op_token).kind() != T_EQUAL_EQUAL) - return null; - - ExpressionAST *lhsExp = skipStringCall(binary->left_expression); - ExpressionAST *rhsExp = skipStringCall(binary->right_expression); - if (!lhsExp || !rhsExp) - return null; - - StringLiteralAST *uriString = lhsExp->asStringLiteral(); - IdExpressionAST *uriArgName = lhsExp->asIdExpression(); - if (!uriString) - uriString = rhsExp->asStringLiteral(); - if (!uriArgName) - uriArgName = rhsExp->asIdExpression(); - if (!uriString || !uriArgName) - return null; - - if (stringOf(uriArgName) != stringOf(uriName)) - return null; - - const StringLiteral *packageLit = translationUnit()->stringLiteral(uriString->literal_token); - return QString::fromUtf8(packageLit->chars(), packageLit->size()); - } -}; - -void Document::findExposedQmlTypes() -{ - if (! _translationUnit->ast()) - return; - - QByteArray qmlRegisterTypeToken("qmlRegisterType"); - if (_translationUnit->control()->findIdentifier( - qmlRegisterTypeToken.constData(), qmlRegisterTypeToken.size())) { - FindExposedQmlTypes finder(this); - _exportedQmlTypes = finder(); - } -} - -void Document::releaseSource() -{ - _source.clear(); + _keepSourceAndASTCount.ref(); } -void Document::releaseTranslationUnit() +void Document::releaseSourceAndAST() { - _translationUnit->release(); + if (!_keepSourceAndASTCount.deref()) { + _source.clear(); + _translationUnit->release(); + if (_fastCheck) + _control->squeeze(); + } } Snapshot::Snapshot() diff --git a/src/libs/cplusplus/CppDocument.h b/src/libs/cplusplus/CppDocument.h index 48d72cad3cb..024fec93fb5 100644 --- a/src/libs/cplusplus/CppDocument.h +++ b/src/libs/cplusplus/CppDocument.h @@ -40,6 +40,7 @@ #include <QtCore/QDateTime> #include <QtCore/QHash> #include <QtCore/QFileInfo> +#include <QtCore/QAtomicInt> namespace CPlusPlus { @@ -125,11 +126,6 @@ public: void check(CheckMode mode = FullCheck); - void findExposedQmlTypes(); - - void releaseSource(); - void releaseTranslationUnit(); - static Ptr create(const QString &fileName); class DiagnosticMessage @@ -320,18 +316,8 @@ 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; } + void keepSourceAndAST(); + void releaseSourceAndAST(); private: QString _fileName; @@ -344,11 +330,12 @@ private: QList<Block> _skippedBlocks; QList<MacroUse> _macroUses; QList<UndefinedMacroUse> _undefinedMacroUses; - QList<ExportedQmlType> _exportedQmlTypes; QByteArray _source; QDateTime _lastModified; + QAtomicInt _keepSourceAndASTCount; unsigned _revision; unsigned _editorRevision; + bool _fastCheck; friend class Snapshot; }; diff --git a/src/libs/cplusplus/ModelManagerInterface.h b/src/libs/cplusplus/ModelManagerInterface.h index 0a9cf6eaeef..75d084053a7 100644 --- a/src/libs/cplusplus/ModelManagerInterface.h +++ b/src/libs/cplusplus/ModelManagerInterface.h @@ -140,8 +140,6 @@ public: virtual void findMacroUsages(const CPlusPlus::Macro ¯o) = 0; - virtual QList<LanguageUtils::FakeMetaObject::ConstPtr> exportedQmlObjects(const CPlusPlus::Document::Ptr &doc) const = 0; - Q_SIGNALS: void documentUpdated(CPlusPlus::Document::Ptr doc); void sourceFilesRefreshed(const QStringList &files); diff --git a/src/libs/qmljs/qmljslink.cpp b/src/libs/qmljs/qmljslink.cpp index 7c2eaaa8166..d43dbbe05c6 100644 --- a/src/libs/qmljs/qmljslink.cpp +++ b/src/libs/qmljs/qmljslink.cpp @@ -502,7 +502,9 @@ void Link::loadImplicitDefaultImports(Imports *imports) import.info = info; import.object = new ObjectValue(d->valueOwner); foreach (QmlObjectValue *object, - d->valueOwner->cppQmlTypes().typesForImport(defaultPackage, ComponentVersion())) { + d->valueOwner->cppQmlTypes().typesForImport( + defaultPackage, + ComponentVersion(ComponentVersion::MaxVersion, ComponentVersion::MaxVersion))) { import.object->setMember(object->className(), object); } d->importCache.insert(ImportCacheKey(info), import); diff --git a/src/plugins/cpptools/cppmodelmanager.cpp b/src/plugins/cpptools/cppmodelmanager.cpp index 489a58c03cb..1aab932648a 100644 --- a/src/plugins/cpptools/cppmodelmanager.cpp +++ b/src/plugins/cpptools/cppmodelmanager.cpp @@ -290,15 +290,11 @@ public: void operator()() { _doc->check(_mode); - _doc->findExposedQmlTypes(); - _doc->releaseSource(); - _doc->releaseTranslationUnit(); - - if (_mode == Document::FastCheck) - _doc->control()->squeeze(); if (_modelManager) _modelManager->emitDocumentUpdated(_doc); // ### TODO: compress + + _doc->releaseSourceAndAST(); } }; } // end of anonymous namespace @@ -590,6 +586,7 @@ void CppPreprocessor::sourceNeeded(QString &fileName, IncludeType type, unsigned const QByteArray preprocessedCode = preprocess(fileName, contents); doc->setSource(preprocessedCode); + doc->keepSourceAndAST(); doc->tokenize(); snapshot.insert(doc); @@ -1284,166 +1281,6 @@ 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::Ptr fmo, Class *klass, - QHash<Class *, LanguageUtils::FakeMetaObject::Ptr> *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(); - const int revision = 0; // ### fixme - FakeMetaProperty property( - namePrinter(propDecl->name()), - toQmlType(type), - isList, isWritable, isPointer, - revision); - 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::Ptr baseFmo = classes->value(baseClass); - if (!baseFmo) { - baseFmo = FakeMetaObject::Ptr(new FakeMetaObject); - populate(baseFmo, baseClass, classes, typeOf); - } - } -} - -QList<LanguageUtils::FakeMetaObject::ConstPtr> CppModelManager::exportedQmlObjects(const Document::Ptr &doc) const -{ - using namespace LanguageUtils; - QList<FakeMetaObject::ConstPtr> exportedObjects; - QHash<Class *, FakeMetaObject::Ptr> classes; - - const QList<CPlusPlus::Document::ExportedQmlType> exported = doc->exportedQmlTypes(); - if (exported.isEmpty()) - return exportedObjects; - - TypeOfExpression typeOf; - const Snapshot currentSnapshot = snapshot(); - typeOf.init(doc, currentSnapshot); - foreach (const Document::ExportedQmlType &exportedType, exported) { - FakeMetaObject::Ptr 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; -} - void CppModelManager::finishedRefreshingSourceFiles(const QStringList &files) { emit sourceFilesRefreshed(files); diff --git a/src/plugins/cpptools/cppmodelmanager.h b/src/plugins/cpptools/cppmodelmanager.h index d902f7d4d14..13a1b851955 100644 --- a/src/plugins/cpptools/cppmodelmanager.h +++ b/src/plugins/cpptools/cppmodelmanager.h @@ -128,8 +128,6 @@ public: virtual void findMacroUsages(const CPlusPlus::Macro ¯o); - virtual QList<LanguageUtils::FakeMetaObject::ConstPtr> exportedQmlObjects(const CPlusPlus::Document::Ptr &doc) const; - void finishedRefreshingSourceFiles(const QStringList &files); Q_SIGNALS: diff --git a/src/plugins/qmljstools/qmljsfindexportedcpptypes.cpp b/src/plugins/qmljstools/qmljsfindexportedcpptypes.cpp new file mode 100644 index 00000000000..82e6dcb085d --- /dev/null +++ b/src/plugins/qmljstools/qmljsfindexportedcpptypes.cpp @@ -0,0 +1,486 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (info@qt.nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at info@qt.nokia.com. +** +**************************************************************************/ + +#include "qmljsfindexportedcpptypes.h" + +#include <qmljs/qmljsinterpreter.h> +#include <cplusplus/AST.h> +#include <cplusplus/TranslationUnit.h> +#include <cplusplus/ASTVisitor.h> +#include <cplusplus/ASTMatcher.h> +#include <cplusplus/ASTPatternBuilder.h> +#include <cplusplus/Overview.h> +#include <cplusplus/TypeOfExpression.h> +#include <cplusplus/Names.h> +#include <cplusplus/Literals.h> +#include <cplusplus/CoreTypes.h> +#include <cplusplus/Symbols.h> +#include <utils/qtcassert.h> + +#include <QtCore/QDebug> + +using namespace QmlJSTools; + +namespace { +using namespace CPlusPlus; + +class ExportedQmlType { +public: + QString packageName; + QString typeName; + LanguageUtils::ComponentVersion version; + Scope *scope; + QString typeExpression; +}; + +class FindExportsVisitor : protected ASTVisitor +{ + CPlusPlus::Document::Ptr _doc; + QList<ExportedQmlType> _exportedTypes; + CompoundStatementAST *_compound; + ASTMatcher _matcher; + ASTPatternBuilder _builder; + Overview _overview; + +public: + FindExportsVisitor(CPlusPlus::Document::Ptr doc) + : ASTVisitor(doc->translationUnit()) + , _doc(doc) + , _compound(0) + {} + + QList<ExportedQmlType> operator()() + { + _exportedTypes.clear(); + accept(translationUnit()->ast()); + return _exportedTypes; + } + +protected: + virtual bool visit(CompoundStatementAST *ast) + { + CompoundStatementAST *old = _compound; + _compound = ast; + accept(ast->statement_list); + _compound = old; + return false; + } + + 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; + + // last argument must be a string literal + const StringLiteral *nameLit = 0; + if (StringLiteralAST *nameAst = ast->expression_list->next->next->next->value->asStringLiteral()) + nameLit = translationUnit()->stringLiteral(nameAst->literal_token); + if (!nameLit) { + // disable this warning for now, we don't want to encourage using string literals if they don't mean to + // in the future, we will also accept annotations for the qmlRegisterType arguments in comments +// 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; + } + + // if the first argument is a string literal, things are easy + QString packageName; + if (StringLiteralAST *packageAst = ast->expression_list->value->asStringLiteral()) { + const StringLiteral *packageLit = translationUnit()->stringLiteral(packageAst->literal_token); + packageName = QString::fromUtf8(packageLit->chars(), packageLit->size()); + } + // as a special case, allow an identifier package argument if there's a + // Q_ASSERT(QLatin1String(uri) == QLatin1String("actual uri")); + // in the enclosing compound statement + IdExpressionAST *uriName = ast->expression_list->value->asIdExpression(); + if (packageName.isEmpty() && uriName && _compound) { + for (StatementListAST *it = _compound->statement_list; it; it = it->next) { + StatementAST *stmt = it->value; + + packageName = nameOfUriAssert(stmt, uriName); + if (!packageName.isEmpty()) + break; + } + } + + // 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 + ExportedQmlType exportedType; + exportedType.typeName = QString::fromUtf8(nameLit->chars(), nameLit->size()); + if (!packageName.isEmpty() && majorLit && minorLit && majorLit->isInt() && minorLit->isInt()) { + exportedType.packageName = packageName; + exportedType.version = LanguageUtils::ComponentVersion( + QString::fromUtf8(majorLit->chars(), majorLit->size()).toInt(), + QString::fromUtf8(minorLit->chars(), minorLit->size()).toInt()); + } else { + // disable this warning, see above for details +// translationUnit()->warning(ast->base_expression->firstToken(), +// "The module will not be available in Qt Creator's QML editors because the uri and version numbers\n" +// "cannot be determined by static analysis. The type will still be available globally."); + exportedType.packageName = QmlJS::CppQmlTypes::defaultPackage; + } + + // 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; + } + +private: + QString stringOf(AST *ast) + { + const Token begin = translationUnit()->tokenAt(ast->firstToken()); + const Token last = translationUnit()->tokenAt(ast->lastToken() - 1); + return _doc->source().mid(begin.begin(), last.end() - begin.begin()); + } + + ExpressionAST *skipStringCall(ExpressionAST *exp) + { + if (!exp) + return 0; + + IdExpressionAST *callName = _builder.IdExpression(); + CallAST *call = _builder.Call(callName); + if (!exp->match(call, &_matcher)) + return exp; + + const QString name = stringOf(callName); + if (name != QLatin1String("QLatin1String") + && name != QLatin1String("QString")) + return exp; + + if (!call->expression_list || call->expression_list->next) + return exp; + + return call->expression_list->value; + } + + QString nameOfUriAssert(StatementAST *stmt, IdExpressionAST *uriName) + { + QString null; + + IdExpressionAST *outerCallName = _builder.IdExpression(); + BinaryExpressionAST *binary = _builder.BinaryExpression(); + // assert(... == ...); + ExpressionStatementAST *pattern = _builder.ExpressionStatement( + _builder.Call(outerCallName, _builder.ExpressionList( + binary))); + + if (!stmt->match(pattern, &_matcher)) { + outerCallName = _builder.IdExpression(); + binary = _builder.BinaryExpression(); + // the expansion of Q_ASSERT(...), + // ((!(... == ...)) ? qt_assert(...) : ...); + pattern = _builder.ExpressionStatement( + _builder.NestedExpression( + _builder.ConditionalExpression( + _builder.NestedExpression( + _builder.UnaryExpression( + _builder.NestedExpression( + binary))), + _builder.Call(outerCallName)))); + + if (!stmt->match(pattern, &_matcher)) + return null; + } + + const QString outerCall = stringOf(outerCallName); + if (outerCall != QLatin1String("qt_assert") + && outerCall != QLatin1String("assert") + && outerCall != QLatin1String("Q_ASSERT")) + return null; + + if (translationUnit()->tokenAt(binary->binary_op_token).kind() != T_EQUAL_EQUAL) + return null; + + ExpressionAST *lhsExp = skipStringCall(binary->left_expression); + ExpressionAST *rhsExp = skipStringCall(binary->right_expression); + if (!lhsExp || !rhsExp) + return null; + + StringLiteralAST *uriString = lhsExp->asStringLiteral(); + IdExpressionAST *uriArgName = lhsExp->asIdExpression(); + if (!uriString) + uriString = rhsExp->asStringLiteral(); + if (!uriArgName) + uriArgName = rhsExp->asIdExpression(); + if (!uriString || !uriArgName) + return null; + + if (stringOf(uriArgName) != stringOf(uriName)) + return null; + + const StringLiteral *packageLit = translationUnit()->stringLiteral(uriString->literal_token); + return QString::fromUtf8(packageLit->chars(), packageLit->size()); + } +}; + +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::Ptr fmo, Class *klass, + QHash<Class *, LanguageUtils::FakeMetaObject::Ptr> *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; + 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(); + const int revision = 0; // ### fixme + FakeMetaProperty property( + namePrinter(propDecl->name()), + toQmlType(type), + isList, isWritable, isPointer, + revision); + 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::Ptr baseFmo = classes->value(baseClass); + if (!baseFmo) { + baseFmo = FakeMetaObject::Ptr(new FakeMetaObject); + populate(baseFmo, baseClass, classes, typeOf); + } + } +} + +QList<LanguageUtils::FakeMetaObject::ConstPtr> exportedQmlObjects( + const Document::Ptr &doc, + const Snapshot &snapshot, + const QList<ExportedQmlType> &exportedTypes) +{ + using namespace LanguageUtils; + QList<FakeMetaObject::ConstPtr> exportedObjects; + QHash<Class *, FakeMetaObject::Ptr> classes; + + if (exportedTypes.isEmpty()) + return exportedObjects; + + TypeOfExpression typeOf; + typeOf.init(doc, snapshot); + foreach (const ExportedQmlType &exportedType, exportedTypes) { + FakeMetaObject::Ptr fmo(new FakeMetaObject); + fmo->addExport(exportedType.typeName, + exportedType.packageName, + exportedType.version); + 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()), QmlJS::CppQmlTypes::cppPackage, ComponentVersion()); + + populate(fmo, klass, &classes, typeOf); + } + + return exportedObjects; +} + +} // anonymous namespace + +FindExportedCppTypes::FindExportedCppTypes(const CPlusPlus::Snapshot &snapshot) + : m_snapshot(snapshot) +{ +} + +QList<LanguageUtils::FakeMetaObject::ConstPtr> FindExportedCppTypes::operator()(const CPlusPlus::Document::Ptr &document) +{ + QList<LanguageUtils::FakeMetaObject::ConstPtr> noResults; + if (document->source().isEmpty() + || !document->translationUnit()->ast()) + return noResults; + + FindExportsVisitor finder(document); + QList<ExportedQmlType> exports = finder(); + if (exports.isEmpty()) + return noResults; + + return exportedQmlObjects(document, m_snapshot, exports); +} + +bool FindExportedCppTypes::maybeExportsTypes(const Document::Ptr &document) +{ + if (!document->control()) + return false; + const QByteArray qmlRegisterTypeToken("qmlRegisterType"); + if (document->control()->findIdentifier( + qmlRegisterTypeToken.constData(), qmlRegisterTypeToken.size())) { + return true; + } + return false; +} diff --git a/src/plugins/qmljstools/qmljsfindexportedcpptypes.h b/src/plugins/qmljstools/qmljsfindexportedcpptypes.h new file mode 100644 index 00000000000..304894e9439 --- /dev/null +++ b/src/plugins/qmljstools/qmljsfindexportedcpptypes.h @@ -0,0 +1,55 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (info@qt.nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at info@qt.nokia.com. +** +**************************************************************************/ + +#ifndef QMLJSTOOLS_QMLJSFINDEXPORTEDCPPTYPES_H +#define QMLJSTOOLS_QMLJSFINDEXPORTEDCPPTYPES_H + +#include <cplusplus/CppDocument.h> +#include <languageutils/fakemetaobject.h> + +namespace QmlJSTools { + +class FindExportedCppTypes +{ +public: + FindExportedCppTypes(const CPlusPlus::Snapshot &snapshot); + QList<LanguageUtils::FakeMetaObject::ConstPtr> operator()(const CPlusPlus::Document::Ptr &document); + + static bool maybeExportsTypes(const CPlusPlus::Document::Ptr &document); + +private: + CPlusPlus::Snapshot m_snapshot; +}; + +} // namespace QmlJSTools + +#endif // QMLJSTOOLS_QMLJSFINDEXPORTEDCPPTYPES_H diff --git a/src/plugins/qmljstools/qmljsmodelmanager.cpp b/src/plugins/qmljstools/qmljsmodelmanager.cpp index 6ff2c895504..545761ad7e1 100644 --- a/src/plugins/qmljstools/qmljsmodelmanager.cpp +++ b/src/plugins/qmljstools/qmljsmodelmanager.cpp @@ -33,6 +33,7 @@ #include "qmljsmodelmanager.h" #include "qmljstoolsconstants.h" #include "qmljsplugindumper.h" +#include "qmljsfindexportedcpptypes.h" #include <coreplugin/icore.h> #include <coreplugin/editormanager/editormanager.h> @@ -41,8 +42,6 @@ #include <coreplugin/messagemanager.h> #include <cplusplus/ModelManagerInterface.h> #include <cplusplus/CppDocument.h> -#include <cplusplus/TypeOfExpression.h> -#include <cplusplus/Overview.h> #include <qmljs/qmljscontext.h> #include <qmljs/qmljsbind.h> #include <qmljs/parser/qmldirparser_p.h> @@ -98,8 +97,10 @@ void ModelManager::delayedInitialization() CPlusPlus::CppModelManagerInterface *cppModelManager = CPlusPlus::CppModelManagerInterface::instance(); if (cppModelManager) { + // It's important to have a direct connection here so we can prevent + // the source and AST of the cpp document being cleaned away. connect(cppModelManager, SIGNAL(documentUpdated(CPlusPlus::Document::Ptr)), - this, SLOT(queueCppQmlTypeUpdate(CPlusPlus::Document::Ptr))); + this, SLOT(maybeQueueCppQmlTypeUpdate(CPlusPlus::Document::Ptr)), Qt::DirectConnection); } } @@ -673,9 +674,25 @@ void ModelManager::loadPluginTypes(const QString &libraryPath, const QString &im m_pluginDumper->loadPluginTypes(libraryPath, importPath, importUri, importVersion); } -void ModelManager::queueCppQmlTypeUpdate(const CPlusPlus::Document::Ptr &doc) +// is called *inside a c++ parsing thread*, to allow hanging on to source and ast +void ModelManager::maybeQueueCppQmlTypeUpdate(const CPlusPlus::Document::Ptr &doc) { - m_queuedCppDocuments.insert(doc->fileName()); + // keep source and AST alive if we want to scan for register calls + const bool scan = FindExportedCppTypes::maybeExportsTypes(doc); + if (scan) + doc->keepSourceAndAST(); + + // delegate actual queuing to the gui thread + QMetaObject::invokeMethod(this, "queueCppQmlTypeUpdate", + Q_ARG(CPlusPlus::Document::Ptr, doc), Q_ARG(bool, scan)); +} + +void ModelManager::queueCppQmlTypeUpdate(const CPlusPlus::Document::Ptr &doc, bool scan) +{ + QPair<CPlusPlus::Document::Ptr, bool> prev = m_queuedCppDocuments.value(doc->fileName()); + if (prev.first && prev.second) + prev.first->releaseSourceAndAST(); + m_queuedCppDocuments.insert(doc->fileName(), qMakePair(doc, scan)); m_updateCppQmlTypesTimer->start(); } @@ -691,20 +708,32 @@ void ModelManager::startCppQmlTypeUpdate() m_queuedCppDocuments.clear(); } -void ModelManager::updateCppQmlTypes(ModelManager *qmlModelManager, CPlusPlus::CppModelManagerInterface *cppModelManager, QSet<QString> files) +void ModelManager::updateCppQmlTypes(ModelManager *qmlModelManager, + CPlusPlus::CppModelManagerInterface *cppModelManager, + QMap<QString, QPair<CPlusPlus::Document::Ptr, bool> > documents) { CppQmlTypeHash newCppTypes = qmlModelManager->cppQmlTypes(); CPlusPlus::Snapshot snapshot = cppModelManager->snapshot(); + FindExportedCppTypes finder(snapshot); + + typedef QPair<CPlusPlus::Document::Ptr, bool> DocScanPair; + foreach (const DocScanPair &pair, documents) { + CPlusPlus::Document::Ptr doc = pair.first; + const bool scan = pair.second; + const QString fileName = doc->fileName(); + if (!scan) { + newCppTypes.remove(fileName); + continue; + } + + QList<LanguageUtils::FakeMetaObject::ConstPtr> exported = finder(doc); - foreach (const QString &fileName, files) { - CPlusPlus::Document::Ptr doc = snapshot.document(fileName); - QList<LanguageUtils::FakeMetaObject::ConstPtr> exported; - if (doc) - exported = cppModelManager->exportedQmlObjects(doc); if (!exported.isEmpty()) newCppTypes[fileName] = exported; else newCppTypes.remove(fileName); + + doc->releaseSourceAndAST(); } QMutexLocker locker(&qmlModelManager->m_cppTypesMutex); diff --git a/src/plugins/qmljstools/qmljsmodelmanager.h b/src/plugins/qmljstools/qmljsmodelmanager.h index 7fe82145b38..0a8c26fdbd1 100644 --- a/src/plugins/qmljstools/qmljsmodelmanager.h +++ b/src/plugins/qmljstools/qmljsmodelmanager.h @@ -117,12 +117,15 @@ protected: void updateImportPaths(); private slots: - void queueCppQmlTypeUpdate(const CPlusPlus::Document::Ptr &doc); + void maybeQueueCppQmlTypeUpdate(const CPlusPlus::Document::Ptr &doc); + void queueCppQmlTypeUpdate(const CPlusPlus::Document::Ptr &doc, bool scan); void startCppQmlTypeUpdate(); private: static bool matchesMimeType(const Core::MimeType &fileMimeType, const Core::MimeType &knownMimeType); - static void updateCppQmlTypes(ModelManager *qmlModelManager, CPlusPlus::CppModelManagerInterface *cppModelManager, QSet<QString> files); + static void updateCppQmlTypes(ModelManager *qmlModelManager, + CPlusPlus::CppModelManagerInterface *cppModelManager, + QMap<QString, QPair<CPlusPlus::Document::Ptr, bool> > documents); mutable QMutex m_mutex; Core::ICore *m_core; @@ -134,7 +137,7 @@ private: QFutureSynchronizer<void> m_synchronizer; QTimer *m_updateCppQmlTypesTimer; - QSet<QString> m_queuedCppDocuments; + QMap<QString, QPair<CPlusPlus::Document::Ptr, bool> > m_queuedCppDocuments; CppQmlTypeHash m_cppTypes; mutable QMutex m_cppTypesMutex; diff --git a/src/plugins/qmljstools/qmljstools-lib.pri b/src/plugins/qmljstools/qmljstools-lib.pri index 2d5cad5b231..596f28816e0 100644 --- a/src/plugins/qmljstools/qmljstools-lib.pri +++ b/src/plugins/qmljstools/qmljstools-lib.pri @@ -17,7 +17,8 @@ HEADERS += \ $$PWD/qmljsfunctionfilter.h \ $$PWD/qmljslocatordata.h \ $$PWD/qmljsindenter.h \ - $$PWD/qmljscodestylesettingspage.h + $$PWD/qmljscodestylesettingspage.h \ + $$PWD/qmljsfindexportedcpptypes.h SOURCES += \ $$PWD/qmljstoolsplugin.cpp \ @@ -30,7 +31,8 @@ SOURCES += \ $$PWD/qmljsfunctionfilter.cpp \ $$PWD/qmljslocatordata.cpp \ $$PWD/qmljsindenter.cpp \ - $$PWD/qmljscodestylesettingspage.cpp + $$PWD/qmljscodestylesettingspage.cpp \ + $$PWD/qmljsfindexportedcpptypes.cpp FORMS += \ $$PWD/qmljscodestylesettingspage.ui -- GitLab