aboutsummaryrefslogtreecommitdiffstats
path: root/tools/qmltc
diff options
context:
space:
mode:
authorAndrei Golubev <andrei.golubev@qt.io>2021-10-28 14:18:04 +0200
committerAndrei Golubev <andrei.golubev@qt.io>2021-11-15 12:00:52 +0100
commit539a08d1f4e4a672c33ccb21e4db5770491ebadb (patch)
tree68571a626c61055baa3ff9a2b2328816b2449757 /tools/qmltc
parent4bfc7847f294fe63878a91ef7180b036efc09b70 (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.cpp3
-rw-r--r--tools/qmltc/qmltccompiler.cpp14
-rw-r--r--tools/qmltc/qmltcoutputir.h1
-rw-r--r--tools/qmltc/qmltcvisitor.cpp119
-rw-r--r--tools/qmltc/qmltcvisitor.h11
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 &current, 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 &current, 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 &param : 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