diff options
-rw-r--r-- | src/qml/doc/snippets/qmltc/special/HelloWorld.qml.cpp | 2 | ||||
-rw-r--r-- | src/qml/qmltc/qqmltcobjectcreationhelper_p.h | 15 | ||||
-rw-r--r-- | src/qmlcompiler/qqmljsimportvisitor.cpp | 5 | ||||
-rw-r--r-- | src/qmlcompiler/qqmljsimportvisitor_p.h | 2 | ||||
-rw-r--r-- | src/qmlcompiler/qqmljsscope_p.h | 4 | ||||
-rw-r--r-- | tools/qmltc/prototype/codegenerator.cpp | 557 | ||||
-rw-r--r-- | tools/qmltc/prototype/codegenerator.h | 32 | ||||
-rw-r--r-- | tools/qmltc/qmltccodewriter.cpp | 35 | ||||
-rw-r--r-- | tools/qmltc/qmltccompiler.cpp | 227 | ||||
-rw-r--r-- | tools/qmltc/qmltccompiler.h | 5 | ||||
-rw-r--r-- | tools/qmltc/qmltccompilerpieces.h | 279 | ||||
-rw-r--r-- | tools/qmltc/qmltcoutputir.h | 7 | ||||
-rw-r--r-- | tools/qmltc/qmltcvisitor.cpp | 224 | ||||
-rw-r--r-- | tools/qmltc/qmltcvisitor.h | 52 |
14 files changed, 803 insertions, 643 deletions
diff --git a/src/qml/doc/snippets/qmltc/special/HelloWorld.qml.cpp b/src/qml/doc/snippets/qmltc/special/HelloWorld.qml.cpp index 122084f1d8..0e125a1926 100644 --- a/src/qml/doc/snippets/qmltc/special/HelloWorld.qml.cpp +++ b/src/qml/doc/snippets/qmltc/special/HelloWorld.qml.cpp @@ -69,7 +69,7 @@ class HelloWorld : public QObject Q_PROPERTY(QString hello WRITE setHello READ hello BINDABLE bindableHello) public: - HelloWorld(QQmlEngine * engine, QObject * parent = nullptr); + HelloWorld(QQmlEngine* engine, QObject* parent = nullptr); Q_SIGNALS: void created(); diff --git a/src/qml/qmltc/qqmltcobjectcreationhelper_p.h b/src/qml/qmltc/qqmltcobjectcreationhelper_p.h index e34bb5506a..6aa37b1e53 100644 --- a/src/qml/qmltc/qqmltcobjectcreationhelper_p.h +++ b/src/qml/qmltc/qqmltcobjectcreationhelper_p.h @@ -70,17 +70,15 @@ class QQmltcObjectCreationHelper QObject **m_data = nullptr; // QObject* array const qsizetype m_size = 0; // size of m_data array, exists for bounds checking const qsizetype m_offset = 0; // global offset into m_data array - const qsizetype m_nonRoot = 1; // addresses the "+ 1" in QQmltcObjectCreationBase::m_objects - qsizetype offset() const { return m_offset + m_nonRoot; } + qsizetype offset() const { return m_offset; } public: /*! Constructs initial "view" from basic data. Supposed to only be called once from QQmltcObjectCreationBase. */ - QQmltcObjectCreationHelper(QObject **data, qsizetype size) - : m_data(data), m_size(size), m_nonRoot(0 /* root object */) + QQmltcObjectCreationHelper(QObject **data, qsizetype size) : m_data(data), m_size(size) { Q_UNUSED(m_size); } @@ -92,7 +90,6 @@ public: QQmltcObjectCreationHelper(const QQmltcObjectCreationHelper *base, qsizetype localOffset) : m_data(base->m_data), m_size(base->m_size), m_offset(base->m_offset + localOffset) { - Q_ASSERT(m_nonRoot == 1); // sanity check - a sub-creator is for non-root object } template<typename T> @@ -113,6 +110,12 @@ public: Q_ASSERT(m_data[i + offset()] == nullptr); // prevent accidental resets m_data[i + offset()] = object; } + + template<typename T> + static constexpr uint typeCount() noexcept + { + return T::q_qmltc_typeCount(); + } }; /*! @@ -125,7 +128,7 @@ template<typename QmltcGeneratedType> class QQmltcObjectCreationBase { // Note: +1 for the document root itself - std::array<QObject *, QmltcGeneratedType::q_qmltc_typeCount + 1> m_objects = {}; + std::array<QObject *, QmltcGeneratedType::q_qmltc_typeCount() + 1> m_objects = {}; public: QQmltcObjectCreationHelper view() diff --git a/src/qmlcompiler/qqmljsimportvisitor.cpp b/src/qmlcompiler/qqmljsimportvisitor.cpp index 04f34d83a2..deb6b22331 100644 --- a/src/qmlcompiler/qqmljsimportvisitor.cpp +++ b/src/qmlcompiler/qqmljsimportvisitor.cpp @@ -112,8 +112,6 @@ QQmlJSImportVisitor::QQmlJSImportVisitor(QQmlJSImporter *importer, QQmlJSLogger } for (const auto &jsGlobVar : jsGlobVars) m_currentScope->insertJSIdentifier(jsGlobVar, globalJavaScript); - - m_runtimeIdCounters.push(0); // global (this document's) runtime id counter } QQmlJSImportVisitor::~QQmlJSImportVisitor() = default; @@ -1255,13 +1253,11 @@ bool QQmlJSImportVisitor::visit(UiInlineComponent *component) m_nextIsInlineComponent = true; m_inlineComponentName = component->name; - m_runtimeIdCounters.push(0); // add new id counter, since counters are component-local return true; } void QQmlJSImportVisitor::endVisit(UiInlineComponent *) { - m_runtimeIdCounters.pop(); m_inlineComponentName = QStringView(); Q_ASSERT(!m_nextIsInlineComponent); } @@ -1576,7 +1572,6 @@ void QQmlJSImportVisitor::handleIdDeclaration(QQmlJS::AST::UiScriptBinding *scri } if (!name.isEmpty()) m_scopesById.insert(name, m_currentScope); - m_currentScope->setRuntimeId(m_runtimeIdCounters.top()++); } bool QQmlJSImportVisitor::visit(UiScriptBinding *scriptBinding) diff --git a/src/qmlcompiler/qqmljsimportvisitor_p.h b/src/qmlcompiler/qqmljsimportvisitor_p.h index f89aa03dc5..69714ced36 100644 --- a/src/qmlcompiler/qqmljsimportvisitor_p.h +++ b/src/qmlcompiler/qqmljsimportvisitor_p.h @@ -290,8 +290,6 @@ protected: QSet<QQmlJSScope::ConstPtr> m_literalScopesToCheck; QQmlJS::SourceLocation m_pendingSignalHandler; - QStack<int> m_runtimeIdCounters; - private: void importBaseModules(); void resolveAliasesAndIds(); diff --git a/src/qmlcompiler/qqmljsscope_p.h b/src/qmlcompiler/qqmljsscope_p.h index 4e692a0d01..da670f0c71 100644 --- a/src/qmlcompiler/qqmljsscope_p.h +++ b/src/qmlcompiler/qqmljsscope_p.h @@ -430,9 +430,6 @@ public: AccessSemantics accessSemantics() const { return m_semantics; } bool isReferenceType() const { return m_semantics == QQmlJSScope::AccessSemantics::Reference; } - void setRuntimeId(int id) { m_runtimeId = id; } - int runtimeId() const { return m_runtimeId; } - bool isIdInCurrentQmlScopes(const QString &id) const; bool isIdInCurrentJSScopes(const QString &id) const; bool isIdInjectedFromSignal(const QString &id) const; @@ -617,7 +614,6 @@ private: AccessSemantics m_semantics = AccessSemantics::Reference; QQmlJS::SourceLocation m_sourceLocation; - int m_runtimeId = -1; // an index counterpart of "foobar" in `id: foobar` }; Q_DECLARE_TYPEINFO(QQmlJSScope::QmlIRCompatibilityBindingData, Q_RELOCATABLE_TYPE); diff --git a/tools/qmltc/prototype/codegenerator.cpp b/tools/qmltc/prototype/codegenerator.cpp index 599bf9fba1..6be93d7756 100644 --- a/tools/qmltc/prototype/codegenerator.cpp +++ b/tools/qmltc/prototype/codegenerator.cpp @@ -157,6 +157,7 @@ QHash<uint, qsizetype> QmlIrBindingCompare::orderTable = { { QmlIR::Binding::Type_Number, 0 }, { QmlIR::Binding::Type_String, 0 }, { QmlIR::Binding::Type_Object, 0 }, + { QmlIR::Binding::Type_Null, 0 }, // translations are also "equal" between themselves, but come after // assignments { QmlIR::Binding::Type_Translation, 1 }, @@ -167,12 +168,12 @@ QHash<uint, qsizetype> QmlIrBindingCompare::orderTable = { { QmlIR::Binding::Type_GroupProperty, 2 }, // JS bindings come last because they can use values from other categories { QmlIR::Binding::Type_Script, 3 }, - // { QmlIR::Binding::Type_Null, 100 }, // TODO: what is this used for? }; -static QList<typename QmlIR::PoolList<QmlIR::Binding>::Iterator> -toOrderedSequence(typename QmlIR::PoolList<QmlIR::Binding>::Iterator first, - typename QmlIR::PoolList<QmlIR::Binding>::Iterator last, qsizetype n) +QList<typename QmlIR::PoolList<QmlIR::Binding>::Iterator> +CodeGenerator::toOrderedSequence(typename QmlIR::PoolList<QmlIR::Binding>::Iterator first, + typename QmlIR::PoolList<QmlIR::Binding>::Iterator last, + qsizetype n) { // bindings actually have to sorted so that e.g. value assignments come // before script bindings. this is important for the code generator as it @@ -184,7 +185,8 @@ toOrderedSequence(typename QmlIR::PoolList<QmlIR::Binding>::Iterator first, sorted << it; // NB: use stable sort for bindings, because this matters: relative order of // bindings must be preserved - this might affect UI - std::stable_sort(sorted.begin(), sorted.end(), QmlIrBindingCompare {}); + std::stable_sort(sorted.begin(), sorted.end(), + QmlIrBindingCompare {}); // TODO: use stable_partition instead return sorted; } @@ -238,8 +240,14 @@ static const QmltcVariable compilationUnitVariable { u"QV4::ExecutableCompilatio Q_LOGGING_CATEGORY(lcCodeGen, "qml.compiler.CodeGenerator", QtWarningMsg); CodeGenerator::CodeGenerator(const QString &url, QQmlJSLogger *logger, QmlIR::Document *doc, - const QmltcTypeResolver *localResolver, const QmltcCompilerInfo *info) - : m_url(url), m_logger(logger), m_doc(doc), m_localTypeResolver(localResolver), m_info(info) + const QmltcTypeResolver *localResolver, const QmltcVisitor *visitor, + const QmltcCompilerInfo *info) + : m_url(url), + m_logger(logger), + m_doc(doc), + m_localTypeResolver(localResolver), + m_visitor(visitor), + m_info(info) { Q_ASSERT(m_info); Q_ASSERT(!m_info->outputHFile.isEmpty()); @@ -350,405 +358,10 @@ QString buildCallSpecialMethodValue(bool documentRoot, const QString &outerFlagN } } -void CodeGenerator::compileObject( - QmltcType &compiled, const CodeGenObject &object, - std::function<void(QmltcType &, const CodeGenObject &)> compileElements) +static QString generate_callCompilationUnit(const QString &urlMethodName) { - if (object.type->isSingleton()) { - recordError(object.type->sourceLocation(), u"Singleton types are not supported"_qs); - return; - } - - compiled.cppType = object.type->internalName(); - const QString baseClass = object.type->baseType()->internalName(); - - const bool baseTypeIsCompiledQml = m_qmlCompiledBaseTypes.contains(object.type->baseTypeName()); - const qsizetype objectIndex = m_typeToObjectIndex[object.type]; - const bool documentRoot = objectIndex == 0; - const bool hasParserStatusInterface = object.type->hasInterface(u"QQmlParserStatus"_qs); - const bool hasFinalizerHookInterface = object.type->hasInterface(u"QQmlFinalizerHook"_qs); - - compiled.baseClasses = { baseClass }; - - // add ctors code - compiled.baselineCtor.access = QQmlJSMetaMethod::Protected; - if (documentRoot) { - compiled.externalCtor.access = QQmlJSMetaMethod::Public; - } else { - compiled.externalCtor.access = QQmlJSMetaMethod::Protected; - } - compiled.init.access = QQmlJSMetaMethod::Protected; - compiled.endInit.access = QQmlJSMetaMethod::Protected; - compiled.completeComponent.access = QQmlJSMetaMethod::Protected; - compiled.finalizeComponent.access = QQmlJSMetaMethod::Protected; - compiled.handleOnCompleted.access = QQmlJSMetaMethod::Protected; - - compiled.baselineCtor.name = compiled.cppType; - compiled.externalCtor.name = compiled.cppType; - compiled.init.name = u"QML_init"_qs; - compiled.init.returnType = u"QQmlRefPointer<QQmlContextData>"_qs; - compiled.endInit.name = u"QML_endInit"_qs; - compiled.endInit.returnType = u"void"_qs; - compiled.completeComponent.name = u"QML_completeComponent"_qs; - compiled.completeComponent.returnType = u"void"_qs; - compiled.finalizeComponent.name = u"QML_finalizeComponent"_qs; - compiled.finalizeComponent.returnType = u"void"_qs; - compiled.handleOnCompleted.name = u"QML_handleOnCompleted"_qs; - compiled.handleOnCompleted.returnType = u"void"_qs; - - QmltcVariable engine(u"QQmlEngine *"_qs, u"engine"_qs, QString()); - QmltcVariable parent(u"QObject *"_qs, u"parent"_qs, u"nullptr"_qs); - compiled.baselineCtor.parameterList = { parent }; - compiled.externalCtor.parameterList = { engine, parent }; - QmltcVariable ctxtdata(u"const QQmlRefPointer<QQmlContextData> &"_qs, u"parentContext"_qs, - QString()); - QmltcVariable finalizeFlag(u"bool"_qs, u"canFinalize"_qs, QString()); - QmltcVariable callSpecialMethodFlag(u"bool"_qs, u"callSpecialMethodNow"_qs, QString()); - if (documentRoot) { - compiled.init.parameterList = { engine, ctxtdata, finalizeFlag, callSpecialMethodFlag }; - compiled.endInit.parameterList = { engine, finalizeFlag }; - compiled.completeComponent.parameterList = { callSpecialMethodFlag }; - compiled.finalizeComponent.parameterList = { callSpecialMethodFlag }; - } else { - compiled.init.parameterList = { engine, ctxtdata }; - compiled.endInit.parameterList = { engine, compilationUnitVariable }; - } - - if (!documentRoot) { - // make document root a friend to allow protected member function access - Q_ASSERT(m_objects[0].type); - compiled.otherCode << u"friend class %1;"_qs.arg(m_objects[0].type->internalName()); - // additionally, befriend the immediate parent of this type - if (auto parent = m_immediateParents.value(object.type); - parent && parent != m_objects[0].type) { - compiled.otherCode << u"friend class %1;"_qs.arg(parent->internalName()); - } - } - - if (baseTypeIsCompiledQml) { - // call baseline ctor of the QML-originated base class. it also takes - // care of QObject::setParent() call - compiled.baselineCtor.initializerList = { baseClass + u"(parent)" }; - } else { - // default call to ctor is enough, but QQml_setParent_noEvent() - is - // needed (note, this is a faster version of QObject::setParent()) - compiled.baselineCtor.body << u"QQml_setParent_noEvent(this, " + parent.name + u");"; - } - - compiled.externalCtor.initializerList = { compiled.baselineCtor.name + u"(parent)" }; - if (documentRoot) { - compiled.externalCtor.body << u"// document root:"_qs; - compiled.endInit.body << u"auto " + compilationUnitVariable.name + u" = " - + u"QQmlEnginePrivate::get(engine)->compilationUnitFromUrl(" - + m_urlMethodName + u"());"; - - // call init method of the document root - compiled.externalCtor.body << compiled.init.name - + u"(engine, QQmlContextData::get(engine->rootContext()), /* finalize */ " - u"true, /* call special method */ true);"; - } else { - compiled.externalCtor.body << u"// not document root:"_qs; - compiled.externalCtor.body - << compiled.init.name + u"(engine, QQmlData::get(parent)->outerContext);"; - } - - compiled.init.body << u"Q_UNUSED(engine);"_qs; - if (documentRoot) { - compiled.init.body << u"Q_UNUSED(" + callSpecialMethodFlag.name + u")"; - compiled.completeComponent.body << u"Q_UNUSED(" + callSpecialMethodFlag.name + u")"; - compiled.finalizeComponent.body << u"Q_UNUSED(" + callSpecialMethodFlag.name + u")"; - } - - // compiled.init.body << u"Q_UNUSED(" + finalizeFlag.name + u");"; - compiled.init.body << u"auto context = parentContext;"_qs; - // TODO: context hierarchy is way-over-the-top complicated already - - // -1. if the parent scope of this type has base type as compiled qml and - // this parent scope is not a root object, we have to go one level up in the - // context. in a nutshell: - // * parentScope->outerContext == parentContext of this type - // * parentScope->outerContext != context of this document - // * parentScope->outerContext is a child of context of this document - // > to ensure correct context, we must use context->parent() instead of - // > parentContext - if (QQmlJSScope::ConstPtr parent = object.type->parentScope(); parent - && m_qmlCompiledBaseTypes.contains(parent->baseTypeName()) - && m_typeToObjectIndex[parent] != 0) { - compiled.init.body << u"// NB: context->parent() is the context of the root "_qs; - compiled.init.body << u"context = context->parent();"_qs; - } - - // 0. call parent's init if necessary - if (baseTypeIsCompiledQml) { - compiled.init.body << u"// 0. call parent's init"_qs; - QString lhs; - if (documentRoot) - lhs = u"context = "_qs; - const QString callParserStatusSpecialMethod = buildCallSpecialMethodValue( - documentRoot, callSpecialMethodFlag.name, hasParserStatusInterface); - compiled.init.body << lhs + baseClass + u"::" + compiled.init.name - + u"(engine, context, /* finalize */ false, /* call special method */ " - + callParserStatusSpecialMethod + u");"; - - compiled.completeComponent.body << u"// call parent's completeComponent"_qs; - compiled.completeComponent.body << baseClass + u"::" + compiled.completeComponent.name - + u"(" + callParserStatusSpecialMethod + u");"; - - const QString callFinalizerHookSpecialMethod = buildCallSpecialMethodValue( - documentRoot, callSpecialMethodFlag.name, hasFinalizerHookInterface); - compiled.finalizeComponent.body << u"// call parent's finalizeComponent"_qs; - compiled.finalizeComponent.body << baseClass + u"::" + compiled.finalizeComponent.name - + u"(" + callFinalizerHookSpecialMethod + u");"; - - compiled.handleOnCompleted.body << u"// call parent's Component.onCompleted handler"_qs; - compiled.handleOnCompleted.body - << baseClass + u"::" + compiled.handleOnCompleted.name + u"();"; - } - // 1. create new context through QQmlCppContextRegistrator - if (documentRoot) { - Q_ASSERT(objectIndex == 0); - compiled.init.body << u"// 1. create context for this type (root)"_qs; - compiled.init.body - << QStringLiteral( - "context = %1->createInternalContext(%1->compilationUnitFromUrl(%2()), " - "context, 0, true);") - .arg(u"QQmlEnginePrivate::get(engine)"_qs, m_urlMethodName); - } else { - // non-root objects adopt parent context and use that one instead of - // creating own context - compiled.init.body << u"// 1. use current as context of this type (non-root)"_qs; - compiled.init.body << u"// context = context;"_qs; - } - - // TODO: optimize step 2: do we need context = parentContext? simplify - // QQmlCppContextRegistrator::set also - - // 2. - if (baseTypeIsCompiledQml && !documentRoot) { - } else { // !baseTypeIsCompiledQml || documentRoot - // set up current context - compiled.init.body << u"// 2. normal flow, set context for this object"_qs; - const QString enumValue = documentRoot ? u"DocumentRoot"_qs : u"OrdinaryObject"_qs; - compiled.init.body << QStringLiteral( - "%1->setInternalContext(this, context, QQmlContextData::%2);") - .arg(u"QQmlEnginePrivate::get(engine)"_qs, enumValue); - if (documentRoot) - compiled.init.body << u"context->setContextObject(this);"_qs; - } - // 3. set id if it's present in the QML document - if (!m_doc->stringAt(object.irObject->idNameIndex).isEmpty()) { - compiled.init.body << u"// 3. set id since it exists"_qs; - QmltcCodeGenerator::generate_setIdValue(&compiled.init.body, u"context"_qs, - object.irObject->id, u"this"_qs, - m_doc->stringAt(object.irObject->idNameIndex)); - } - - // TODO: we might want to optimize storage space when there are no object - // bindings, but this requires deep checking (e.g. basically go over all - // bindings and all bindings of attached/grouped properties) - compiled.init.body << u"// create objects for object bindings in advance:"_qs; - // TODO: support private and protected variables - compiled.variables.emplaceBack(childrenOffsetVariable); - compiled.init.body << childrenOffsetVariable.name + u" = QObject::children().size();"; - - // magic step: if the type has a QQmlParserStatus interface, we should call - // it's method here - if (hasParserStatusInterface) { - const QString indent = documentRoot ? u" "_qs : QString(); - - compiled.init.body << u"// this type has QQmlParserStatus interface:"_qs; - compiled.init.body << u"Q_ASSERT(dynamic_cast<QQmlParserStatus *>(this) != nullptr);"_qs; - if (documentRoot) - compiled.init.body << u"if (" + callSpecialMethodFlag.name + u")"; - compiled.init.body << indent + u"this->classBegin();"; - - compiled.completeComponent.lastLines << u"// this type has QQmlParserStatus interface:"_qs; - compiled.completeComponent.lastLines - << u"Q_ASSERT(dynamic_cast<QQmlParserStatus *>(this) != nullptr);"_qs; - if (documentRoot) - compiled.completeComponent.lastLines << u"if (" + callSpecialMethodFlag.name + u")"; - compiled.completeComponent.lastLines << indent + u"this->componentComplete();"; - } - - // magic step: if the type has a QQmlFinalizerHook interface, we should call - // it's method here - if (hasFinalizerHookInterface) { - QString indent; - compiled.finalizeComponent.lastLines << u"// this type has QQmlFinalizerHook interface:"_qs; - compiled.finalizeComponent.lastLines - << u"Q_ASSERT(dynamic_cast<QQmlFinalizerHook *>(this) != nullptr);"_qs; - if (documentRoot) { - compiled.finalizeComponent.lastLines << u"if (" + callSpecialMethodFlag.name + u")"; - indent = u" "_qs; - } - compiled.finalizeComponent.lastLines << indent + u"this->componentFinalized();"_qs; - } - - // NB: step 4 - and everything below that - is done at the very end of QML - // init and so we use lastLines. we need to make sure that objects from - // object bindings are created (and initialized) before we start the - // finalization: we need fully-set context at the beginning of QML finalize. - - // 4. finalize if necessary - if (documentRoot) { - compiled.init.lastLines << u"// 4. document root, call finalize"_qs; - compiled.init.lastLines << u"if (" + finalizeFlag.name + u") {"; - compiled.init.lastLines << u" " + compiled.endInit.name - + u"(engine, /* finalize */ true);"; - compiled.init.lastLines << u"}"_qs; - } - // return - compiled.init.lastLines << u"return context;"_qs; - - // TODO: is property update group needed? - compiled.endInit.body << u"Q_UNUSED(engine);"_qs; - compiled.endInit.body << u"Q_UNUSED(" + compilationUnitVariable.name + u")"_qs; - // compiled.endInit.body << u"Q_UNUSED(" + finalizeFlag.name + u");"; - if (baseTypeIsCompiledQml) { - compiled.endInit.body << u"{ // call parent's finalize"_qs; - compiled.endInit.body << baseClass + u"::" + compiled.endInit.name - + u"(engine, /* finalize */ false);"; - compiled.endInit.body << u"}"_qs; - } - - if (object.irObject->flags & QV4::CompiledData::Object::HasDeferredBindings) { - compiled.endInit.body << u"{ // defer bindings"_qs; - compiled.endInit.body << u"auto ddata = QQmlData::get(this);"_qs; - compiled.endInit.body << u"auto thisContext = ddata->outerContext;"_qs; - compiled.endInit.body << u"Q_ASSERT(thisContext);"_qs; - compiled.endInit.body << u"ddata->deferData(" + QString::number(objectIndex) + u", " - + compilationUnitVariable.name + u", thisContext);"; - compiled.endInit.body << u"}"_qs; - } - - // TODO: decide whether begin/end property update group is needed - // compiled.endInit.body << u"Qt::beginPropertyUpdateGroup(); // defer binding evaluation"_qs; - - // add basic MOC stuff - compiled.mocCode = { - u"Q_OBJECT"_qs, - (m_isAnonymous ? u"QML_ANONYMOUS"_qs : u"QML_ELEMENT"_qs), - }; - - compileElements(compiled, object); - - // add finalization steps only to document root - if (documentRoot) { - compiled.endInit.body << u"if (" + finalizeFlag.name + u") {"; - - // at this point, all bindings must've been finished, thus, we need: - // 1. componentComplete() - // 2. finalize callbacks / componentFinalized() - // 3. Component.onCompleted() - - // 1. - compiled.endInit.body << u" this->" + compiled.completeComponent.name - + u"(/* complete component */ true);"; - - // 2 - compiled.endInit.body << u" this->" + compiled.finalizeComponent.name - + u"(/* finalize component */ true);"; - - // 3. - compiled.endInit.body << u" this->" + compiled.handleOnCompleted.name + u"();"; - - compiled.endInit.body << u"}"_qs; - } - - // compiled.endInit.body << u"Qt::endPropertyUpdateGroup();"_qs; -} - -void CodeGenerator::compileObjectElements(QmltcType &compiled, const CodeGenObject &object) -{ - // compile enums - const auto enums = object.type->ownEnumerations(); - compiled.enums.reserve(enums.size()); - for (auto it = enums.cbegin(); it != enums.cend(); ++it) - compileEnum(compiled, it.value()); - - // compile properties in order - auto properties = object.type->ownProperties().values(); - compiled.variables.reserve(properties.size()); - std::sort(properties.begin(), properties.end(), - [](const QQmlJSMetaProperty &x, const QQmlJSMetaProperty &y) { - return x.index() < y.index(); - }); - for (const QQmlJSMetaProperty &p : properties) { - if (p.index() == -1) { - recordError(object.type->sourceLocation(), - u"Property '" + p.propertyName() - + u"' has incomplete information (internal error)"); - continue; - } - if (p.isAlias()) { - compileAlias(compiled, p, object.type); - } else { // normal property - compileProperty(compiled, p, object.type); - } - } - - // compile methods - QHash<QString, const QmlIR::Function *> irFunctionsByName; - std::for_each(object.irObject->functionsBegin(), object.irObject->functionsEnd(), - [&](const QmlIR::Function &function) { - irFunctionsByName.insert(m_doc->stringAt(function.nameIndex), - std::addressof(function)); - }); - const auto methods = object.type->ownMethods(); - compiled.functions.reserve(methods.size()); - for (auto it = methods.cbegin(); it != methods.cend(); ++it) { - const QmlIR::Function *irFunction = irFunctionsByName.value(it.key(), nullptr); - compileMethod(compiled, it.value(), irFunction, object); - } - - // NB: just clearing is safe since we do not call this function recursively - m_localChildrenToEndInit.clear(); - m_localChildrenToFinalize.clear(); - - // compile bindings - const auto sortedBindings = - toOrderedSequence(object.irObject->bindingsBegin(), object.irObject->bindingsEnd(), - object.irObject->bindingCount()); - - // for (auto it : sortedBindings) - // compileBinding(compiled, *it, object, u"this"_qs); - - // NB: can't use lower_bound since it only accepts a value, not a unary - // predicate - auto scriptBindingsBegin = - std::find_if(sortedBindings.cbegin(), sortedBindings.cend(), - [](auto it) { return it->type == QmlIR::Binding::Type_Script; }); - auto it = sortedBindings.cbegin(); - for (; it != scriptBindingsBegin; ++it) - compileBinding(compiled, **it, object, { object.type, u"this"_qs, u""_qs, false }); - - // NB: finalize children before creating/setting script bindings for `this` - for (qsizetype i = 0; i < m_localChildrenToEndInit.size(); ++i) { - compiled.endInit.body << m_localChildrenToEndInit.at(i) + u"->" + compiled.endInit.name - + u"(engine, " + compilationUnitVariable.name + u");"; - } - - const auto buildChildAtString = [](const QQmlJSScope::ConstPtr &type, - const QString &i) -> QString { - return u"static_cast<" + type->internalName() + u"* >(QObject::children().at(" - + childrenOffsetVariable.name + u" + " + i + u"))"; - }; - // TODO: there's exceptional redundancy (!) in this code generation part - for (qsizetype i = 0; i < m_localChildrenToFinalize.size(); ++i) { - QString index = QString::number(i); - const QQmlJSScope::ConstPtr &child = m_localChildrenToFinalize.at(i); - const QString childAt = buildChildAtString(child, index); - // NB: children are not document roots, so all special methods are argument-less - compiled.completeComponent.body - << childAt + u"->" + compiled.completeComponent.name + u"();"; - compiled.finalizeComponent.body - << childAt + u"->" + compiled.finalizeComponent.name + u"();"; - compiled.handleOnCompleted.body - << childAt + u"->" + compiled.handleOnCompleted.name + u"();"; - } - - for (; it != sortedBindings.cend(); ++it) - compileBinding(compiled, **it, object, { object.type, u"this"_qs, u""_qs, false }); + // NB: assume `engine` variable always exists + return u"QQmlEnginePrivate::get(engine)->compilationUnitFromUrl(%1())"_qs.arg(urlMethodName); } void CodeGenerator::compileQQmlComponentElements(QmltcType &compiled, const CodeGenObject &object) @@ -785,102 +398,6 @@ void CodeGenerator::compileQQmlComponentElements(QmltcType &compiled, const Code compiled.init.body << u"}"_qs; } -void CodeGenerator::compileEnum(QmltcType ¤t, const QQmlJSMetaEnum &e) -{ - const auto intValues = e.values(); - QStringList values; - values.reserve(intValues.size()); - std::transform(intValues.cbegin(), intValues.cend(), std::back_inserter(values), - [](int x) { return QString::number(x); }); - - // structure: (C++ type name, enum keys, enum values, MOC line) - current.enums.emplaceBack(e.name(), e.keys(), std::move(values), - u"Q_ENUM(%1)"_qs.arg(e.name())); -} - -void CodeGenerator::compileProperty(QmltcType ¤t, const QQmlJSMetaProperty &p, - const QQmlJSScope::ConstPtr &owner) -{ - Q_ASSERT(!p.isAlias()); // will be handled separately - Q_ASSERT(p.type()); - - const QString name = p.propertyName(); - const QString variableName = u"m_" + name; - const QString underlyingType = getUnderlyingType(p); - // only check for isList() here as it needs some special arrangements. - // otherwise, getUnderlyingType() handles the specifics of a type in C++ - if (p.isList()) { - const QString storageName = variableName + u"_storage"; - current.variables.emplaceBack(u"QList<" + p.type()->internalName() + u" *>", storageName, - QString()); - current.baselineCtor.initializerList.emplaceBack(variableName + u"(" + underlyingType - + u"(this, std::addressof(" + storageName - + u")))"); - } - - // along with property, also add relevant moc code, so that we can use the - // property in Qt/QML contexts - QStringList mocPieces; - mocPieces.reserve(10); - mocPieces << underlyingType << name; - - QmltcPropertyData compilationData(p); - - // 1. add setter and getter - // If p.isList(), it's a QQmlListProperty. Then you can write the underlying list through - // the QQmlListProperty object retrieved with the getter. Setting it would make no sense. - if (p.isWritable() && !p.isList()) { - QmltcMethod setter {}; - setter.returnType = u"void"_qs; - setter.name = compilationData.write; - // QmltcVariable - setter.parameterList.emplaceBack(QQmlJSUtils::constRefify(underlyingType), name + u"_", - u""_qs); - setter.body << variableName + u".setValue(" + name + u"_);"; - setter.body << u"emit " + compilationData.notify + u"();"; - setter.userVisible = true; - current.functions.emplaceBack(setter); - mocPieces << u"WRITE"_qs << setter.name; - } - - QmltcMethod getter {}; - getter.returnType = underlyingType; - getter.name = compilationData.read; - getter.body << u"return " + variableName + u".value();"; - getter.userVisible = true; - current.functions.emplaceBack(getter); - mocPieces << u"READ"_qs << getter.name; - - // 2. add bindable - if (!p.isList()) { - QmltcMethod bindable {}; - bindable.returnType = u"QBindable<" + underlyingType + u">"; - bindable.name = compilationData.bindable; - bindable.body << u"return QBindable<" + underlyingType + u">(std::addressof(" + variableName - + u"));"; - bindable.userVisible = true; - current.functions.emplaceBack(bindable); - mocPieces << u"BINDABLE"_qs << bindable.name; - } - - // 3. add/check notify (actually, this is already done inside QmltcVisitor) - - if (owner->isPropertyRequired(name)) - mocPieces << u"REQUIRED"_qs; - - // 4. add moc entry - // e.g. Q_PROPERTY(QString p READ getP WRITE setP BINDABLE bindableP) - current.mocCode << u"Q_PROPERTY(" + mocPieces.join(u" "_qs) + u")"; - - // 5. add extra moc entry if this property is marked default - if (name == owner->defaultPropertyName()) - current.mocCode << u"Q_CLASSINFO(\"DefaultProperty\", \"%1\")"_qs.arg(name); - - // structure: (C++ type name, name, C++ class name, C++ signal name) - current.properties.emplaceBack(underlyingType, variableName, current.cppType, - compilationData.notify); -} - void CodeGenerator::compileAlias(QmltcType ¤t, const QQmlJSMetaProperty &alias, const QQmlJSScope::ConstPtr &owner) { @@ -1256,15 +773,16 @@ void CodeGenerator::compileBinding(QmltcType ¤t, const QmlIR::Binding &bin const auto uniqueId = UniqueStringId(current, onAssignmentName); if (!m_onAssignmentObjectsCreated.contains(uniqueId)) { m_onAssignmentObjectsCreated.insert(uniqueId); - current.init.body << u"new " + bindingObject.type->internalName() + u"(engine, " - + qobjectParent + u");"; + current.init.body << u"auto %1 = new %2(creator, engine, %3);"_qs.arg( + onAssignmentName, bindingObject.type->internalName(), qobjectParent); + current.init.body << u"creator->set(%1, %2);"_qs.arg( + QString::number(m_visitor->creationIndex(bindingObject.type)), + onAssignmentName); // static_cast is fine, because we (must) know the exact type - current.endInit.body << u"auto " + onAssignmentName + u" = static_cast<" - + bindingObject.type->internalName() - + u" *>(QObject::children().at(" + childrenOffsetVariable.name - + u" + " + QString::number(m_localChildrenToEndInit.size()) - + u"));"; + current.endInit.body << u"auto %1 = creator->get<%2>(%3);"_qs.arg( + onAssignmentName, bindingObject.type->internalName(), + QString::number(m_visitor->creationIndex(bindingObject.type))); m_localChildrenToEndInit.append(onAssignmentName); m_localChildrenToFinalize.append(bindingObject.type); @@ -1324,7 +842,8 @@ void CodeGenerator::compileBinding(QmltcType ¤t, const QmlIR::Binding &bin current.endInit.body << QStringLiteral( "auto %1 = QQmlObjectCreator::createComponent(engine, " "%2, %3, %4, thisContext);") - .arg(objectName, compilationUnitVariable.name, + .arg(objectName, + generate_callCompilationUnit(m_urlMethodName), QString::number(index), qobjectParent); current.endInit.body << QStringLiteral("thisContext->installContext(QQmlData::get(%1), " "QQmlContextData::OrdinaryObject);") @@ -1345,16 +864,16 @@ void CodeGenerator::compileBinding(QmltcType ¤t, const QmlIR::Binding &bin break; } - current.init.body << u"new " + bindingObject.type->internalName() + u"(engine, " - + qobjectParent + u");"; - const QString objectName = makeGensym(u"o"_qs); - - // static_cast is fine, because we (must) know the exact type - current.endInit.body << u"auto " + objectName + u" = static_cast<" - + bindingObject.type->internalName() + u" *>(QObject::children().at(" - + childrenOffsetVariable.name + u" + " - + QString::number(m_localChildrenToEndInit.size()) + u"));"; + current.init.body << u"auto %1 = new %2(creator, engine, %3);"_qs.arg( + objectName, bindingObject.type->internalName(), qobjectParent); + current.init.body << u"creator->set(%1, %2);"_qs.arg( + QString::number(m_visitor->creationIndex(bindingObject.type)), objectName); + + // refetch the same object during endInit to set the bindings + current.endInit.body << u"auto %1 = creator->get<%2>(%3);"_qs.arg( + objectName, bindingObject.type->internalName(), + QString::number(m_visitor->creationIndex(bindingObject.type))); setObjectBinding(objectName); m_localChildrenToEndInit.append(objectName); @@ -1692,7 +1211,7 @@ void CodeGenerator::compileScriptBinding(QmltcType ¤t, const QmlIR::Bindin } QmltcCodeGenerator::generate_createBindingOnProperty( - ¤t.endInit.body, compilationUnitVariable.name, + ¤t.endInit.body, generate_callCompilationUnit(m_urlMethodName), u"this"_qs, // NB: always using enclosing object as a scope for the binding relativeToAbsoluteRuntimeIndex(object.irObject, binding.value.compiledScriptIndex), bindingTarget, // binding target diff --git a/tools/qmltc/prototype/codegenerator.h b/tools/qmltc/prototype/codegenerator.h index a50bf46116..ae7df452a8 100644 --- a/tools/qmltc/prototype/codegenerator.h +++ b/tools/qmltc/prototype/codegenerator.h @@ -51,7 +51,8 @@ class CodeGenerator { public: CodeGenerator(const QString &url, QQmlJSLogger *logger, QmlIR::Document *doc, - const QmltcTypeResolver *localResolver, const QmltcCompilerInfo *info); + const QmltcTypeResolver *localResolver, const QmltcVisitor *visitor, + const QmltcCompilerInfo *info); // TODO: this should really be just QQmlJSScope::ConstPtr (and maybe C++ // class name), but bindings are currently not represented in QQmlJSScope, @@ -65,11 +66,30 @@ public: const QList<CodeGenObject> &objects() const { return m_objects; } bool ignoreObject(const CodeGenObject &object) const; + qsizetype codegenObjectIndex(const QQmlJSScope::ConstPtr &type) const + { + Q_ASSERT(m_typeToObjectIndex.contains(type)); + return m_typeToObjectIndex[type]; + } + + const CodeGenObject &objectFromType(const QQmlJSScope::ConstPtr &type) const + { + return m_objects[codegenObjectIndex(type)]; + } + + QString stringAt(int index) const { return m_doc->stringAt(index); } + + // QmlIR::Binding-specific sort function + static QList<typename QmlIR::PoolList<QmlIR::Binding>::Iterator> + toOrderedSequence(typename QmlIR::PoolList<QmlIR::Binding>::Iterator first, + typename QmlIR::PoolList<QmlIR::Binding>::Iterator last, qsizetype n); + private: QString m_url; // document url QQmlJSLogger *m_logger = nullptr; QmlIR::Document *m_doc = nullptr; const QmltcTypeResolver *m_localTypeResolver = nullptr; + const QmltcVisitor *m_visitor = nullptr; const QmltcCompilerInfo *m_info = nullptr; @@ -145,16 +165,8 @@ private: bool m_isAnonymous = false; // crutch to distinguish QML_ELEMENT from QML_ANONYMOUS public: - // code compilation functions that produce "compiled" entities - void compileObject(QmltcType ¤t, const CodeGenObject &object, - std::function<void(QmltcType &, const CodeGenObject &)> compileElements); - void compileObjectElements(QmltcType ¤t, const CodeGenObject &object); void compileQQmlComponentElements(QmltcType ¤t, const CodeGenObject &object); -private: - void compileEnum(QmltcType ¤t, const QQmlJSMetaEnum &e); - void compileProperty(QmltcType ¤t, const QQmlJSMetaProperty &p, - const QQmlJSScope::ConstPtr &owner); void compileAlias(QmltcType ¤t, const QQmlJSMetaProperty &alias, const QQmlJSScope::ConstPtr &owner); void compileMethod(QmltcType ¤t, const QQmlJSMetaMethod &m, const QmlIR::Function *f, @@ -173,6 +185,8 @@ private: }; void compileBinding(QmltcType ¤t, const QmlIR::Binding &binding, const CodeGenObject &object, const AccessorData &accessor); + +private: // special case (for simplicity) void compileScriptBinding(QmltcType ¤t, const QmlIR::Binding &binding, const QString &bindingSymbolName, const CodeGenObject &object, diff --git a/tools/qmltc/qmltccodewriter.cpp b/tools/qmltc/qmltccodewriter.cpp index f4aaf693d7..a70ae029ba 100644 --- a/tools/qmltc/qmltccodewriter.cpp +++ b/tools/qmltc/qmltccodewriter.cpp @@ -145,6 +145,7 @@ void QmltcCodeWriter::writeGlobalHeader(QmltcOutputWrapper &code, const QString code.rawAppendToHeader(u"#include <QtQml/qqml.h>"); // used for attached properties code.rawAppendToHeader(u"#include <private/qqmlengine_p.h>"); // executeRuntimeFunction(), etc. + code.rawAppendToHeader(u"#include <private/qqmltcobjectcreationhelper_p.h>"); // QmltcSupportLib code.rawAppendToHeader(u"#include <QtQml/qqmllist.h>"); // QQmlListProperty @@ -225,6 +226,22 @@ void QmltcCodeWriter::write(QmltcOutputWrapper &code, const QmltcProgram &progra // write all the types and their content for (const QmltcType &type : qAsConst(program.compiledTypes)) write(code, type); + + // add typeCount definitions. after all types have been written down (so + // they are now complete types as per C++). practically, this only concerns + // document root type + for (const QmltcType &type : qAsConst(program.compiledTypes)) { + if (!type.typeCount) + continue; + code.rawAppendToHeader(u""); // blank line + code.rawAppendToHeader(u"constexpr %1 %2::%3()"_qs.arg(type.typeCount->returnType, + type.cppType, type.typeCount->name)); + code.rawAppendToHeader(u"{"); + for (const QString &line : qAsConst(type.typeCount->body)) + code.rawAppendToHeader(line, 1); + code.rawAppendToHeader(u"}"); + } + writeGlobalFooter(code, program.url, program.outNamespace); writeToFile(program.hPath, code.code().header.toUtf8()); @@ -272,9 +289,6 @@ 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); { @@ -331,11 +345,10 @@ void QmltcCodeWriter::write(QmltcOutputWrapper &code, const QmltcType &type) QmltcCodeWriter::write(code, type.baselineCtor); QmltcCodeWriter::write(code, type.init); QmltcCodeWriter::write(code, type.endInit); + QmltcCodeWriter::write(code, type.beginClass); QmltcCodeWriter::write(code, type.completeComponent); QmltcCodeWriter::write(code, type.finalizeComponent); QmltcCodeWriter::write(code, type.handleOnCompleted); - - // code.rawAppendToHeader(u"public:", -1); } // children @@ -356,14 +369,16 @@ void QmltcCodeWriter::write(QmltcOutputWrapper &code, const QmltcType &type) write(code, variable); } + code.rawAppendToHeader(u"private:", -1); + for (const QString &otherLine : qAsConst(type.otherCode)) + code.rawAppendToHeader(otherLine, 1); + if (type.typeCount) { - // we know that typeCount variable is very special + // add typeCount declaration, definition is added later code.rawAppendToHeader(u""); // blank line code.rawAppendToHeader(u"protected:"); - Q_ASSERT(!type.typeCount->defaultValue.isEmpty()); - code.rawAppendToHeader(u"constexpr static %1 %2 = %3;"_qs.arg(type.typeCount->cppType, - type.typeCount->name, - type.typeCount->defaultValue), + code.rawAppendToHeader(u"constexpr static %1 %2();"_qs.arg(type.typeCount->returnType, + type.typeCount->name), 1); } diff --git a/tools/qmltc/qmltccompiler.cpp b/tools/qmltc/qmltccompiler.cpp index ad8e231a7c..0a55fd3bbf 100644 --- a/tools/qmltc/qmltccompiler.cpp +++ b/tools/qmltc/qmltccompiler.cpp @@ -45,6 +45,7 @@ Q_LOGGING_CATEGORY(lcQmltcCompiler, "qml.qmltc.compiler", QtWarningMsg); const QString QmltcCodeGenerator::privateEngineName = u"ePriv"_qs; const QString QmltcCodeGenerator::urlMethodName = u"q_qmltc_docUrl"_qs; +const QString QmltcCodeGenerator::typeCountName = u"q_qmltc_typeCount"_qs; QmltcCompiler::QmltcCompiler(const QString &url, QmltcTypeResolver *resolver, QmltcVisitor *visitor, QQmlJSLogger *logger) @@ -66,7 +67,7 @@ void QmltcCompiler::compile(const QmltcCompilerInfo &info, QmlIR::Document *doc) Q_ASSERT(!m_info.resourcePath.isEmpty()); m_prototypeCodegen = - std::make_unique<CodeGenerator>(m_url, m_logger, doc, m_typeResolver, &info); + std::make_unique<CodeGenerator>(m_url, m_logger, doc, m_typeResolver, m_visitor, &info); QSet<QString> cppIncludesFromPrototype; m_prototypeCodegen->prepare(&cppIncludesFromPrototype); @@ -78,8 +79,10 @@ void QmltcCompiler::compile(const QmltcCompilerInfo &info, QmlIR::Document *doc) return base && base->internalName() == u"QQmlComponent"_qs; }; - auto qmlTypes = m_visitor->qmlTypes(); - const QSet<QQmlJSScope::ConstPtr> types(qmlTypes.begin(), qmlTypes.end()); + // Note: we only compile "pure" QML types. any component-wrapped type is + // expected to appear through a binding + auto pureTypes = m_visitor->pureQmlTypes(); + const QSet<QQmlJSScope::ConstPtr> types(pureTypes.begin(), pureTypes.end()); QmltcMethod urlMethod; compileUrlMethod(urlMethod); @@ -96,14 +99,16 @@ void QmltcCompiler::compile(const QmltcCompilerInfo &info, QmlIR::Document *doc) if (isComponent(root)) { compiledTypes.reserve(1); compiledTypes.emplaceBack(); // create empty type - const auto compile = [this](QmltcType &type, const CodeGenerator::CodeGenObject &object) { - m_prototypeCodegen->compileQQmlComponentElements(type, object); + const auto compile = [this](QmltcType ¤t, const QQmlJSScope::ConstPtr &type) { + const auto &object = m_prototypeCodegen->objectFromType(type); + m_prototypeCodegen->compileQQmlComponentElements(current, object); }; Q_ASSERT(root == filteredObjects.at(0).type); - m_prototypeCodegen->compileObject(compiledTypes.back(), filteredObjects.at(0), compile); + compileType(compiledTypes.back(), root, compile); + // m_prototypeCodegen->compileObject(compiledTypes.back(), filteredObjects.at(0), compile); } else { - const auto compile = [this](QmltcType &type, const CodeGenerator::CodeGenObject &object) { - m_prototypeCodegen->compileObjectElements(type, object); + const auto compile = [this](QmltcType ¤t, const QQmlJSScope::ConstPtr &type) { + compileTypeElements(current, type); }; compiledTypes.reserve(filteredObjects.size()); @@ -112,7 +117,7 @@ void QmltcCompiler::compile(const QmltcCompilerInfo &info, QmlIR::Document *doc) if (m_prototypeCodegen->ignoreObject(object)) continue; compiledTypes.emplaceBack(); // create empty type - m_prototypeCodegen->compileObject(compiledTypes.back(), object, compile); + compileType(compiledTypes.back(), object.type, compile); } } if (hasErrors()) @@ -142,7 +147,9 @@ void QmltcCompiler::compileUrlMethod(QmltcMethod &urlMethod) urlMethod.modifiers << u"noexcept"_qs; } -void QmltcCompiler::compileType(QmltcType ¤t, const QQmlJSScope::ConstPtr &type) +void QmltcCompiler::compileType( + QmltcType ¤t, const QQmlJSScope::ConstPtr &type, + std::function<void(QmltcType &, const QQmlJSScope::ConstPtr &)> compileElements) { if (type->isSingleton()) { recordError(type->sourceLocation(), u"Singleton types are not supported"_qs); @@ -156,35 +163,49 @@ void QmltcCompiler::compileType(QmltcType ¤t, const QQmlJSScope::ConstPtr const auto rootType = m_visitor->result(); const bool documentRoot = (type == rootType); - const bool baseTypeIsCompiledQml = false; // TODO: support this in QmltcTypeResolver - const bool isAnonymous = type->internalName().at(0).isLower(); + const bool isAnonymous = !documentRoot || type->internalName().at(0).isLower(); + + const auto hasQmlBase = [](const QQmlJSScope::ConstPtr &scope) { + if (!scope) + return false; + const auto base = scope->baseType(); + if (!base) + return false; + return base->isComposite() && base->scopeType() == QQmlJSScope::QMLScope; + }; + const bool baseTypeIsCompiledQml = hasQmlBase(type); + + QmltcCodeGenerator generator { m_visitor }; current.baseClasses = { baseClass }; if (!documentRoot) { // make document root a friend to allow it to access init and endInit current.otherCode << u"friend class %1;"_qs.arg(rootType->internalName()); + + // additionally make an immediate parent a friend since that parent + // would create the object through a non-public constructor + const auto realQmlScope = [](const QQmlJSScope::ConstPtr &scope) { + if (scope->isArrayScope()) + return scope->parentScope(); + return scope; + }; + current.otherCode << u"friend class %1;"_qs.arg( + realQmlScope(type->parentScope())->internalName()); } else { // make QQmltcObjectCreationBase<DocumentRoot> a friend to allow it to // be created for the root object current.otherCode << u"friend class QQmltcObjectCreationBase<%1>;"_qs.arg( rootType->internalName()); - current.typeCount = QmltcVariable { u"uint"_qs, u"q_qmltc_typeCount"_qs, QString() }; - Q_ASSERT(m_visitor->qmlTypes().size() > 0); - QList<QQmlJSScope::ConstPtr> typesWithBaseTypeCount = m_visitor->qmlTypesWithQmlBases(); - QStringList typeCountComponents; - typeCountComponents.reserve(1 + typesWithBaseTypeCount.size()); - // add this document's type counts minus document root - typeCountComponents << QString::number(m_visitor->qmlTypes().size() - 1); - for (const QQmlJSScope::ConstPtr &t : qAsConst(typesWithBaseTypeCount)) { - if (t == type) { // t is this document's root - typeCountComponents << t->baseTypeName() + u"::" + current.typeCount->name; - } else { - typeCountComponents << t->internalName() + u"::" + current.typeCount->name; - } - } - current.typeCount->defaultValue = typeCountComponents.join(u" + "_qs); + QmltcMethod typeCountMethod; + typeCountMethod.name = QmltcCodeGenerator::typeCountName; + typeCountMethod.returnType = u"uint"_qs; + typeCountMethod.body << u"return " + generator.generate_typeCount() + u";"; + current.typeCount = typeCountMethod; } + // make QQmltcObjectCreationHelper a friend of every type since it provides + // useful helper methods for all types + current.otherCode << u"friend class QT_PREPEND_NAMESPACE(QQmltcObjectCreationHelper);"_qs; current.mocCode = { u"Q_OBJECT"_qs, @@ -194,31 +215,54 @@ void QmltcCompiler::compileType(QmltcType ¤t, const QQmlJSScope::ConstPtr // add special member functions current.baselineCtor.access = QQmlJSMetaMethod::Protected; + if (documentRoot) { + current.externalCtor.access = QQmlJSMetaMethod::Public; + } else { + current.externalCtor.access = QQmlJSMetaMethod::Protected; + } current.init.access = QQmlJSMetaMethod::Protected; + current.beginClass.access = QQmlJSMetaMethod::Protected; current.endInit.access = QQmlJSMetaMethod::Protected; - current.externalCtor.access = QQmlJSMetaMethod::Public; + current.completeComponent.access = QQmlJSMetaMethod::Protected; + current.finalizeComponent.access = QQmlJSMetaMethod::Protected; + current.handleOnCompleted.access = QQmlJSMetaMethod::Protected; current.baselineCtor.name = current.cppType; current.externalCtor.name = current.cppType; - current.init.name = u"qmltc_init"_qs; + current.init.name = u"QML_init"_qs; current.init.returnType = u"QQmlRefPointer<QQmlContextData>"_qs; - current.endInit.name = u"qmltc_finalize"_qs; + current.beginClass.name = u"QML_beginClass"_qs; + current.beginClass.returnType = u"void"_qs; + current.endInit.name = u"QML_endInit"_qs; current.endInit.returnType = u"void"_qs; - + current.completeComponent.name = u"QML_completeComponent"_qs; + current.completeComponent.returnType = u"void"_qs; + current.finalizeComponent.name = u"QML_finalizeComponent"_qs; + current.finalizeComponent.returnType = u"void"_qs; + current.handleOnCompleted.name = u"QML_handleOnCompleted"_qs; + current.handleOnCompleted.returnType = u"void"_qs; QmltcVariable creator(u"QQmltcObjectCreationHelper*"_qs, u"creator"_qs); QmltcVariable engine(u"QQmlEngine*"_qs, u"engine"_qs); QmltcVariable parent(u"QObject*"_qs, u"parent"_qs, u"nullptr"_qs); - current.baselineCtor.parameterList = { parent }; QmltcVariable ctxtdata(u"const QQmlRefPointer<QQmlContextData>&"_qs, u"parentContext"_qs); QmltcVariable finalizeFlag(u"bool"_qs, u"canFinalize"_qs); + current.baselineCtor.parameterList = { parent }; if (documentRoot) { current.externalCtor.parameterList = { engine, parent }; current.init.parameterList = { creator, engine, ctxtdata, finalizeFlag }; + current.beginClass.parameterList = { creator, finalizeFlag }; current.endInit.parameterList = { creator, engine, finalizeFlag }; + current.completeComponent.parameterList = { creator, finalizeFlag }; + current.finalizeComponent.parameterList = { creator, finalizeFlag }; + current.handleOnCompleted.parameterList = { creator, finalizeFlag }; } else { current.externalCtor.parameterList = { creator, engine, parent }; current.init.parameterList = { creator, engine, ctxtdata }; + current.beginClass.parameterList = { creator }; current.endInit.parameterList = { creator, engine }; + current.completeComponent.parameterList = { creator }; + current.finalizeComponent.parameterList = { creator }; + current.handleOnCompleted.parameterList = { creator }; } current.externalCtor.initializerList = { current.baselineCtor.name + u"(" + parent.name @@ -233,8 +277,6 @@ void QmltcCompiler::compileType(QmltcType ¤t, const QQmlJSScope::ConstPtr current.baselineCtor.body << u"QQml_setParent_noEvent(this, " + parent.name + u");"; } - QmltcCodeGenerator generator { rootType }; - // compilation stub: current.externalCtor.body << u"Q_UNUSED(engine);"_qs; current.endInit.body << u"Q_UNUSED(engine);"_qs; @@ -247,6 +289,7 @@ void QmltcCompiler::compileType(QmltcType ¤t, const QQmlJSScope::ConstPtr type->internalName()); current.externalCtor.body << u"QQmltcObjectCreationHelper creator = objectHolder.view();"_qs; + current.externalCtor.body << u"creator.set(0, this);"_qs; // special case // now call init current.externalCtor.body << current.init.name + u"(&creator, engine, QQmlContextData::get(engine->rootContext()), /* " @@ -260,7 +303,35 @@ void QmltcCompiler::compileType(QmltcType ¤t, const QQmlJSScope::ConstPtr + u"(creator, engine, QQmlData::get(parent)->outerContext);"; } - auto postponedGenerate = generator.generate_qmlContextSetup(current, type); + auto postponedQmlContextSetup = generator.generate_initCode(current, type); + auto postponedFinalizeCode = generator.generate_endInitCode(current, type); + generator.generate_beginClassCode(current, type); + generator.generate_completeComponentCode(current, type); + generator.generate_finalizeComponentCode(current, type); + generator.generate_handleOnCompletedCode(current, type); + + compileElements(current, type); +} + +void QmltcCompiler::compileTypeElements(QmltcType ¤t, const QQmlJSScope::ConstPtr &type) +{ + const CodeGenerator::CodeGenObject &object = m_prototypeCodegen->objectFromType(type); + + // TODO: make this a part of QmltcCodeGenerator::generate_endInitCode (and + // stop relying on QmlIR!) + if (object.irObject->flags & QV4::CompiledData::Object::HasDeferredBindings) { + current.endInit.body << u"{ // defer bindings"_qs; + current.endInit.body << u"auto ddata = QQmlData::get(this);"_qs; + current.endInit.body << u"auto thisContext = ddata->outerContext;"_qs; + current.endInit.body << u"Q_ASSERT(thisContext);"_qs; + current.endInit.body << QStringLiteral("ddata->deferData(%1, " + "QQmlEnginePrivate::get(engine)->" + "compilationUnitFromUrl(%2()), thisContext);") + .arg(QString::number( + m_prototypeCodegen->codegenObjectIndex(type)), + QmltcCodeGenerator::urlMethodName); + current.endInit.body << u"}"_qs; + } // compile components of a type: // - enums @@ -273,13 +344,8 @@ void QmltcCompiler::compileType(QmltcType ¤t, const QQmlJSScope::ConstPtr for (auto it = enums.begin(); it != enums.end(); ++it) compileEnum(current, it.value()); - const auto methods = type->ownMethods(); auto properties = type->ownProperties().values(); - current.functions.reserve(methods.size() + properties.size() * 3); // sensible default - for (const QQmlJSMetaMethod &m : methods) - compileMethod(current, m); - - current.variables.reserve(properties.size()); + current.properties.reserve(properties.size()); // Note: index() is the (future) meta property index, so make sure given // properties are ordered by that index before compiling std::sort(properties.begin(), properties.end(), @@ -294,15 +360,44 @@ void QmltcCompiler::compileType(QmltcType ¤t, const QQmlJSScope::ConstPtr continue; } if (p.isAlias()) { - recordError(type->sourceLocation(), u"Property aliases are not supported"_qs); + m_prototypeCodegen->compileAlias(current, p, type); } else { compileProperty(current, p, type); } } - const QMultiHash<QString, QQmlJSMetaPropertyBinding> allBindings = type->ownPropertyBindings(); - for (auto it = allBindings.begin(); it != allBindings.end(); ++it) - compileBinding(current, it.value(), type, BindingAccessorData { type }); + QHash<QString, const QmlIR::Function *> irFunctionsByName; + std::for_each(object.irObject->functionsBegin(), object.irObject->functionsEnd(), + [&](const QmlIR::Function &function) { + irFunctionsByName.insert(m_prototypeCodegen->stringAt(function.nameIndex), + std::addressof(function)); + }); + const auto methods = type->ownMethods(); + current.functions.reserve(methods.size() + properties.size() * 3); // sensible default + for (auto it = methods.cbegin(); it != methods.cend(); ++it) { + const QmlIR::Function *irFunction = irFunctionsByName.value(it.key(), nullptr); + m_prototypeCodegen->compileMethod(current, it.value(), irFunction, object); + } + + { + const auto sortedBindings = CodeGenerator::toOrderedSequence( + object.irObject->bindingsBegin(), object.irObject->bindingsEnd(), + object.irObject->bindingCount()); + + auto scriptBindingsBegin = + std::find_if(sortedBindings.cbegin(), sortedBindings.cend(), + [](auto it) { return it->type == QmlIR::Binding::Type_Script; }); + auto it = sortedBindings.cbegin(); + for (; it != scriptBindingsBegin; ++it) { + m_prototypeCodegen->compileBinding(current, **it, object, + { object.type, u"this"_qs, u""_qs, false }); + } + + for (; it != sortedBindings.cend(); ++it) { + m_prototypeCodegen->compileBinding(current, **it, object, + { type, u"this"_qs, u""_qs, false }); + } + } } void QmltcCompiler::compileEnum(QmltcType ¤t, const QQmlJSMetaEnum &e) @@ -412,34 +507,44 @@ void QmltcCompiler::compileProperty(QmltcType ¤t, const QQmlJSMetaProperty mocPieces.reserve(10); mocPieces << underlyingType << name; + QmltcPropertyData compilationData(p); + // 1. add setter and getter - if (p.isWritable()) { + // If p.isList(), it's a QQmlListProperty. Then you can write the underlying list through + // the QQmlListProperty object retrieved with the getter. Setting it would make no sense. + if (p.isWritable() && !p.isList()) { QmltcMethod setter {}; setter.returnType = u"void"_qs; - setter.name = p.write(); - // QQmlJSAotVariable + setter.name = compilationData.write; + // QmltcVariable setter.parameterList.emplaceBack(QQmlJSUtils::constRefify(underlyingType), name + u"_", u""_qs); setter.body << variableName + u".setValue(" + name + u"_);"; + setter.body << u"Q_EMIT " + compilationData.notify + u"();"; + setter.userVisible = true; current.functions.emplaceBack(setter); mocPieces << u"WRITE"_qs << setter.name; } QmltcMethod getter {}; getter.returnType = underlyingType; - getter.name = p.read(); + getter.name = compilationData.read; getter.body << u"return " + variableName + u".value();"; + getter.userVisible = true; current.functions.emplaceBack(getter); mocPieces << u"READ"_qs << getter.name; // 2. add bindable - QmltcMethod bindable {}; - bindable.returnType = u"QBindable<" + underlyingType + u">"; - bindable.name = p.bindable(); - bindable.body << u"return QBindable<" + underlyingType + u">(std::addressof(" + variableName - + u"));"; - current.functions.emplaceBack(bindable); - mocPieces << u"BINDABLE"_qs << bindable.name; + if (!p.isList()) { + QmltcMethod bindable {}; + bindable.returnType = u"QBindable<" + underlyingType + u">"; + bindable.name = compilationData.bindable; + bindable.body << u"return QBindable<" + underlyingType + u">(std::addressof(" + variableName + + u"));"; + bindable.userVisible = true; + current.functions.emplaceBack(bindable); + mocPieces << u"BINDABLE"_qs << bindable.name; + } // 3. add/check notify (actually, this is already done inside QmltcVisitor) @@ -455,7 +560,8 @@ void QmltcCompiler::compileProperty(QmltcType ¤t, const QQmlJSMetaProperty current.mocCode << u"Q_CLASSINFO(\"DefaultProperty\", \"%1\")"_qs.arg(name); // structure: (C++ type name, name, C++ class name, C++ signal name) - current.properties.emplaceBack(underlyingType, variableName, current.cppType, p.notify()); + current.properties.emplaceBack(underlyingType, variableName, current.cppType, + compilationData.notify); } void QmltcCompiler::compileBinding(QmltcType ¤t, const QQmlJSMetaPropertyBinding &binding, @@ -482,9 +588,7 @@ void QmltcCompiler::compileBinding(QmltcType ¤t, const QQmlJSMetaPropertyB // other errors, so the compiler just needs to add correct instructions, // without if-checking every type - QmltcCodeGenerator generator { - QQmlJSScope::ConstPtr() - }; // NB: we don't need document root here + QmltcCodeGenerator generator {}; switch (binding.bindingType()) { case QQmlJSMetaPropertyBinding::BoolLiteral: { @@ -520,7 +624,6 @@ void QmltcCompiler::compileBinding(QmltcType ¤t, const QQmlJSMetaPropertyB } break; } - // case QQmlJSMetaPropertyBinding::RegExpLiteral: // case QQmlJSMetaPropertyBinding::Translation: // case QQmlJSMetaPropertyBinding::TranslationById: @@ -531,7 +634,7 @@ void QmltcCompiler::compileBinding(QmltcType ¤t, const QQmlJSMetaPropertyB // case QQmlJSMetaPropertyBinding::AttachedProperty: // case QQmlJSMetaPropertyBinding::GroupProperty: case QQmlJSMetaPropertyBinding::Invalid: { - Q_UNREACHABLE(); // this is truly something that must not happen here + m_logger->log(u"This binding is invalid"_qs, Log_Compiler, binding.sourceLocation()); break; } default: { diff --git a/tools/qmltc/qmltccompiler.h b/tools/qmltc/qmltccompiler.h index 85b861e8de..c3e5bf317d 100644 --- a/tools/qmltc/qmltccompiler.h +++ b/tools/qmltc/qmltccompiler.h @@ -70,7 +70,10 @@ private: QmltcCompilerInfo m_info {}; // miscellaneous input/output information void compileUrlMethod(QmltcMethod &urlMethod); - void compileType(QmltcType ¤t, const QQmlJSScope::ConstPtr &type); + void + compileType(QmltcType ¤t, const QQmlJSScope::ConstPtr &type, + std::function<void(QmltcType &, const QQmlJSScope::ConstPtr &)> compileElements); + void compileTypeElements(QmltcType ¤t, const QQmlJSScope::ConstPtr &type); void compileEnum(QmltcType ¤t, const QQmlJSMetaEnum &e); void compileMethod(QmltcType ¤t, const QQmlJSMetaMethod &m); void compileProperty(QmltcType ¤t, const QQmlJSMetaProperty &p, diff --git a/tools/qmltc/qmltccompilerpieces.h b/tools/qmltc/qmltccompilerpieces.h index ccee2c51a8..29adf4f2e5 100644 --- a/tools/qmltc/qmltccompilerpieces.h +++ b/tools/qmltc/qmltccompilerpieces.h @@ -50,8 +50,9 @@ struct QmltcCodeGenerator { static const QString privateEngineName; static const QString urlMethodName; + static const QString typeCountName; - QQmlJSScope::ConstPtr documentRoot; + QmltcVisitor *visitor = nullptr; /*! \internal @@ -61,8 +62,22 @@ struct QmltcCodeGenerator that have to be generated at a later point, once everything else is compiled. */ - [[nodiscard]] inline decltype(auto) generate_qmlContextSetup(QmltcType ¤t, - const QQmlJSScope::ConstPtr &type); + [[nodiscard]] inline decltype(auto) generate_initCode(QmltcType ¤t, + const QQmlJSScope::ConstPtr &type) const; + [[nodiscard]] inline decltype(auto) + generate_endInitCode(QmltcType ¤t, const QQmlJSScope::ConstPtr &type) const; + + inline void generate_interfaceCallCode(QmltcMethod *function, const QQmlJSScope::ConstPtr &type, + const QString &interfaceName, + const QString &interfaceCall) const; + inline void generate_beginClassCode(QmltcType ¤t, + const QQmlJSScope::ConstPtr &type) const; + inline void generate_completeComponentCode(QmltcType ¤t, + const QQmlJSScope::ConstPtr &type) const; + inline void generate_finalizeComponentCode(QmltcType ¤t, + const QQmlJSScope::ConstPtr &type) const; + inline void generate_handleOnCompletedCode(QmltcType ¤t, + const QQmlJSScope::ConstPtr &type) const; static void generate_assignToProperty(QStringList *block, const QQmlJSScope::ConstPtr &type, const QQmlJSMetaProperty &p, const QString &value, @@ -70,6 +85,13 @@ struct QmltcCodeGenerator static void generate_setIdValue(QStringList *block, const QString &context, qsizetype index, const QString &accessor, const QString &idString); + inline QString generate_typeCount() const + { + return generate_typeCount([](const QQmlJSScope::ConstPtr &) { return false; }); + } + template<typename Predicate> + inline QString generate_typeCount(Predicate p) const; + static void generate_callExecuteRuntimeFunction(QStringList *block, const QString &url, qsizetype index, const QString &accessor, const QString &returnType, @@ -82,6 +104,8 @@ struct QmltcCodeGenerator const QQmlJSMetaProperty &p, int valueTypeIndex, const QString &subTarget); + static inline void generate_getCompilationUnitFromUrl(); + static std::tuple<QStringList, QString, QStringList> wrap_mismatchingTypeConversion(const QQmlJSMetaProperty &p, QString value); @@ -91,25 +115,40 @@ struct QmltcCodeGenerator static QString wrap_addressof(const QString &addressed); }; -inline decltype(auto) -QmltcCodeGenerator::generate_qmlContextSetup(QmltcType ¤t, const QQmlJSScope::ConstPtr &type) +inline decltype(auto) QmltcCodeGenerator::generate_initCode(QmltcType ¤t, + const QQmlJSScope::ConstPtr &type) const { // qmltc_init()'s parameters: // * QQmltcObjectCreationHelper* creator // * QQmlEngine* engine // * const QQmlRefPointer<QQmlContextData>& parentContext // * bool canFinalize [optional, when document root] - const bool isDocumentRoot = type == documentRoot; + const bool isDocumentRoot = type == visitor->result(); current.init.body << u"Q_UNUSED(creator);"_qs; // can happen sometimes current.init.body << u"auto context = parentContext;"_qs; - // if parent scope is a QML type and is not a (current) document root, the - // parentContext we passed as input to this object is a context of another - // document. we need to fix it by using parentContext->parent() - if (auto parentScope = type->parentScope(); parentScope && parentScope->isComposite() - && parentScope->scopeType() == QQmlJSScope::QMLScope && parentScope != documentRoot) { + // if parent scope has a QML base type and is not a (current) document root, + // the parentContext we passed as input to this object is a context of + // another document. we need to fix it by using parentContext->parent() + + const auto realQmlScope = [](const QQmlJSScope::ConstPtr &scope) { + if (scope->isArrayScope()) // TODO: it is special for some reason + return scope->parentScope(); + return scope; + }; + const auto hasQmlBase = [](const QQmlJSScope::ConstPtr &scope) { + if (!scope) + return false; + const auto base = scope->baseType(); + if (!base) + return false; + return base->isComposite() && base->scopeType() == QQmlJSScope::QMLScope; + }; + + if (auto parentScope = realQmlScope(type->parentScope()); + parentScope != visitor->result() && hasQmlBase(parentScope)) { current.init.body << u"// NB: context->parent() is the context of this document"_qs; current.init.body << u"context = context->parent();"_qs; } @@ -122,9 +161,20 @@ QmltcCodeGenerator::generate_qmlContextSetup(QmltcType ¤t, const QQmlJSSco if (isDocumentRoot) lhs = u"context = "_qs; current.init.body << u"// 0. call base's init method"_qs; - current.init.body << QStringLiteral( - "%1%2::%3(creator, engine, context, /* finalize */ false)") - .arg(lhs, base->internalName(), current.init.name); + + Q_ASSERT(!isDocumentRoot || visitor->qmlTypesWithQmlBases()[0] == visitor->result()); + const auto isCurrentType = [&](const QQmlJSScope::ConstPtr &qmlType) { + return qmlType == type; + }; + const QString creationOffset = generate_typeCount(isCurrentType); + + current.init.body << u"{"_qs; + current.init.body << u"QQmltcObjectCreationHelper subCreator(creator, %1);"_qs.arg( + creationOffset); + current.init.body + << QStringLiteral("%1%2::%3(&subCreator, engine, context, /* finalize */ false);") + .arg(lhs, base->internalName(), current.init.name); + current.init.body << u"}"_qs; } current.init.body @@ -159,20 +209,26 @@ QmltcCodeGenerator::generate_qmlContextSetup(QmltcType ¤t, const QQmlJSSco current.init.body << u"context->setContextObject(this);"_qs; } - if (int id = type->runtimeId(); id >= 0) { + if (int id = visitor->runtimeId(type); id >= 0) { current.init.body << u"// 3. set id since it is provided"_qs; QmltcCodeGenerator::generate_setIdValue(¤t.init.body, u"context"_qs, id, u"this"_qs, u"<unknown>"_qs); } - // TODO: add QQmlParserStatus::classBegin() to init - const auto generateFinalLines = [¤t, isDocumentRoot]() { if (isDocumentRoot) { current.init.body << u"// 4. call finalize in the document root"_qs; current.init.body << u"if (canFinalize) {"_qs; + current.init.body << QStringLiteral(" %1(creator, /* finalize */ true);") + .arg(current.beginClass.name); current.init.body << QStringLiteral(" %1(creator, engine, /* finalize */ true);") .arg(current.endInit.name); + current.init.body << QStringLiteral(" %1(creator, /* finalize */ true);") + .arg(current.completeComponent.name); + current.init.body << QStringLiteral(" %1(creator, /* finalize */ true);") + .arg(current.finalizeComponent.name); + current.init.body << QStringLiteral(" %1(creator, /* finalize */ true);") + .arg(current.handleOnCompleted.name); current.init.body << u"}"_qs; } current.init.body << u"return context;"_qs; @@ -181,6 +237,195 @@ QmltcCodeGenerator::generate_qmlContextSetup(QmltcType ¤t, const QQmlJSSco return QScopeGuard(generateFinalLines); } +inline decltype(auto) +QmltcCodeGenerator::generate_endInitCode(QmltcType ¤t, + const QQmlJSScope::ConstPtr &type) const +{ + // QML_endInit()'s parameters: + // * QQmltcObjectCreationHelper* creator + // * QQmlEngine* engine + // * bool canFinalize [optional, when document root] + const bool isDocumentRoot = type == visitor->result(); + current.endInit.body << u"Q_UNUSED(creator);"_qs; + current.endInit.body << u"Q_UNUSED(engine);"_qs; + if (isDocumentRoot) + current.endInit.body << u"Q_UNUSED(canFinalize);"_qs; + + if (auto base = type->baseType(); base->isComposite()) { + current.endInit.body << u"// call base's finalize method"_qs; + Q_ASSERT(!isDocumentRoot || visitor->qmlTypesWithQmlBases()[0] == visitor->result()); + const auto isCurrentType = [&](const QQmlJSScope::ConstPtr &qmlType) { + return qmlType == type; + }; + const QString creationOffset = generate_typeCount(isCurrentType); + current.endInit.body << u"{"_qs; + current.endInit.body << u"QQmltcObjectCreationHelper subCreator(creator, %1);"_qs.arg( + creationOffset); + current.endInit.body << u"%1::%2(&subCreator, engine, /* finalize */ false);"_qs.arg( + base->internalName(), current.endInit.name); + current.endInit.body << u"}"_qs; + } + + const auto generateFinalLines = [¤t, isDocumentRoot, this]() { + if (!isDocumentRoot) // document root does all the work here + return; + + const auto types = visitor->pureQmlTypes(); + current.endInit.body << u"// finalize children"_qs; + for (qsizetype i = 1; i < types.size(); ++i) { + const auto &type = types[i]; + Q_ASSERT(!type->isComponentRootElement()); + current.endInit.body << u"creator->get<%1>(%2)->%3(creator, engine);"_qs.arg( + type->internalName(), QString::number(i), current.endInit.name); + } + }; + return QScopeGuard(generateFinalLines); +} + +inline void QmltcCodeGenerator::generate_interfaceCallCode(QmltcMethod *function, + const QQmlJSScope::ConstPtr &type, + const QString &interfaceName, + const QString &interfaceCall) const +{ + // function's parameters: + // * QQmltcObjectCreationHelper* creator + // * bool canFinalize [optional, when document root] + const bool isDocumentRoot = type == visitor->result(); + function->body << u"Q_UNUSED(creator);"_qs; + if (isDocumentRoot) + function->body << u"Q_UNUSED(canFinalize);"_qs; + + if (auto base = type->baseType(); base->isComposite()) { + function->body << u"// call base's method"_qs; + Q_ASSERT(!isDocumentRoot || visitor->qmlTypesWithQmlBases()[0] == visitor->result()); + const auto isCurrentType = [&](const QQmlJSScope::ConstPtr &qmlType) { + return qmlType == type; + }; + const QString creationOffset = generate_typeCount(isCurrentType); + function->body << u"{"_qs; + function->body << u"QQmltcObjectCreationHelper subCreator(creator, %1);"_qs.arg( + creationOffset); + function->body << u"%1::%2(&subCreator, /* finalize */ false);"_qs.arg(base->internalName(), + function->name); + function->body << u"}"_qs; + } + + if (!isDocumentRoot) + return; + + const auto types = visitor->pureQmlTypes(); + function->body << u"// call children's methods"_qs; + for (qsizetype i = 1; i < types.size(); ++i) { + const auto &type = types[i]; + Q_ASSERT(!type->isComponentRootElement()); + function->body << u"{"_qs; + function->body << u"auto child = creator->get<%1>(%2);"_qs.arg(type->internalName(), + QString::number(i)); + function->body << u"child->%1(creator);"_qs.arg(function->name); + if (type->hasInterface(interfaceName)) { + function->body << u"Q_ASSERT(dynamic_cast<%1 *>(child) != nullptr);"_qs.arg( + interfaceName); + function->body << u"child->%1();"_qs.arg(interfaceCall); + } + function->body << u"}"_qs; + } + + if (type->hasInterface(interfaceName)) { + function->body << u"if (canFinalize) {"_qs; + function->body << u" // call own method"_qs; + function->body << u" this->%1();"_qs.arg(interfaceCall); + function->body << u"}"_qs; + } +} + +inline void QmltcCodeGenerator::generate_beginClassCode(QmltcType ¤t, + const QQmlJSScope::ConstPtr &type) const +{ + generate_interfaceCallCode(¤t.beginClass, type, u"QQmlParserStatus"_qs, u"classBegin"_qs); +} + +inline void +QmltcCodeGenerator::generate_completeComponentCode(QmltcType ¤t, + const QQmlJSScope::ConstPtr &type) const +{ + generate_interfaceCallCode(¤t.completeComponent, type, u"QQmlParserStatus"_qs, + u"componentComplete"_qs); +} + +inline void +QmltcCodeGenerator::generate_finalizeComponentCode(QmltcType ¤t, + const QQmlJSScope::ConstPtr &type) const +{ + generate_interfaceCallCode(¤t.finalizeComponent, type, u"QQmlFinalizerHook"_qs, + u"componentFinalized"_qs); +} + +inline void +QmltcCodeGenerator::generate_handleOnCompletedCode(QmltcType ¤t, + const QQmlJSScope::ConstPtr &type) const +{ + // QML_handleOnCompleted()'s parameters: + // * QQmltcObjectCreationHelper* creator + // * bool canFinalize [optional, when document root] + const bool isDocumentRoot = type == visitor->result(); + current.handleOnCompleted.body << u"Q_UNUSED(creator);"_qs; + if (isDocumentRoot) + current.handleOnCompleted.body << u"Q_UNUSED(canFinalize);"_qs; + + if (auto base = type->baseType(); base->isComposite()) { + current.handleOnCompleted.body << u"// call base's method"_qs; + Q_ASSERT(!isDocumentRoot || visitor->qmlTypesWithQmlBases()[0] == visitor->result()); + const auto isCurrentType = [&](const QQmlJSScope::ConstPtr &qmlType) { + return qmlType == type; + }; + const QString creationOffset = generate_typeCount(isCurrentType); + current.handleOnCompleted.body << u"{"_qs; + current.handleOnCompleted.body + << u"QQmltcObjectCreationHelper subCreator(creator, %1);"_qs.arg(creationOffset); + current.handleOnCompleted.body << u"%1::%2(&subCreator, /* finalize */ false);"_qs.arg( + base->internalName(), current.handleOnCompleted.name); + current.handleOnCompleted.body << u"}"_qs; + } + + if (!isDocumentRoot) // document root does all the work here + return; + + const auto types = visitor->pureQmlTypes(); + current.handleOnCompleted.body << u"// call children's methods"_qs; + for (qsizetype i = 1; i < types.size(); ++i) { + const auto &type = types[i]; + Q_ASSERT(!type->isComponentRootElement()); + current.handleOnCompleted.body << u"creator->get<%1>(%2)->%3(creator);"_qs.arg( + type->internalName(), QString::number(i), current.handleOnCompleted.name); + } +} + +template<typename Predicate> +inline QString QmltcCodeGenerator::generate_typeCount(Predicate p) const +{ + const QList<QQmlJSScope::ConstPtr> typesWithBaseTypeCount = visitor->qmlTypesWithQmlBases(); + QStringList components; + components.reserve(1 + typesWithBaseTypeCount.size()); + + // add this document's type counts minus document root + Q_ASSERT(visitor->pureQmlTypes().size() > 0); + components << QString::number(visitor->pureQmlTypes().size() - 1); + + // traverse types with QML base classes + for (const QQmlJSScope::ConstPtr &t : typesWithBaseTypeCount) { + if (p(t)) + break; + QString typeCountTemplate = u"QQmltcObjectCreationHelper::typeCount<%1>()"_qs; + if (t == visitor->result()) { // t is this document's root + components << typeCountTemplate.arg(t->baseTypeName()); + } else { + components << typeCountTemplate.arg(t->internalName()); + } + } + + return components.join(u" + "_qs); +} + QT_END_NAMESPACE #endif // QMLTCCOMPILERPIECES_H diff --git a/tools/qmltc/qmltcoutputir.h b/tools/qmltc/qmltcoutputir.h index 0a3e7617bd..1e586490a0 100644 --- a/tools/qmltc/qmltcoutputir.h +++ b/tools/qmltc/qmltcoutputir.h @@ -137,9 +137,10 @@ struct QmltcType QmltcCtor baselineCtor {}; // does basic contruction QmltcCtor externalCtor {}; // calls basicCtor, calls init QmltcMethod init {}; // starts object initialization (context setup), calls finalize + QmltcMethod beginClass {}; // calls QQmlParserStatus::classBegin() QmltcMethod endInit {}; // ends object initialization (with binding setup) - QmltcMethod completeComponent {}; // calls componentComplete() - QmltcMethod finalizeComponent {}; // calls componentFinalized() + QmltcMethod completeComponent {}; // calls QQmlParserStatus::componentComplete() + QmltcMethod finalizeComponent {}; // calls QQmlFinalizerHook::componentFinalized() QmltcMethod handleOnCompleted {}; // calls Component.onCompleted std::optional<QmltcDtor> dtor {}; @@ -151,7 +152,7 @@ struct QmltcType QList<QmltcProperty> properties; // QML document root specific: - std::optional<QmltcVariable> typeCount; // the number of QML types defined in a document + std::optional<QmltcMethod> typeCount; // the number of QML types defined in a document // TODO: only needed for binding callables - should not be needed, generally bool ignoreInit = false; // specifies whether init and externalCtor should be ignored diff --git a/tools/qmltc/qmltcvisitor.cpp b/tools/qmltc/qmltcvisitor.cpp index b70e3fe329..213acb89f6 100644 --- a/tools/qmltc/qmltcvisitor.cpp +++ b/tools/qmltc/qmltcvisitor.cpp @@ -29,6 +29,7 @@ #include "qmltcvisitor.h" #include <QtCore/qfileinfo.h> +#include <QtCore/qstack.h> #include <algorithm> @@ -43,6 +44,24 @@ static QString uniqueNameFromPieces(const QStringList &pieces, QHash<QString, in return possibleName; } +static bool isOrUnderComponent(QQmlJSScope::ConstPtr type) +{ + Q_ASSERT(type->isComposite()); // we're dealing with composite types here + for (; type; type = type->parentScope()) { + if (type->isWrappedInImplicitComponent()) + return true; + // for a composite type, its internalName() is guaranteed to not be a + // QQmlComponent. we need to detect a case with `Component {}` QML type + // where the *immediate* base type of the current type will be the + // QQmlComponent. note that non-composite base is different: if our type + // is not a direct child of QQmlComponent, then it is a good type that + // we have to compile + if (auto base = type->baseType(); base && base->internalName() == u"QQmlComponent") + return true; + } + return false; +} + QmltcVisitor::QmltcVisitor(QQmlJSImporter *importer, QQmlJSLogger *logger, const QString &implicitImportDirectory, const QStringList &qmldirFiles) : QQmlJSImportVisitor(importer, logger, implicitImportDirectory, qmldirFiles) @@ -90,7 +109,7 @@ void QmltcVisitor::findCppIncludes() }; // walk the whole type hierarchy - for (const QQmlJSScope::ConstPtr &type : qAsConst(m_qmlTypes)) { + for (const QQmlJSScope::ConstPtr &type : qAsConst(m_pureQmlTypes)) { // 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()) { @@ -142,15 +161,18 @@ bool QmltcVisitor::visit(QQmlJS::AST::UiObjectDefinition *object) // 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()) + if (auto base = m_currentScope->baseType(); + base && base->isComposite() && !isOrUnderComponent(m_currentScope)) { m_qmlTypesWithQmlBases.append(m_currentScope); + } return true; } void QmltcVisitor::endVisit(QQmlJS::AST::UiObjectDefinition *object) { - m_qmlTypeNames.removeLast(); + if (m_currentScope->scopeType() == QQmlJSScope::QMLScope) + m_qmlTypeNames.removeLast(); QQmlJSImportVisitor::endVisit(object); } @@ -167,8 +189,10 @@ bool QmltcVisitor::visit(QQmlJS::AST::UiObjectBinding *uiob) // 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()) + if (auto base = m_currentScope->baseType(); + base && base->isComposite() && !isOrUnderComponent(m_currentScope)) { m_qmlTypesWithQmlBases.append(m_currentScope); + } return true; } @@ -215,6 +239,20 @@ bool QmltcVisitor::visit(QQmlJS::AST::UiPublicMember *publicMember) return true; } +bool QmltcVisitor::visit(QQmlJS::AST::UiScriptBinding *scriptBinding) +{ + if (!QQmlJSImportVisitor::visit(scriptBinding)) + return false; + + { + const auto id = scriptBinding->qualifiedId; + if (!id->next && id->name == QLatin1String("id")) + m_typesWithId[m_currentScope] = -1; // temporary value + } + + return true; +} + bool QmltcVisitor::visit(QQmlJS::AST::UiInlineComponent *component) { if (!QQmlJSImportVisitor::visit(component)) @@ -229,8 +267,186 @@ bool QmltcVisitor::visit(QQmlJS::AST::UiInlineComponent *component) void QmltcVisitor::endVisit(QQmlJS::AST::UiProgram *program) { QQmlJSImportVisitor::endVisit(program); + if (!m_exportedRootScope) // in case we failed badly + return; + + QHash<QQmlJSScope::ConstPtr, QList<QQmlJSMetaPropertyBinding>> bindings; + for (const QQmlJSScope::ConstPtr &type : qAsConst(m_qmlTypes)) { + if (isOrUnderComponent(type)) + continue; + bindings.insert(type, type->ownPropertyBindingsInQmlIROrder()); + } + postVisitResolve(bindings); findCppIncludes(); } +QQmlJSScope::ConstPtr fetchType(const QQmlJSMetaPropertyBinding &binding) +{ + switch (binding.bindingType()) { + case QQmlJSMetaPropertyBinding::Object: + return binding.objectType(); + case QQmlJSMetaPropertyBinding::Interceptor: + return binding.interceptorType(); + case QQmlJSMetaPropertyBinding::ValueSource: + return binding.valueSourceType(); + // TODO: AttachedProperty and GroupProperty are not supported yet, + // but have to also be acknowledged here + default: + return {}; + } + return {}; +} + +template<typename Predicate> +void iterateTypes( + const QQmlJSScope::ConstPtr &root, + const QHash<QQmlJSScope::ConstPtr, QList<QQmlJSMetaPropertyBinding>> &qmlIrOrderedBindings, + Predicate predicate) +{ + // NB: depth-first-search is used here to mimic various QmlIR passes + QStack<QQmlJSScope::ConstPtr> types; + types.push(root); + while (!types.isEmpty()) { + auto current = types.pop(); + + if (predicate(current)) + continue; + + if (isOrUnderComponent(current)) // ignore implicit/explicit components + continue; + + Q_ASSERT(qmlIrOrderedBindings.contains(current)); + const auto &bindings = qmlIrOrderedBindings[current]; + // reverse the binding order here, because stack processes right-most + // child first and we need left-most first + for (auto it = bindings.rbegin(); it != bindings.rend(); ++it) { + const auto &binding = *it; + if (auto type = fetchType(binding)) + types.push(type); + } + } +} + +template<typename Predicate> +void iterateBindings( + const QQmlJSScope::ConstPtr &root, + const QHash<QQmlJSScope::ConstPtr, QList<QQmlJSMetaPropertyBinding>> &qmlIrOrderedBindings, + Predicate predicate) +{ + // NB: depth-first-search is used here to mimic various QmlIR passes + QStack<QQmlJSScope::ConstPtr> types; + types.push(root); + while (!types.isEmpty()) { + auto current = types.pop(); + + if (isOrUnderComponent(current)) // ignore implicit/explicit components + continue; + + Q_ASSERT(qmlIrOrderedBindings.contains(current)); + const auto &bindings = qmlIrOrderedBindings[current]; + // reverse the binding order here, because stack processes right-most + // child first and we need left-most first + for (auto it = bindings.rbegin(); it != bindings.rend(); ++it) { + const auto &binding = *it; + + if (predicate(current, binding)) + continue; + + if (auto type = fetchType(binding)) + types.push(type); + } + } +} + +void QmltcVisitor::postVisitResolve( + const QHash<QQmlJSScope::ConstPtr, QList<QQmlJSMetaPropertyBinding>> &qmlIrOrderedBindings) +{ + // This is a special function that must be called after + // QQmlJSImportVisitor::endVisit(QQmlJS::AST::UiProgram *). It is used to + // resolve things that couldn't be resolved during the AST traversal, such + // as anything that is dependent on implicit or explicit components + + // 1. find types that are part of the deferred bindings (we care about + // *types* exclusively here) + QSet<QQmlJSScope::ConstPtr> deferredTypes; + const auto findDeferred = [&deferredTypes](const QQmlJSScope::ConstPtr &type, + const QQmlJSMetaPropertyBinding &binding) { + if (binding.hasObject() || binding.hasInterceptor() || binding.hasValueSource()) { + const QString propertyName = binding.propertyName(); + Q_ASSERT(!propertyName.isEmpty()); + if (type->isNameDeferred(propertyName)) { + deferredTypes.insert(fetchType(binding)); + return true; + } + } + return false; + }; + iterateBindings(m_exportedRootScope, qmlIrOrderedBindings, findDeferred); + + const auto isOrUnderDeferred = [&deferredTypes](QQmlJSScope::ConstPtr type) { + for (; type; type = type->parentScope()) { + if (deferredTypes.contains(type)) + return true; + } + return false; + }; + + // 2. find all "pure" QML types + m_pureQmlTypes.reserve(m_qmlTypes.size()); + for (qsizetype i = 0; i < m_qmlTypes.size(); ++i) { + const QQmlJSScope::ConstPtr &type = m_qmlTypes.at(i); + + if (isOrUnderComponent(type) || isOrUnderDeferred(type)) { + // root is special: we compile Component roots. root is also never + // deferred, so in case `isOrUnderDeferred(type)` returns true, we + // always continue here + if (type != m_exportedRootScope) + continue; + } + + m_pureTypeIndices[type] = m_pureQmlTypes.size(); + m_pureQmlTypes.append(type); + } + + // filter out deferred types + { + QList<QQmlJSScope::ConstPtr> filteredQmlTypesWithQmlBases; + filteredQmlTypesWithQmlBases.reserve(m_qmlTypesWithQmlBases.size()); + std::copy_if(m_qmlTypesWithQmlBases.cbegin(), m_qmlTypesWithQmlBases.cend(), + std::back_inserter(filteredQmlTypesWithQmlBases), + [&](const QQmlJSScope::ConstPtr &type) { return !isOrUnderDeferred(type); }); + qSwap(m_qmlTypesWithQmlBases, filteredQmlTypesWithQmlBases); + } + + // 3. figure synthetic indices of QQmlComponent-wrapped types + int syntheticCreationIndex = -1; + const auto addSyntheticIndex = [&](const QQmlJSScope::ConstPtr &type) mutable { + if (type->isComponentRootElement()) { + m_syntheticTypeIndices[type] = ++syntheticCreationIndex; + return true; + } + return false; + }; + iterateTypes(m_exportedRootScope, qmlIrOrderedBindings, addSyntheticIndex); + + // 4. figure runtime object ids for non-component wrapped types + int currentId = 0; + const auto setRuntimeId = [&](const QQmlJSScope::ConstPtr &type) mutable { + // fancy way to call type->isComponentRootElement(). any type that is + // considered synthetic shouldn't be processed here. even if it has id, + // it doesn't need to be set by qmltc + if (m_syntheticTypeIndices.contains(type)) + return true; + + if (m_typesWithId.contains(type)) + m_typesWithId[type] = currentId++; + + // otherwise we need to `return true` here + Q_ASSERT(!type->isInlineComponent()); + return false; + }; + iterateTypes(m_exportedRootScope, qmlIrOrderedBindings, setRuntimeId); +} + QT_END_NAMESPACE diff --git a/tools/qmltc/qmltcvisitor.h b/tools/qmltc/qmltcvisitor.h index f3a4babbda..b3ba6e91aa 100644 --- a/tools/qmltc/qmltcvisitor.h +++ b/tools/qmltc/qmltcvisitor.h @@ -42,6 +42,8 @@ QT_BEGIN_NAMESPACE class QmltcVisitor : public QQmlJSImportVisitor { void findCppIncludes(); + void postVisitResolve(const QHash<QQmlJSScope::ConstPtr, QList<QQmlJSMetaPropertyBinding>> + &qmlIrOrderedBindings); public: QmltcVisitor(QQmlJSImporter *importer, QQmlJSLogger *logger, @@ -54,6 +56,8 @@ public: bool visit(QQmlJS::AST::UiObjectBinding *) override; void endVisit(QQmlJS::AST::UiObjectBinding *) override; + bool visit(QQmlJS::AST::UiScriptBinding *) override; + bool visit(QQmlJS::AST::UiPublicMember *) override; bool visit(QQmlJS::AST::UiInlineComponent *) override; @@ -63,11 +67,59 @@ public: QList<QQmlJSScope::ConstPtr> qmlTypesWithQmlBases() const { return m_qmlTypesWithQmlBases; } QSet<QString> cppIncludeFiles() const { return m_cppIncludes; } + qsizetype creationIndex(const QQmlJSScope::ConstPtr &type) const + { + Q_ASSERT(m_pureTypeIndices.contains(type)); + Q_ASSERT(type->scopeType() == QQmlJSScope::QMLScope); + return m_pureTypeIndices[type]; + } + + qsizetype qmlComponentIndex(const QQmlJSScope::ConstPtr &type) const + { + Q_ASSERT(m_syntheticTypeIndices.contains(type)); + Q_ASSERT(type->scopeType() == QQmlJSScope::QMLScope); + Q_ASSERT(type->isComponentRootElement()); + return m_syntheticTypeIndices[type] + qmlTypes().size(); + } + + /*! \internal + Returns a runtime index counterpart of `id: foo` for \a type. Returns -1 + if \a type does not have an id. + */ + int runtimeId(const QQmlJSScope::ConstPtr &type) const + { + // NB: this function is expected to be called for "pure" types + Q_ASSERT(!m_typesWithId.contains(type) || m_typesWithId[type] != -1); + return m_typesWithId.value(type, -1); + } + + /*! \internal + Returns all encountered QML types. + */ + QList<QQmlJSScope::ConstPtr> allQmlTypes() const { return qmlTypes(); } + + /*! \internal + Returns encountered QML types which return \c false in + \c{isComponentRootElement()}. Called "pure", because these are the ones + that are not wrapped into QQmlComponent. Pure QML types can be created + through direct constructor invocation. + */ + QList<QQmlJSScope::ConstPtr> pureQmlTypes() const { return m_pureQmlTypes; } + 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 + QList<QQmlJSScope::ConstPtr> m_pureQmlTypes; // the ones not under QQmlComponent + + QHash<QQmlJSScope::ConstPtr, qsizetype> m_pureTypeIndices; + QHash<QQmlJSScope::ConstPtr, qsizetype> m_syntheticTypeIndices; + + // prefer allQmlTypes or pureQmlTypes. this function is misleading in qmltc + QList<QQmlJSScope::ConstPtr> qmlTypes() const { return QQmlJSImportVisitor::qmlTypes(); } + + QHash<QQmlJSScope::ConstPtr, int> m_typesWithId; }; QT_END_NAMESPACE |