diff options
author | Andrei Golubev <andrei.golubev@qt.io> | 2021-10-28 14:18:04 +0200 |
---|---|---|
committer | Andrei Golubev <andrei.golubev@qt.io> | 2021-11-15 12:00:52 +0100 |
commit | 539a08d1f4e4a672c33ccb21e4db5770491ebadb (patch) | |
tree | 68571a626c61055baa3ff9a2b2328816b2449757 /tools/qmltc | |
parent | 4bfc7847f294fe63878a91ef7180b036efc09b70 (diff) |
qmltc: Learn to collect C++ includes for a type hierarchy
We need this to be able to properly include types, properties, etc.
The strategy right now is very exhaustive but it seems necessary (afair)
to be able to support weird cases with non-trivial forward declarations
As a drive-by, fix some mistakes in QQmlJSScopeVisitor and its qmltc's
counterpart
Task-number: QTBUG-84368
Change-Id: I97719222598af85886821c0bf6e0e788770c8924
Reviewed-by: Ulf Hermann <ulf.hermann@qt.io>
Diffstat (limited to 'tools/qmltc')
-rw-r--r-- | tools/qmltc/qmltccodewriter.cpp | 3 | ||||
-rw-r--r-- | tools/qmltc/qmltccompiler.cpp | 14 | ||||
-rw-r--r-- | tools/qmltc/qmltcoutputir.h | 1 | ||||
-rw-r--r-- | tools/qmltc/qmltcvisitor.cpp | 119 | ||||
-rw-r--r-- | tools/qmltc/qmltcvisitor.h | 11 |
5 files changed, 145 insertions, 3 deletions
diff --git a/tools/qmltc/qmltccodewriter.cpp b/tools/qmltc/qmltccodewriter.cpp index d9d29dccd3..3039b10da7 100644 --- a/tools/qmltc/qmltccodewriter.cpp +++ b/tools/qmltc/qmltccodewriter.cpp @@ -209,6 +209,9 @@ void QmltcCodeWriter::write(QmltcOutputWrapper &code, const QmltcType &type) for (const QString &mocLine : qAsConst(type.mocCode)) code.rawAppendToHeader(mocLine, 1); + for (const QString &otherLine : qAsConst(type.otherCode)) + code.rawAppendToHeader(otherLine, 1); + QmltcOutputWrapper::MemberNameScope typeScope(&code, type.cppType); Q_UNUSED(typeScope); { diff --git a/tools/qmltc/qmltccompiler.cpp b/tools/qmltc/qmltccompiler.cpp index c6d463c671..e792e0787a 100644 --- a/tools/qmltc/qmltccompiler.cpp +++ b/tools/qmltc/qmltccompiler.cpp @@ -65,6 +65,7 @@ void QmltcCompiler::compile(const QmltcCompilerInfo &info) program.cppPath = m_info.outputCppFile; program.hPath = m_info.outputHFile; program.compiledTypes = compiledTypes; + program.includes = m_visitor->cppIncludeFiles(); QmltcOutput out; QmltcOutputWrapper code(out); @@ -78,11 +79,14 @@ void QmltcCompiler::compileType(QmltcType ¤t, const QQmlJSScope::ConstPtr return; } + Q_ASSERT(!type->internalName().isEmpty()); current.cppType = type->internalName(); + Q_ASSERT(!type->baseType()->internalName().isEmpty()); const QString baseClass = type->baseType()->internalName(); const bool documentRoot = (type == m_visitor->result()); const bool baseTypeIsCompiledQml = false; // TODO: support this in QmltcTypeResolver + const bool isAnonymous = type->internalName().at(0).isLower(); current.baseClasses = { baseClass }; if (!documentRoot) { @@ -96,6 +100,16 @@ void QmltcCompiler::compileType(QmltcType ¤t, const QQmlJSScope::ConstPtr current.init.access = QQmlJSMetaMethod::Protected; current.finalize.access = QQmlJSMetaMethod::Protected; current.fullCtor.access = QQmlJSMetaMethod::Public; + if (!documentRoot) { + // make document root a friend to allow it to access init and finalize + auto root = m_visitor->result(); + current.otherCode << u"friend class %1;"_qs.arg(root->internalName()); + } + current.mocCode = { + u"Q_OBJECT"_qs, + // Note: isAnonymous holds for non-root types in the document as well + isAnonymous ? u"QML_ANONYMOUS"_qs : u"QML_ELEMENT"_qs, + }; current.basicCtor.name = current.cppType; current.fullCtor.name = current.cppType; diff --git a/tools/qmltc/qmltcoutputir.h b/tools/qmltc/qmltcoutputir.h index ced2c48f86..dd40ac5ea5 100644 --- a/tools/qmltc/qmltcoutputir.h +++ b/tools/qmltc/qmltcoutputir.h @@ -99,6 +99,7 @@ struct QmltcType QString cppType; // C++ type of the QML type QStringList baseClasses; // C++ type names of base classes QStringList mocCode; // Qt MOC code + QStringList otherCode; // Random code that doesn't fit any category, e.g. friend declarations // member types: enumerations and child types QList<QmltcEnum> enums; diff --git a/tools/qmltc/qmltcvisitor.cpp b/tools/qmltc/qmltcvisitor.cpp index 5396f5368f..04208da4a4 100644 --- a/tools/qmltc/qmltcvisitor.cpp +++ b/tools/qmltc/qmltcvisitor.cpp @@ -50,27 +50,140 @@ QmltcVisitor::QmltcVisitor(QQmlJSImporter *importer, QQmlJSLogger *logger, m_qmlTypeNames.append(QFileInfo(logger->fileName()).baseName()); // put document root } +void QmltcVisitor::findCppIncludes() +{ + // TODO: this pass is fairly slow: we have to do exhaustive search because + // some C++ code could do forward declarations + QSet<const QQmlJSScope *> visitedTypes; // we can still improve by walking all types only once + const auto visitType = [&visitedTypes](const QQmlJSScope::ConstPtr &type) -> bool { + if (visitedTypes.contains(type.data())) + return true; + visitedTypes.insert(type.data()); + return false; + }; + + const auto populateFromType = [&](const QQmlJSScope::ConstPtr &type) { + if (!type) // TODO: it is a crutch + return; + if (visitType(type)) // optimization - don't call nonCompositeBaseType() needlessly + return; + auto t = QQmlJSScope::nonCompositeBaseType(type); + if (t != type && visitType(t)) + return; + + QString includeFile = t->fileName(); + if (!includeFile.isEmpty()) + m_cppIncludes.insert(std::move(includeFile)); + }; + + const auto constructPrivateInclude = [](QStringView publicInclude) -> QString { + if (publicInclude.isEmpty()) + return QString(); + Q_ASSERT(publicInclude.endsWith(u".h"_qs) || publicInclude.endsWith(u".hpp"_qs)); + const qsizetype dotLocation = publicInclude.lastIndexOf(u'.'); + QStringView extension = publicInclude.sliced(dotLocation); + QStringView includeWithoutExtension = publicInclude.first(dotLocation); + // check if "public" include is in fact already private + if (publicInclude.startsWith(u"private")) + return includeWithoutExtension + u"_p" + extension; + return u"private/" + includeWithoutExtension + u"_p" + extension; + }; + + // walk the whole type hierarchy + for (const QQmlJSScope::ConstPtr &type : qAsConst(m_qmlTypes)) { + // TODO: figure how to NOT walk all the types. theoretically, we can + // stop at first non-composite type + for (auto t = type; t; t = t->baseType()) { + if (visitType(t)) + break; + // look in type + if (auto includeFile = t->fileName(); !includeFile.isEmpty()) + m_cppIncludes.insert(std::move(includeFile)); + + // look in properties + const auto properties = t->ownProperties(); + for (const QQmlJSMetaProperty &p : properties) { + populateFromType(p.type()); + + if (p.isPrivate()) { + const QString ownersInclude = t->fileName(); + QString privateInclude = constructPrivateInclude(ownersInclude); + if (!privateInclude.isEmpty()) + m_cppIncludes.insert(std::move(privateInclude)); + } + } + + // look in methods + const auto methods = t->ownMethods(); + for (const QQmlJSMetaMethod &m : methods) { + populateFromType(m.returnType()); + // TODO: debug Q_ASSERT(m.returnType()) + const auto parameters = m.parameterTypes(); + for (const auto ¶m : parameters) + populateFromType(param); + } + } + } +} + bool QmltcVisitor::visit(QQmlJS::AST::UiObjectDefinition *object) { if (!QQmlJSImportVisitor::visit(object)) return false; - Q_ASSERT(m_currentScope->scopeType() == QQmlJSScope::QMLScope); + // we're not interested in non-QML scopes + if (m_currentScope->scopeType() != QQmlJSScope::QMLScope) + return true; + Q_ASSERT(m_currentScope->internalName().isEmpty()); Q_ASSERT(!m_currentScope->baseTypeName().isEmpty()); - if (m_currentScope != m_exportedRootScope) // not document root m_qmlTypeNames.append(m_currentScope->baseTypeName()); + // give C++-relevant internal names to QMLScopes, we can use them later in compiler + m_currentScope->setInternalName(uniqueNameFromPieces(m_qmlTypeNames, m_qmlTypeNameCounts)); + + if (auto base = m_currentScope->baseType(); base && base->isComposite()) + m_qmlTypesWithQmlBases.append(m_currentScope); + return true; +} + +void QmltcVisitor::endVisit(QQmlJS::AST::UiObjectDefinition *object) +{ + m_qmlTypeNames.removeLast(); + QQmlJSImportVisitor::endVisit(object); +} + +bool QmltcVisitor::visit(QQmlJS::AST::UiObjectBinding *uiob) +{ + if (!QQmlJSImportVisitor::visit(uiob)) + return false; + + Q_ASSERT(m_currentScope->scopeType() == QQmlJSScope::QMLScope); + Q_ASSERT(m_currentScope->internalName().isEmpty()); + Q_ASSERT(!m_currentScope->baseTypeName().isEmpty()); + if (m_currentScope != m_exportedRootScope) // not document root + m_qmlTypeNames.append(m_currentScope->baseTypeName()); // give C++-relevant internal names to QMLScopes, we can use them later in compiler m_currentScope->setInternalName(uniqueNameFromPieces(m_qmlTypeNames, m_qmlTypeNameCounts)); + if (auto base = m_currentScope->baseType(); base && base->isComposite()) + m_qmlTypesWithQmlBases.append(m_currentScope); + return true; } -void QmltcVisitor::endVisit(QQmlJS::AST::UiObjectDefinition *) +void QmltcVisitor::endVisit(QQmlJS::AST::UiObjectBinding *uiob) { m_qmlTypeNames.removeLast(); + QQmlJSImportVisitor::endVisit(uiob); +} + +void QmltcVisitor::endVisit(QQmlJS::AST::UiProgram *program) +{ + QQmlJSImportVisitor::endVisit(program); + + findCppIncludes(); } QT_END_NAMESPACE diff --git a/tools/qmltc/qmltcvisitor.h b/tools/qmltc/qmltcvisitor.h index 870e9863d1..de04f6977e 100644 --- a/tools/qmltc/qmltcvisitor.h +++ b/tools/qmltc/qmltcvisitor.h @@ -41,6 +41,8 @@ QT_BEGIN_NAMESPACE class QmltcVisitor : public QQmlJSImportVisitor { + void findCppIncludes(); + public: QmltcVisitor(QQmlJSImporter *importer, QQmlJSLogger *logger, const QString &implicitImportDirectory, @@ -49,12 +51,21 @@ public: bool visit(QQmlJS::AST::UiObjectDefinition *) override; void endVisit(QQmlJS::AST::UiObjectDefinition *) override; + bool visit(QQmlJS::AST::UiObjectBinding *) override; + void endVisit(QQmlJS::AST::UiObjectBinding *) override; + + void endVisit(QQmlJS::AST::UiProgram *) override; + // NB: overwrite result() method to return ConstPtr QQmlJSScope::ConstPtr result() const { return QQmlJSImportVisitor::result(); } + QList<QQmlJSScope::ConstPtr> qmlScopesWithQmlBases() const { return m_qmlTypesWithQmlBases; } + QSet<QString> cppIncludeFiles() const { return m_cppIncludes; } protected: QStringList m_qmlTypeNames; // names of QML types arranged as a stack QHash<QString, int> m_qmlTypeNameCounts; + QList<QQmlJSScope::ConstPtr> m_qmlTypesWithQmlBases; // QML types with composite/QML base types + QSet<QString> m_cppIncludes; // all C++ includes found from QQmlJSScope hierarchy }; QT_END_NAMESPACE |