From 85f15e2af40be1e9500fe600daba31839b904ef4 Mon Sep 17 00:00:00 2001 From: Fabian Kosmale Date: Thu, 30 Jan 2020 10:51:34 +0100 Subject: Required properties: Allow retroactive require specification It is now possible to mark a property of a parent class as required in the child by writing required Change-Id: I9e9d58c7b5c00577b056e905b39744b2fa359ea0 Reviewed-by: Ulf Hermann --- src/qml/common/qv4compileddata_p.h | 30 +++++++++-- src/qml/compiler/qqmlirbuilder.cpp | 59 +++++++++++++++++++++- src/qml/compiler/qqmlirbuilder_p.h | 13 +++++ src/qml/parser/qqmljs.g | 13 ++++- src/qml/parser/qqmljsast.cpp | 8 +++ src/qml/parser/qqmljsast_p.h | 25 ++++++++- src/qml/parser/qqmljsastfwd_p.h | 1 + src/qml/parser/qqmljsastvisitor_p.h | 3 ++ src/qml/qml/qqmlincubator_p.h | 4 +- src/qml/qml/qqmlobjectcreator.cpp | 40 +++++++++++---- src/qml/qml/qqmlobjectcreator_p.h | 2 +- tests/auto/qml/qmlformat/tst_qmlformat.cpp | 1 - tests/auto/qml/qmlmin/tst_qmlmin.cpp | 1 - .../auto/qml/qqmllanguage/data/NonRequiredBase.qml | 5 ++ tests/auto/qml/qqmllanguage/data/RequiredBase.qml | 3 ++ .../qml/qqmllanguage/data/requiredProperties.3.qml | 6 +++ .../qml/qqmllanguage/data/requiredProperties.4.qml | 5 ++ .../qml/qqmllanguage/data/requiredProperties.5.qml | 1 + .../qml/qqmllanguage/data/requiredProperties.6.qml | 3 ++ .../qml/qqmllanguage/data/requiredProperties.7.qml | 5 ++ tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp | 30 +++++++++++ 21 files changed, 237 insertions(+), 21 deletions(-) create mode 100644 tests/auto/qml/qqmllanguage/data/NonRequiredBase.qml create mode 100644 tests/auto/qml/qqmllanguage/data/RequiredBase.qml create mode 100644 tests/auto/qml/qqmllanguage/data/requiredProperties.3.qml create mode 100644 tests/auto/qml/qqmllanguage/data/requiredProperties.4.qml create mode 100644 tests/auto/qml/qqmllanguage/data/requiredProperties.5.qml create mode 100644 tests/auto/qml/qqmllanguage/data/requiredProperties.6.qml create mode 100644 tests/auto/qml/qqmllanguage/data/requiredProperties.7.qml diff --git a/src/qml/common/qv4compileddata_p.h b/src/qml/common/qv4compileddata_p.h index 42c476763e..a5a1cf8969 100644 --- a/src/qml/common/qv4compileddata_p.h +++ b/src/qml/common/qv4compileddata_p.h @@ -75,7 +75,7 @@ QT_BEGIN_NAMESPACE // Also change the comment behind the number to describe the latest change. This has the added // benefit that if another patch changes the version too, it will result in a merge conflict, and // not get removed silently. -#define QV4_DATA_STRUCTURE_VERSION 0x28// support inline components +#define QV4_DATA_STRUCTURE_VERSION 0x29// support additional required property features class QIODevice; class QQmlTypeNameCache; @@ -695,6 +695,12 @@ struct Property }; static_assert(sizeof(Property) == 12, "Property structure needs to have the expected size to be binary compatible on disk when generated by host compiler and loaded by target"); +struct RequiredPropertyExtraData { + quint32_le nameIndex; +}; + +static_assert (sizeof(RequiredPropertyExtraData) == 4, "RequiredPropertyExtraData structure needs to have the expected size to be binary compatible on disk when generated by host compiler and loaded by target"); + struct Alias { enum Flags : unsigned int { IsReadOnly = 0x1, @@ -765,13 +771,16 @@ struct Object Location locationOfIdProperty; quint32_le offsetToInlineComponents; quint16_le nInlineComponents; + quint32_le offsetToRequiredPropertyExtraData; + quint16_le nRequiredPropertyExtraData; // Function[] // Property[] // Signal[] // Binding[] // InlineComponent[] +// RequiredPropertyExtraData[] - static int calculateSizeExcludingSignalsAndEnums(int nFunctions, int nProperties, int nAliases, int nEnums, int nSignals, int nBindings, int nNamedObjectsInComponent, int nInlineComponents) + static int calculateSizeExcludingSignalsAndEnums(int nFunctions, int nProperties, int nAliases, int nEnums, int nSignals, int nBindings, int nNamedObjectsInComponent, int nInlineComponents, int nRequiredPropertyExtraData) { return ( sizeof(Object) + nFunctions * sizeof(quint32) @@ -782,6 +791,7 @@ struct Object + nBindings * sizeof(Binding) + nNamedObjectsInComponent * sizeof(int) + nInlineComponents * sizeof(InlineComponent) + + nRequiredPropertyExtraData * sizeof(RequiredPropertyExtraData) + 0x7 ) & ~0x7; } @@ -835,6 +845,16 @@ struct Object return reinterpret_cast(reinterpret_cast(this) + offsetToInlineComponents); } + const RequiredPropertyExtraData *requiredPropertyExtraDataAt(int idx) const + { + return requiredPropertyExtraDataTable() + idx; + } + + const RequiredPropertyExtraData *requiredPropertyExtraDataTable() const + { + return reinterpret_cast(reinterpret_cast(this) + offsetToRequiredPropertyExtraData); + } + // --- QQmlPropertyCacheCreator interface int propertyCount() const { return nProperties; } int aliasCount() const { return nAliases; } @@ -863,10 +883,14 @@ struct Object InlineComponentIterator inlineComponentsBegin() const {return InlineComponentIterator(this, 0);} InlineComponentIterator inlineComponentsEnd() const {return InlineComponentIterator(this, nInlineComponents);} + typedef TableIterator RequiredPropertyExtraDataIterator; + RequiredPropertyExtraDataIterator requiredPropertyExtraDataBegin() const {return RequiredPropertyExtraDataIterator(this, 0); }; + RequiredPropertyExtraDataIterator requiredPropertyExtraDataEnd() const {return RequiredPropertyExtraDataIterator(this, nRequiredPropertyExtraData); }; + int namedObjectsInComponentCount() const { return nNamedObjectsInComponent; } // --- }; -static_assert(sizeof(Object) == 76, "Object structure needs to have the expected size to be binary compatible on disk when generated by host compiler and loaded by target"); +static_assert(sizeof(Object) == 84, "Object structure needs to have the expected size to be binary compatible on disk when generated by host compiler and loaded by target"); struct Import { diff --git a/src/qml/compiler/qqmlirbuilder.cpp b/src/qml/compiler/qqmlirbuilder.cpp index 811f88cb73..a3ee431e90 100644 --- a/src/qml/compiler/qqmlirbuilder.cpp +++ b/src/qml/compiler/qqmlirbuilder.cpp @@ -64,6 +64,33 @@ using namespace QQmlJS; return false; \ } +void Object::simplifyRequiredProperties() { + // if a property of the current object was marked as required + // do not store that information in the ExtraData + // but rather mark the property as required + QSet required; + for (auto it = this->requiredPropertyExtraDataBegin(); it != this->requiredPropertyExtraDataEnd(); ++it) + required.insert(it->nameIndex); + if (required.isEmpty()) + return; + for (auto it = this->propertiesBegin(); it != this->propertiesEnd(); ++it) { + auto requiredIt = required.find(it->nameIndex); + if (requiredIt != required.end()) { + it->isRequired = true; + required.erase(requiredIt); + } + } + QmlIR::RequiredPropertyExtraData *prev = nullptr; + auto current = this->requiredPropertyExtraDatas->first; + while (current) { + if (required.contains(current->nameIndex)) + prev = current; + else + requiredPropertyExtraDatas->unlink(prev, current); + current = current->next; + } +} + bool Parameter::init(QV4::Compiler::JSUnitGenerator *stringGenerator, const QString ¶meterName, const QString &typeName) { @@ -162,6 +189,7 @@ void Object::init(QQmlJS::MemoryPool *pool, int typeNameIndex, int idIndex, cons functions = pool->New >(); functionsAndExpressions = pool->New >(); inlineComponents = pool->New>(); + requiredPropertyExtraDatas = pool->New>(); declarationsOverride = nullptr; } @@ -287,6 +315,11 @@ void Object::appendInlineComponent(InlineComponent *ic) inlineComponents->append(ic); } +void Object::appendRequiredPropertyExtraData(RequiredPropertyExtraData *extraData) +{ + requiredPropertyExtraDatas->append(extraData); +} + QString Object::appendBinding(Binding *b, bool isListBinding) { const bool bindingToDefaultProperty = (b->propertyNameIndex == quint32(0)); @@ -452,6 +485,10 @@ bool IRBuilder::generateFromQml(const QString &code, const QString &url, Documen qSwap(_imports, output->imports); qSwap(_pragmas, output->pragmas); qSwap(_objects, output->objects); + + for (auto object: output->objects) + object->simplifyRequiredProperties(); + return errors.isEmpty(); } @@ -988,6 +1025,14 @@ bool IRBuilder::visit(QQmlJS::AST::UiSourceElement *node) return false; } +bool IRBuilder::visit(AST::UiRequired *ast) +{ + auto extraData = New(); + extraData->nameIndex = registerString(ast->name.toString()); + _object->appendRequiredPropertyExtraData(extraData); + return false; +} + QString IRBuilder::asString(QQmlJS::AST::UiQualifiedId *node) { QString s; @@ -1590,7 +1635,7 @@ void QmlUnitGenerator::generate(Document &output, const QV4::CompiledData::Depen uint nextOffset = objectOffset + objectOffsetTableSize; for (Object *o : qAsConst(output.objects)) { objectOffsets.insert(o, nextOffset); - nextOffset += QV4::CompiledData::Object::calculateSizeExcludingSignalsAndEnums(o->functionCount(), o->propertyCount(), o->aliasCount(), o->enumCount(), o->signalCount(), o->bindingCount(), o->namedObjectsInComponent.size(), o->inlineComponentCount()); + nextOffset += QV4::CompiledData::Object::calculateSizeExcludingSignalsAndEnums(o->functionCount(), o->propertyCount(), o->aliasCount(), o->enumCount(), o->signalCount(), o->bindingCount(), o->namedObjectsInComponent.size(), o->inlineComponentCount(), o->requiredPropertyExtraDataCount()); int signalTableSize = 0; for (const Signal *s = o->firstSignal(); s; s = s->next) @@ -1673,6 +1718,10 @@ void QmlUnitGenerator::generate(Document &output, const QV4::CompiledData::Depen objectToWrite->offsetToInlineComponents = nextOffset; nextOffset += objectToWrite->nInlineComponents * sizeof (QV4::CompiledData::InlineComponent); + objectToWrite->nRequiredPropertyExtraData = o->requiredPropertyExtraDataCount(); + objectToWrite->offsetToRequiredPropertyExtraData = nextOffset; + nextOffset += objectToWrite->nRequiredPropertyExtraData * sizeof(QV4::CompiledData::RequiredPropertyExtraData); + quint32_le *functionsTable = reinterpret_cast(objectPtr + objectToWrite->offsetToFunctions); for (const Function *f = o->firstFunction(); f; f = f->next) *functionsTable++ = o->runtimeFunctionIndices.at(f->index); @@ -1752,6 +1801,14 @@ void QmlUnitGenerator::generate(Document &output, const QV4::CompiledData::Depen *icToWrite = *ic; inlineComponentPtr += sizeof(QV4::CompiledData::InlineComponent); } + + char *requiredPropertyExtraDataPtr = objectPtr + objectToWrite->offsetToRequiredPropertyExtraData; + for (auto it = o->requiredPropertyExtraDataBegin(); it != o->requiredPropertyExtraDataEnd(); ++it) { + const RequiredPropertyExtraData *extraData = it.ptr; + QV4::CompiledData::RequiredPropertyExtraData *extraDataToWrite = reinterpret_cast(requiredPropertyExtraDataPtr); + *extraDataToWrite = *extraData; + requiredPropertyExtraDataPtr += sizeof(QV4::CompiledData::RequiredPropertyExtraData); + } } if (!output.javaScriptCompilationUnit.data) { diff --git a/src/qml/compiler/qqmlirbuilder_p.h b/src/qml/compiler/qqmlirbuilder_p.h index d4f2eb8dd4..32921638af 100644 --- a/src/qml/compiler/qqmlirbuilder_p.h +++ b/src/qml/compiler/qqmlirbuilder_p.h @@ -285,6 +285,11 @@ struct Alias : public QV4::CompiledData::Alias Alias *next; }; +struct RequiredPropertyExtraData : public QV4::CompiledData::RequiredPropertyExtraData +{ + RequiredPropertyExtraData *next; +}; + struct Function { QV4::CompiledData::Location location; @@ -341,6 +346,9 @@ public: int functionCount() const { return functions->count; } const InlineComponent *inlineComponent() const { return inlineComponents->first; } int inlineComponentCount() const { return inlineComponents->count; } + const RequiredPropertyExtraData *requiredPropertyExtraData() const {return requiredPropertyExtraDatas->first; } + int requiredPropertyExtraDataCount() const { return requiredPropertyExtraDatas->count; } + void simplifyRequiredProperties(); PoolList::Iterator bindingsBegin() const { return bindings->begin(); } PoolList::Iterator bindingsEnd() const { return bindings->end(); } @@ -356,6 +364,8 @@ public: PoolList::Iterator functionsEnd() const { return functions->end(); } PoolList::Iterator inlineComponentsBegin() const { return inlineComponents->begin(); } PoolList::Iterator inlineComponentsEnd() const { return inlineComponents->end(); } + PoolList::Iterator requiredPropertyExtraDataBegin() const {return requiredPropertyExtraDatas->begin(); } + PoolList::Iterator requiredPropertyExtraDataEnd() const {return requiredPropertyExtraDatas->end(); } // If set, then declarations for this object (and init bindings for these) should go into the // specified object. Used for declarations inside group properties. @@ -369,6 +379,7 @@ public: QString appendAlias(Alias *prop, const QString &aliasName, bool isDefaultProperty, const QQmlJS::AST::SourceLocation &defaultToken, QQmlJS::AST::SourceLocation *errorLocation); void appendFunction(QmlIR::Function *f); void appendInlineComponent(InlineComponent *ic); + void appendRequiredPropertyExtraData(RequiredPropertyExtraData *extraData); QString appendBinding(Binding *b, bool isListBinding); Binding *findBinding(quint32 nameIndex) const; @@ -393,6 +404,7 @@ private: PoolList *bindings; PoolList *functions; PoolList *inlineComponents; + PoolList *requiredPropertyExtraDatas; }; struct Q_QMLCOMPILER_PRIVATE_EXPORT Pragma @@ -469,6 +481,7 @@ public: bool visit(QQmlJS::AST::UiPublicMember *ast) override; bool visit(QQmlJS::AST::UiScriptBinding *ast) override; bool visit(QQmlJS::AST::UiSourceElement *ast) override; + bool visit(QQmlJS::AST::UiRequired *ast) override; void throwRecursionDepthError() override { diff --git a/src/qml/parser/qqmljs.g b/src/qml/parser/qqmljs.g index b0f0f6a809..879de92e5f 100644 --- a/src/qml/parser/qqmljs.g +++ b/src/qml/parser/qqmljs.g @@ -1350,6 +1350,18 @@ OptionalSemicolon: | Semicolon; and then we would miss a semicolon (see tests/auto/quick/qquickvisualdatamodel/data/objectlist.qml)*/ ./ +UiRequired: T_REQUIRED QmlIdentifier Semicolon; +/. + case $rule_number: { + AST::UiRequired *node = new (pool) AST::UiRequired(stringRef(2)); + node->requiredToken = loc(1); + node->semicolonToken = loc(3); + sym(1).Node = node; + } break; +./ + +UiObjectMember: UiRequired; + UiObjectMember: T_REQUIRED UiObjectMemberPropertyNoInitialiser; /. case $rule_number: { @@ -1360,7 +1372,6 @@ UiObjectMember: T_REQUIRED UiObjectMemberPropertyNoInitialiser; } break; ./ - UiObjectMemberWithScriptStatement: T_PROPERTY UiPropertyType QmlIdentifier T_COLON UiScriptStatement OptionalSemicolon; /. case $rule_number: { diff --git a/src/qml/parser/qqmljsast.cpp b/src/qml/parser/qqmljsast.cpp index aa3e8ab5e3..416916c547 100644 --- a/src/qml/parser/qqmljsast.cpp +++ b/src/qml/parser/qqmljsast.cpp @@ -1554,6 +1554,14 @@ void UiInlineComponent::accept0(Visitor *visitor) visitor->endVisit(this); } +void UiRequired::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + } + + visitor->endVisit(this); +} + } } // namespace QQmlJS::AST QT_END_NAMESPACE diff --git a/src/qml/parser/qqmljsast_p.h b/src/qml/parser/qqmljsast_p.h index 48a994cd33..f3369676e9 100644 --- a/src/qml/parser/qqmljsast_p.h +++ b/src/qml/parser/qqmljsast_p.h @@ -258,7 +258,8 @@ public: Kind_UiHeaderItemList, Kind_UiEnumDeclaration, Kind_UiEnumMemberList, - Kind_UiVersionSpecifier + Kind_UiVersionSpecifier, + Kind_UiRequired }; inline Node() {} @@ -3103,6 +3104,28 @@ public: SourceLocation semicolonToken; }; +class QML_PARSER_EXPORT UiRequired: public Node +{ +public: + QQMLJS_DECLARE_AST_NODE(UiRequired) + + UiRequired(QStringRef name) + :name(name) + { kind = K; } + + void accept0(Visitor *visitor) override; + + SourceLocation firstSourceLocation() const override + { return requiredToken; } + + SourceLocation lastSourceLocation() const override + { return semicolonToken; } + + QStringRef name; + SourceLocation requiredToken; + SourceLocation semicolonToken; +}; + class QML_PARSER_EXPORT UiHeaderItemList: public Node { public: diff --git a/src/qml/parser/qqmljsastfwd_p.h b/src/qml/parser/qqmljsastfwd_p.h index 8a8ee2dfae..fe260e2bb5 100644 --- a/src/qml/parser/qqmljsastfwd_p.h +++ b/src/qml/parser/qqmljsastfwd_p.h @@ -183,6 +183,7 @@ class UiHeaderItemList; class UiEnumDeclaration; class UiEnumMemberList; class UiVersionSpecifier; +class UiRequired; } // namespace AST } // namespace QQmlJS diff --git a/src/qml/parser/qqmljsastvisitor_p.h b/src/qml/parser/qqmljsastvisitor_p.h index d6b92990ad..fcc48da1d3 100644 --- a/src/qml/parser/qqmljsastvisitor_p.h +++ b/src/qml/parser/qqmljsastvisitor_p.h @@ -414,6 +414,9 @@ public: virtual bool visit(TypeAnnotation *) { return true; } virtual void endVisit(TypeAnnotation *) {} + virtual bool visit(UiRequired *) { return true; } + virtual void endVisit(UiRequired *) {} + virtual void throwRecursionDepthError() = 0; quint16 recursionDepth() const { return m_recursionDepth; } diff --git a/src/qml/qml/qqmlincubator_p.h b/src/qml/qml/qqmlincubator_p.h index aadb147bd5..a674ff274f 100644 --- a/src/qml/qml/qqmlincubator_p.h +++ b/src/qml/qml/qqmlincubator_p.h @@ -61,9 +61,7 @@ QT_BEGIN_NAMESPACE -class QQmlPropertyData; -struct RequiredPropertyInfo; -using RequiredProperties = QHash; +class RequiredProperties; class QQmlIncubator; class Q_QML_PRIVATE_EXPORT QQmlIncubatorPrivate : public QQmlEnginePrivate::Incubator diff --git a/src/qml/qml/qqmlobjectcreator.cpp b/src/qml/qml/qqmlobjectcreator.cpp index a26cdba53a..e990376fbc 100644 --- a/src/qml/qml/qqmlobjectcreator.cpp +++ b/src/qml/qml/qqmlobjectcreator.cpp @@ -1505,23 +1505,45 @@ bool QQmlObjectCreator::populateInstance(int index, QObject *instance, QObject * if (_compiledObject->flags & QV4::CompiledData::Object::HasDeferredBindings) _ddata->deferData(_compiledObjectIndex, compilationUnit, context); + QSet postHocRequired; + for (auto it = _compiledObject->requiredPropertyExtraDataBegin(); it != _compiledObject->requiredPropertyExtraDataEnd(); ++it) + postHocRequired.insert(stringAt(it->nameIndex)); + bool hadInheritedRequiredProperties = !postHocRequired.empty(); + for (int propertyIndex = 0; propertyIndex != _compiledObject->propertyCount(); ++propertyIndex) { const QV4::CompiledData::Property* property = _compiledObject->propertiesBegin() + propertyIndex; QQmlPropertyData *propertyData = _propertyCache->property(_propertyCache->propertyOffset() + propertyIndex); - if (property->isRequired) { - sharedState->hadRequiredProperties = true; - sharedState->requiredProperties.insert(propertyData, - RequiredPropertyInfo {compilationUnit->stringAt(property->nameIndex), compilationUnit->finalUrl(), property->location, {}}); - } + // only compute stringAt if there's a chance for the lookup to succeed + auto postHocIt = postHocRequired.isEmpty() ? postHocRequired.end() : postHocRequired.find(stringAt(property->nameIndex)); + if (!property->isRequired && postHocRequired.end() == postHocIt) + continue; + if (postHocIt != postHocRequired.end()) + postHocRequired.erase(postHocIt); + sharedState->hadRequiredProperties = true; + sharedState->requiredProperties.insert(propertyData, + RequiredPropertyInfo {compilationUnit->stringAt(property->nameIndex), compilationUnit->finalUrl(), property->location, {}}); + } for (int i = 0; i <= _propertyCache->propertyOffset(); ++i) { QQmlPropertyData *propertyData = _propertyCache->property(i); - if (propertyData && propertyData->isRequired()) { - sharedState->hadRequiredProperties = true; - sharedState->requiredProperties.insert(propertyData, RequiredPropertyInfo {propertyData->name(_qobject), compilationUnit->finalUrl(), _compiledObject->location, {}}); - } + if (!propertyData) + continue; + if (!propertyData->isRequired() && postHocRequired.isEmpty()) + continue; + QString name = propertyData->name(_qobject); + auto postHocIt = postHocRequired.find(name); + if (!propertyData->isRequired() && postHocRequired.end() == postHocIt ) + continue; + + if (postHocIt != postHocRequired.end()) + postHocRequired.erase(postHocIt); + + sharedState->hadRequiredProperties = true; + sharedState->requiredProperties.insert(propertyData, RequiredPropertyInfo {name, compilationUnit->finalUrl(), _compiledObject->location, {}}); } + if (!postHocRequired.isEmpty() && hadInheritedRequiredProperties) + recordError({}, QLatin1String("Property %1 was marked as required but does not exist").arg(*postHocRequired.begin())); if (_compiledObject->nFunctions > 0) setupFunctions(); diff --git a/src/qml/qml/qqmlobjectcreator_p.h b/src/qml/qml/qqmlobjectcreator_p.h index f8ad90be15..bca450addb 100644 --- a/src/qml/qml/qqmlobjectcreator_p.h +++ b/src/qml/qml/qqmlobjectcreator_p.h @@ -86,7 +86,7 @@ struct RequiredPropertyInfo QVector aliasesToRequired; }; -using RequiredProperties = QHash; +class RequiredProperties : public QHash {}; struct QQmlObjectCreatorSharedState : public QSharedData { diff --git a/tests/auto/qml/qmlformat/tst_qmlformat.cpp b/tests/auto/qml/qmlformat/tst_qmlformat.cpp index 3201fa5ace..b10eaf26f1 100644 --- a/tests/auto/qml/qmlformat/tst_qmlformat.cpp +++ b/tests/auto/qml/qmlformat/tst_qmlformat.cpp @@ -113,7 +113,6 @@ void TestQmlformat::initTestCase() m_invalidFiles << "tests/auto/qml/qqmllanguage/data/fuzzed.2.qml"; m_invalidFiles << "tests/auto/qml/qqmllanguage/data/fuzzed.3.qml"; m_invalidFiles << "tests/auto/qml/qqmllanguage/data/requiredProperties.2.qml"; - m_invalidFiles << "tests/auto/qml/qqmllanguage/data/requiredProperties.3.qml"; m_invalidFiles << "tests/auto/qml/qqmllanguage/data/nullishCoalescing_LHS_And.qml"; m_invalidFiles << "tests/auto/qml/qqmllanguage/data/nullishCoalescing_LHS_And.qml"; m_invalidFiles << "tests/auto/qml/qqmllanguage/data/nullishCoalescing_LHS_Or.qml"; diff --git a/tests/auto/qml/qmlmin/tst_qmlmin.cpp b/tests/auto/qml/qmlmin/tst_qmlmin.cpp index e7498a8583..0501a8112a 100644 --- a/tests/auto/qml/qmlmin/tst_qmlmin.cpp +++ b/tests/auto/qml/qmlmin/tst_qmlmin.cpp @@ -132,7 +132,6 @@ void tst_qmlmin::initTestCase() invalidFiles << "tests/auto/qml/qqmllanguage/data/fuzzed.2.qml"; invalidFiles << "tests/auto/qml/qqmllanguage/data/fuzzed.3.qml"; invalidFiles << "tests/auto/qml/qqmllanguage/data/requiredProperties.2.qml"; - invalidFiles << "tests/auto/qml/qqmllanguage/data/requiredProperties.3.qml"; // generatorFunction.qml is not invalid per se, but the minifier cannot handle yield statements invalidFiles << "tests/auto/qml/qqmlecmascript/data/generatorFunction.qml"; #endif diff --git a/tests/auto/qml/qqmllanguage/data/NonRequiredBase.qml b/tests/auto/qml/qqmllanguage/data/NonRequiredBase.qml new file mode 100644 index 0000000000..60d45c2b1e --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/NonRequiredBase.qml @@ -0,0 +1,5 @@ +import QtQuick 2.15 + +Item { + property int i +} diff --git a/tests/auto/qml/qqmllanguage/data/RequiredBase.qml b/tests/auto/qml/qqmllanguage/data/RequiredBase.qml new file mode 100644 index 0000000000..4effdbf1c7 --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/RequiredBase.qml @@ -0,0 +1,3 @@ +NonRequiredBase { + required i +} diff --git a/tests/auto/qml/qqmllanguage/data/requiredProperties.3.qml b/tests/auto/qml/qqmllanguage/data/requiredProperties.3.qml new file mode 100644 index 0000000000..2585cf361e --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/requiredProperties.3.qml @@ -0,0 +1,6 @@ +import QtQuick 2.15 + +Item { + property int i; + required i; +} diff --git a/tests/auto/qml/qqmllanguage/data/requiredProperties.4.qml b/tests/auto/qml/qqmllanguage/data/requiredProperties.4.qml new file mode 100644 index 0000000000..1126f845c9 --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/requiredProperties.4.qml @@ -0,0 +1,5 @@ +import QtQuick 2.15 + +Item { + required objectName +} diff --git a/tests/auto/qml/qqmllanguage/data/requiredProperties.5.qml b/tests/auto/qml/qqmllanguage/data/requiredProperties.5.qml new file mode 100644 index 0000000000..c2d155b123 --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/requiredProperties.5.qml @@ -0,0 +1 @@ +RequiredBase {} diff --git a/tests/auto/qml/qqmllanguage/data/requiredProperties.6.qml b/tests/auto/qml/qqmllanguage/data/requiredProperties.6.qml new file mode 100644 index 0000000000..e8802aef20 --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/requiredProperties.6.qml @@ -0,0 +1,3 @@ +RequiredBase { + i: 42 +} diff --git a/tests/auto/qml/qqmllanguage/data/requiredProperties.7.qml b/tests/auto/qml/qqmllanguage/data/requiredProperties.7.qml new file mode 100644 index 0000000000..40987f5c56 --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/requiredProperties.7.qml @@ -0,0 +1,5 @@ +import QtQuick 2.15 + +Item { + required blub +} diff --git a/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp b/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp index 5027c0825f..b4ad9bd7b3 100644 --- a/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp +++ b/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp @@ -1675,6 +1675,36 @@ void tst_qqmllanguage::requiredProperty() QQmlComponent component(&engine, testFileUrl("requiredProperties.2.qml")); QVERIFY(!component.errors().empty()); } + { + QQmlComponent component(&engine, testFileUrl("requiredProperties.4.qml")); + QScopedPointer object(component.create()); + QVERIFY(!component.errors().empty()); + QVERIFY(component.errorString().contains("Required property objectName was not initialized")); + } + { + QQmlComponent component(&engine, testFileUrl("requiredProperties.3.qml")); + QScopedPointer object(component.create()); + QVERIFY(!component.errors().empty()); + QVERIFY(component.errorString().contains("Required property i was not initialized")); + } + { + QQmlComponent component(&engine, testFileUrl("requiredProperties.5.qml")); + QScopedPointer object(component.create()); + QVERIFY(!component.errors().empty()); + QVERIFY(component.errorString().contains("Required property i was not initialized")); + } + { + QQmlComponent component(&engine, testFileUrl("requiredProperties.6.qml")); + VERIFY_ERRORS(0); + QScopedPointer object(component.create()); + QVERIFY(object); + } + { + QQmlComponent component(&engine, testFileUrl("requiredProperties.7.qml")); + QScopedPointer object(component.create()); + QVERIFY(!component.errors().empty()); + QVERIFY(component.errorString().contains("Property blub was marked as required but does not exist")); + } } class MyClassWithRequiredProperty : public QObject -- cgit v1.2.3