diff options
author | Andrei Golubev <andrei.golubev@qt.io> | 2022-04-22 12:51:41 +0200 |
---|---|---|
committer | Andrei Golubev <andrei.golubev@qt.io> | 2022-05-03 15:25:11 +0200 |
commit | 229903122536a2ebeaf91e41dc9376c464b1c2e0 (patch) | |
tree | 147bcfcb00c8b00a1663a21c1d8ff5ef7eabcda4 /tools | |
parent | 82b76e4eb57b4da46d8e7c24bd0b83310c2164b2 (diff) |
qmltc: Move alias compilation to qqmltccompiler
Introduce an alias resolution procedure to query the origin
property and its owner for a given alias
Add alias compilation to the "proper" qmltc code and remove
the equivalent one from the prototype
Change-Id: I55bc1e3e6206b4cfce259526d1bc2813e8ea7cfb
Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
Diffstat (limited to 'tools')
-rw-r--r-- | tools/qmltc/prototype/codegenerator.cpp | 222 | ||||
-rw-r--r-- | tools/qmltc/prototype/codegenerator.h | 3 | ||||
-rw-r--r-- | tools/qmltc/qmltccompiler.cpp | 226 | ||||
-rw-r--r-- | tools/qmltc/qmltccompiler.h | 2 | ||||
-rw-r--r-- | tools/qmltc/qmltccompilerpieces.h | 12 |
5 files changed, 230 insertions, 235 deletions
diff --git a/tools/qmltc/prototype/codegenerator.cpp b/tools/qmltc/prototype/codegenerator.cpp index eb2e0ef23b..129ff681af 100644 --- a/tools/qmltc/prototype/codegenerator.cpp +++ b/tools/qmltc/prototype/codegenerator.cpp @@ -327,234 +327,12 @@ bool CodeGenerator::ignoreObject(const CodeGenObject &object) const return false; } -QString buildCallSpecialMethodValue(bool documentRoot, const QString &outerFlagName, - bool overridesInterface) -{ - const QString callInBase = overridesInterface ? u"false"_s : u"true"_s; - if (documentRoot) { - return outerFlagName + u" && " + callInBase; - } else { - return callInBase; - } -} - static QString generate_callCompilationUnit(const QString &urlMethodName) { // NB: assume `engine` variable always exists return u"QQmlEnginePrivate::get(engine)->compilationUnitFromUrl(%1())"_s.arg(urlMethodName); } -void CodeGenerator::compileAlias(QmltcType ¤t, const QQmlJSMetaProperty &alias, - const QQmlJSScope::ConstPtr &owner) -{ - const QString aliasName = alias.propertyName(); - Q_ASSERT(!aliasName.isEmpty()); - - QStringList aliasExprBits = alias.aliasExpression().split(u'.'); - Q_ASSERT(!aliasExprBits.isEmpty()); - QString idString = aliasExprBits.front(); - Q_ASSERT(!idString.isEmpty()); - QQmlJSScope::ConstPtr type = m_localTypeResolver->scopeForId(idString, owner); - Q_ASSERT(type); - const bool aliasToProperty = aliasExprBits.size() > 1; // and not an object - - QString latestAccessor = u"this"_s; - QString latestAccessorNonPrivate; // TODO: crutch for notify - QStringList prologue; - QStringList writeSpecificEpilogue; - - if (owner != type) { // cannot start at `this`, need to fetch object through context at run time - Q_ASSERT(m_typeToObjectIndex.contains(type) - && m_typeToObjectIndex[type] <= m_objects.size()); - const int id = m_objects[m_typeToObjectIndex[type]].irObject->id; - Q_ASSERT(id >= 0); // since the type is found by id, it must have an id - - // TODO: the context fetching must be done once in the notify code - // (across multiple alias properties). now there's a huge duplication - prologue << u"auto context = QQmlData::get(this)->outerContext;"_s; - // there's a special case: when `this` type has compiled QML type as a base - // type and it is not a root, it has a non-root first context, so we need to - // step one level up - if (m_qmlCompiledBaseTypes.contains(owner->baseTypeName()) - && m_typeToObjectIndex[owner] != 0) { - Q_ASSERT(!owner->baseTypeName().isEmpty()); - prologue << u"// `this` is special: not a root and its base type is compiled"_s; - prologue << u"context = context->parent().data();"_s; - } - // doing the above allows us to lookup id object by integer, which is fast - latestAccessor = u"alias_objectById_" + idString; // unique enough - if (aliasToProperty) { - prologue << u"auto " + latestAccessor + u" = static_cast<" + type->internalName() - + u"*>(context->idValue(" + QString::number(id) + u"));"; - } else { - // kind of a crutch for object aliases: expect that the user knows - // what to do and so accept QObject* - prologue << u"QObject *" + latestAccessor + u" = context->idValue(" - + QString::number(id) + u");"; - } - prologue << u"Q_ASSERT(" + latestAccessor + u");"; - } - - struct AliasData - { - QString underlyingType; - QString readLine; - QString writeLine; - QString bindableLine; - } info; // note, we only really need this crutch to cover the alias to object case - QQmlJSMetaProperty resultingProperty; - - if (aliasToProperty) { - // the following code goes to prologue section. the idea of that is to - // construct the necessary code to fetch the resulting property - for (qsizetype i = 1; i < aliasExprBits.size() - 1; ++i) { - const QString &bit = qAsConst(aliasExprBits)[i]; - Q_ASSERT(!bit.isEmpty()); - Q_ASSERT(type); - QQmlJSMetaProperty p = type->property(bit); - - QString currAccessor = QmltcCodeGenerator::wrap_privateClass(latestAccessor, p); - if (p.type()->accessSemantics() == QQmlJSScope::AccessSemantics::Value) { - // we need to read the property to a local variable and then - // write the updated value once the actual operation is done - QString localVariableName = u"alias_" + bit; // should be fairly unique - prologue << u"auto " + localVariableName + u" = " + currAccessor + u"->" + p.read() - + u"();"; - writeSpecificEpilogue - << currAccessor + u"->" + p.write() + u"(" + localVariableName + u");"; - // NB: since accessor becomes a value type, wrap it into an - // addressof operator so that we could access it as a pointer - currAccessor = QmltcCodeGenerator::wrap_addressof(localVariableName); // reset - } else { - currAccessor += u"->" + p.read() + u"()"; // amend - } - - type = p.type(); - latestAccessor = currAccessor; - } - - resultingProperty = type->property(aliasExprBits.back()); - latestAccessorNonPrivate = latestAccessor; - latestAccessor = QmltcCodeGenerator::wrap_privateClass(latestAccessor, resultingProperty); - info.underlyingType = resultingProperty.type()->internalName(); - if (resultingProperty.isList()) { - info.underlyingType = u"QQmlListProperty<" + info.underlyingType + u">"; - } else if (resultingProperty.type()->isReferenceType()) { - info.underlyingType += u"*"_s; - } - // reset to generic type when having alias to id: - if (m_aliasesToIds.contains(resultingProperty)) - info.underlyingType = u"QObject*"_s; - - // READ must always be present - info.readLine = latestAccessor + u"->" + resultingProperty.read() + u"()"; - if (QString setName = resultingProperty.write(); !setName.isEmpty()) - info.writeLine = latestAccessor + u"->" + setName + u"(%1)"; - if (QString bindableName = resultingProperty.bindable(); !bindableName.isEmpty()) - info.bindableLine = latestAccessor + u"->" + bindableName + u"()"; - } else { - info.underlyingType = u"QObject*"_s; - info.readLine = latestAccessor; - // NB: id-pointing aliases are read-only, also alias to object is not - // bindable since it's not a QProperty itself - } - - QStringList mocLines; - mocLines.reserve(10); - mocLines << info.underlyingType << aliasName; - - QmltcPropertyData compilationData(aliasName); - // 1. add setter and getter - if (!info.readLine.isEmpty()) { - QmltcMethod getter {}; - getter.returnType = info.underlyingType; - getter.name = compilationData.read; - getter.body += prologue; - getter.body << u"return " + info.readLine + u";"; - // getter.body += writeSpecificEpilogue; - getter.userVisible = true; - current.functions.emplaceBack(getter); - mocLines << u"READ"_s << getter.name; - } // else always an error? - - if (!info.writeLine.isEmpty()) { - QmltcMethod setter {}; - setter.returnType = u"void"_s; - setter.name = compilationData.write; - - QList<QQmlJSMetaMethod> methods = type->methods(resultingProperty.write()); - if (methods.isEmpty()) { - // QmltcVariable - setter.parameterList.emplaceBack(QQmlJSUtils::constRefify(info.underlyingType), - aliasName + u"_", u""_s); - } else { - setter.parameterList = compileMethodParameters(methods.at(0).parameterNames(), - methods.at(0).parameterTypes(), true); - } - - setter.body += prologue; - if (aliasToProperty) { - QStringList parameterNames; - parameterNames.reserve(setter.parameterList.size()); - std::transform(setter.parameterList.cbegin(), setter.parameterList.cend(), - std::back_inserter(parameterNames), - [](const QmltcVariable &x) { return x.name; }); - QString commaSeparatedParameterNames = parameterNames.join(u", "_s); - setter.body << info.writeLine.arg(commaSeparatedParameterNames) + u";"; - } else { - setter.body << info.writeLine + u";"; - } - setter.body += writeSpecificEpilogue; - setter.userVisible = true; - current.functions.emplaceBack(setter); - mocLines << u"WRITE"_s << setter.name; - } - - // 2. add bindable - if (!info.bindableLine.isEmpty()) { - QmltcMethod bindable {}; - bindable.returnType = u"QBindable<" + info.underlyingType + u">"; - bindable.name = compilationData.bindable; - bindable.body += prologue; - bindable.body << u"return " + info.bindableLine + u";"; - bindable.userVisible = true; - current.functions.emplaceBack(bindable); - mocLines << u"BINDABLE"_s << bindable.name; - } - // 3. add notify - which is pretty special - if (QString notifyName = resultingProperty.notify(); !notifyName.isEmpty()) { - // notify is very special (even more than reasonable) - current.endInit.body << u"{ // alias notify connection:"_s; - // TODO: part of the prologue (e.g. QQmlData::get(this)->outerContext) - // must be shared across different notifies (to speed up finalize) - current.endInit.body += prologue; - // TODO: use non-private accessor since signals must exist on the public - // type, not on the private one -- otherwise, they can't be used from - // C++ which is not what's wanted (this is a mess) - current.endInit.body << u"QObject::connect(" + latestAccessorNonPrivate + u", &" - + type->internalName() + u"::" + notifyName + u", this, &" + current.cppType - + u"::" + compilationData.notify + u");"; - current.endInit.body << u"}"_s; - } - - // 4. add moc entry - // Q_PROPERTY(QString text READ text WRITE setText BINDABLE bindableText NOTIFY textChanged) - current.mocCode << u"Q_PROPERTY(" + mocLines.join(u" "_s) + u")"; - - // 5. add extra moc entry if this alias is default one - if (aliasName == owner->defaultPropertyName()) { - // Q_CLASSINFO("DefaultProperty", propertyName) - current.mocCode << u"Q_CLASSINFO(\"DefaultProperty\", \"%1\")"_s.arg(aliasName); - } -} - -template<typename Iterator> -static QString getPropertyOrAliasNameFromIr(const QmlIR::Document *doc, Iterator first, int pos) -{ - std::advance(first, pos); // assume that first is correct - due to earlier check - return doc->stringAt(first->nameIndex); -} - void CodeGenerator::compileBinding(QmltcType ¤t, const QmlIR::Binding &binding, const CodeGenObject &object, const CodeGenerator::AccessorData &accessor) diff --git a/tools/qmltc/prototype/codegenerator.h b/tools/qmltc/prototype/codegenerator.h index 0e0b7a1bc2..c8c5fff195 100644 --- a/tools/qmltc/prototype/codegenerator.h +++ b/tools/qmltc/prototype/codegenerator.h @@ -165,9 +165,6 @@ private: bool m_isAnonymous = false; // crutch to distinguish QML_ELEMENT from QML_ANONYMOUS public: - void compileAlias(QmltcType ¤t, const QQmlJSMetaProperty &alias, - const QQmlJSScope::ConstPtr &owner); - // helper structure that holds the information necessary for most bindings, // such as accessor name, which is used to reference the properties like: // (accessor.name)->(propertyName): this->myProperty. it is also used in diff --git a/tools/qmltc/qmltccompiler.cpp b/tools/qmltc/qmltccompiler.cpp index 52d913dfe5..4cb695a0f4 100644 --- a/tools/qmltc/qmltccompiler.cpp +++ b/tools/qmltc/qmltccompiler.cpp @@ -345,7 +345,7 @@ void QmltcCompiler::compileTypeElements(QmltcType ¤t, const QQmlJSScope::C continue; } if (p.isAlias()) { - m_prototypeCodegen->compileAlias(current, p, type); + compileAlias(current, p, type); } else { compileProperty(current, p, type); } @@ -541,6 +541,230 @@ void QmltcCompiler::compileProperty(QmltcType ¤t, const QQmlJSMetaProperty compilationData.notify); } +struct AliasResolutionFrame +{ + static QString inVar; + QStringList prologue; + QStringList epilogueForWrite; + QString outVar; +}; +// special string replaced by outVar of the previous frame +QString AliasResolutionFrame::inVar = QStringLiteral("__QMLTC_ALIAS_FRAME_INPUT_VAR__"); + +static void unpackFrames(QStack<AliasResolutionFrame> &frames) +{ + if (frames.size() < 2) + return; + + // assume first frame is fine + auto prev = frames.begin(); + for (auto it = std::next(prev); it != frames.end(); ++it, ++prev) { + for (QString &line : it->prologue) + line.replace(AliasResolutionFrame::inVar, prev->outVar); + for (QString &line : it->epilogueForWrite) + line.replace(AliasResolutionFrame::inVar, prev->outVar); + it->outVar.replace(AliasResolutionFrame::inVar, prev->outVar); + } +} + +template<typename Projection> +static QStringList joinFrames(const QStack<AliasResolutionFrame> &frames, Projection project) +{ + QStringList joined; + for (const AliasResolutionFrame &frame : frames) + joined += project(frame); + return joined; +} + +void QmltcCompiler::compileAlias(QmltcType ¤t, const QQmlJSMetaProperty &alias, + const QQmlJSScope::ConstPtr &owner) +{ + const QString aliasName = alias.propertyName(); + Q_ASSERT(!aliasName.isEmpty()); + + QStringList aliasExprBits = alias.aliasExpression().split(u'.'); + Q_ASSERT(!aliasExprBits.isEmpty()); + + QStack<AliasResolutionFrame> frames; + + QQmlJSUtils::AliasResolutionVisitor aliasVisitor; + qsizetype i = 0; + aliasVisitor.reset = [&]() { + frames.clear(); + i = 0; // we use it in property processing + + // first frame is a dummy one: + frames.push(AliasResolutionFrame { QStringList(), QStringList(), u"this"_s }); + }; + aliasVisitor.processResolvedId = [&](const QQmlJSScope::ConstPtr &type) { + Q_ASSERT(type); + if (owner != type) { // cannot start at `this`, need to fetch object through context + const int id = m_visitor->runtimeId(type); + Q_ASSERT(id >= 0); // since the type is found by id, it must have an id + + AliasResolutionFrame queryIdFrame {}; + queryIdFrame.prologue << u"auto context = QQmlData::get(%1)->outerContext;"_s.arg( + AliasResolutionFrame::inVar); + // there's a special case: when `this` type has compiled QML type as + // a base type and it is not a root, it has a non-root first + // context, so we need to step one level up + if (QQmlJSUtils::hasCompositeBase(owner) && owner != m_visitor->result()) { + Q_ASSERT(!owner->baseTypeName().isEmpty()); + queryIdFrame.prologue + << u"// `this` is special: not a root and its base type is compiled"_s; + queryIdFrame.prologue << u"context = context->parent().data();"_s; + } + + // doing the above allows us to lookup id object by index (fast) + queryIdFrame.outVar = u"alias_objectById_" + aliasExprBits.front(); // unique enough + queryIdFrame.prologue << u"auto " + queryIdFrame.outVar + u" = static_cast<" + + type->internalName() + u"*>(context->idValue(" + QString::number(id) + + u"));"; + queryIdFrame.prologue << u"Q_ASSERT(" + queryIdFrame.outVar + u");"; + + frames.push(queryIdFrame); + } + }; + aliasVisitor.processResolvedProperty = [&](const QQmlJSMetaProperty &p, + const QQmlJSScope::ConstPtr &) { + AliasResolutionFrame queryPropertyFrame {}; + + QString inVar = QmltcCodeGenerator::wrap_privateClass(AliasResolutionFrame::inVar, p); + if (p.type()->accessSemantics() == QQmlJSScope::AccessSemantics::Value) { + // we need to read the property to a local variable and then + // write the updated value once the actual operation is done + const QString aliasVar = u"alias_" + QString::number(i); // should be fairly unique + ++i; + queryPropertyFrame.prologue + << u"auto " + aliasVar + u" = " + inVar + u"->" + p.read() + u"();"; + queryPropertyFrame.epilogueForWrite + << inVar + u"->" + p.write() + u"(" + aliasVar + u");"; + // NB: since accessor becomes a value type, wrap it into an + // addressof operator so that we could access it as a pointer + inVar = QmltcCodeGenerator::wrap_addressof(aliasVar); // reset + } else { + inVar += u"->" + p.read() + u"()"; // update + } + queryPropertyFrame.outVar = inVar; + + frames.push(queryPropertyFrame); + }; + + QQmlJSUtils::ResolvedAlias result = + QQmlJSUtils::resolveAlias(m_typeResolver, alias, owner, aliasVisitor); + Q_ASSERT(result.kind != QQmlJSUtils::AliasTarget_Invalid); + + unpackFrames(frames); + + if (result.kind == QQmlJSUtils::AliasTarget_Property) { + // we don't need the last frame here + frames.pop(); + + // instead, add a custom frame + AliasResolutionFrame customFinalFrame {}; + customFinalFrame.outVar = + QmltcCodeGenerator::wrap_privateClass(frames.top().outVar, result.property); + frames.push(customFinalFrame); + } + + const QString latestAccessor = frames.top().outVar; + const QStringList prologue = + joinFrames(frames, [](const AliasResolutionFrame &frame) { return frame.prologue; }); + const QString underlyingType = (result.kind == QQmlJSUtils::AliasTarget_Property) + ? getUnderlyingType(result.property) + : result.owner->internalName() + u" *"; + + QStringList mocLines; + mocLines.reserve(10); + mocLines << underlyingType << aliasName; + + QmltcPropertyData compilationData(aliasName); + // 1. add setter and getter + QmltcMethod getter {}; + getter.returnType = underlyingType; + getter.name = compilationData.read; + getter.body += prologue; + if (result.kind == QQmlJSUtils::AliasTarget_Property) + getter.body << u"return " + latestAccessor + u"->" + result.property.read() + u"();"; + else // AliasTarget_Object + getter.body << u"return " + latestAccessor + u";"; + getter.userVisible = true; + current.functions.emplaceBack(getter); + mocLines << u"READ"_s << getter.name; + + if (QString setName = result.property.write(); !setName.isEmpty()) { + Q_ASSERT(result.kind == QQmlJSUtils::AliasTarget_Property); // property is invalid otherwise + QmltcMethod setter {}; + setter.returnType = u"void"_s; + setter.name = compilationData.write; + + QList<QQmlJSMetaMethod> methods = result.owner->methods(setName); + if (methods.isEmpty()) { // when we are compiling the property as well + // QmltcVariable + setter.parameterList.emplaceBack(QQmlJSUtils::constRefify(underlyingType), + aliasName + u"_", u""_s); + } else { + setter.parameterList = compileMethodParameters(methods.at(0).parameterNames(), + methods.at(0).parameterTypes(), + /* allow unnamed = */ true); + } + + setter.body += prologue; + QStringList parameterNames; + parameterNames.reserve(setter.parameterList.size()); + std::transform(setter.parameterList.cbegin(), setter.parameterList.cend(), + std::back_inserter(parameterNames), + [](const QmltcVariable &x) { return x.name; }); + QString commaSeparatedParameterNames = parameterNames.join(u", "_s); + setter.body << latestAccessor + u"->" + setName + + u"(%1)"_s.arg(commaSeparatedParameterNames) + u";"; + setter.body += joinFrames( + frames, [](const AliasResolutionFrame &frame) { return frame.epilogueForWrite; }); + setter.userVisible = true; + current.functions.emplaceBack(setter); + mocLines << u"WRITE"_s << setter.name; + } + // 2. add bindable + if (QString bindableName = result.property.bindable(); !bindableName.isEmpty()) { + Q_ASSERT(result.kind == QQmlJSUtils::AliasTarget_Property); // property is invalid otherwise + QmltcMethod bindable {}; + bindable.returnType = u"QBindable<" + underlyingType + u">"; + bindable.name = compilationData.bindable; + bindable.body += prologue; + bindable.body << u"return " + latestAccessor + u"->" + bindableName + u"()" + u";"; + bindable.userVisible = true; + current.functions.emplaceBack(bindable); + mocLines << u"BINDABLE"_s << bindable.name; + } + // 3. add notify - which is pretty special + if (QString notifyName = result.property.notify(); !notifyName.isEmpty()) { + Q_ASSERT(result.kind == QQmlJSUtils::AliasTarget_Property); // property is invalid otherwise + + // notify is very special + current.endInit.body << u"{ // alias notify connection:"_s; + current.endInit.body += prologue; + // TODO: use non-private accessor since signals must exist on the public + // type, not on the private one -- otherwise, you can't connect to a + // private property signal in C++ and so it is useless (hence, use + // public type) + const QString latestAccessorNonPrivate = frames[frames.size() - 2].outVar; + current.endInit.body << u"QObject::connect(" + latestAccessorNonPrivate + u", &" + + result.owner->internalName() + u"::" + notifyName + u", this, &" + + current.cppType + u"::" + compilationData.notify + u");"; + current.endInit.body << u"}"_s; + } + + // 4. add moc entry + // Q_PROPERTY(QString text READ text WRITE setText BINDABLE bindableText NOTIFY textChanged) + current.mocCode << u"Q_PROPERTY(" + mocLines.join(u" "_s) + u")"; + + // 5. add extra moc entry if this alias is default one + if (aliasName == owner->defaultPropertyName()) { + // Q_CLASSINFO("DefaultProperty", propertyName) + current.mocCode << u"Q_CLASSINFO(\"DefaultProperty\", \"%1\")"_s.arg(aliasName); + } +} + void QmltcCompiler::compileBinding(QmltcType ¤t, const QQmlJSMetaPropertyBinding &binding, const QQmlJSScope::ConstPtr &type, const BindingAccessorData &accessor) diff --git a/tools/qmltc/qmltccompiler.h b/tools/qmltc/qmltccompiler.h index 0cef51f064..ef33aae393 100644 --- a/tools/qmltc/qmltccompiler.h +++ b/tools/qmltc/qmltccompiler.h @@ -79,6 +79,8 @@ private: const QQmlJSScope::ConstPtr &owner); void compileProperty(QmltcType ¤t, const QQmlJSMetaProperty &p, const QQmlJSScope::ConstPtr &owner); + void compileAlias(QmltcType ¤t, const QQmlJSMetaProperty &alias, + const QQmlJSScope::ConstPtr &owner); /*! \internal diff --git a/tools/qmltc/qmltccompilerpieces.h b/tools/qmltc/qmltccompilerpieces.h index 282985c30b..1e0f19ad19 100644 --- a/tools/qmltc/qmltccompilerpieces.h +++ b/tools/qmltc/qmltccompilerpieces.h @@ -33,6 +33,8 @@ #include <QtCore/qstringbuilder.h> #include <QtCore/qfileinfo.h> +#include <private/qqmljsutils_p.h> + #include "qmltcoutputir.h" #include "qmltcvisitor.h" @@ -160,17 +162,9 @@ inline decltype(auto) QmltcCodeGenerator::generate_initCode(QmltcType ¤t, 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)) { + parentScope != visitor->result() && QQmlJSUtils::hasCompositeBase(parentScope)) { current.init.body << u"// NB: context->parent() is the context of this document"_s; current.init.body << u"context = context->parent();"_s; } |