Commit 27d08306 authored by Christian Kamm's avatar Christian Kamm

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/3392Reviewed-by: default avatarFawzi Mohamed <fawzi.mohamed@nokia.com>
parent c105ae47
......@@ -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()
......
......@@ -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;
};
......
......@@ -140,8 +140,6 @@ public:
virtual void findMacroUsages(const CPlusPlus::Macro &macro) = 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);
......
......@@ -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);
......
......@@ -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);
......
......@@ -128,8 +128,6 @@ public:
virtual void findMacroUsages(const CPlusPlus::Macro &macro);
virtual QList<LanguageUtils::FakeMetaObject::ConstPtr> exportedQmlObjects(const CPlusPlus::Document::Ptr &doc) const;
void finishedRefreshingSourceFiles(const QStringList &files);
Q_SIGNALS:
......
/**************************************************************************
**
** 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.
**
**************************************************************************/