diff options
93 files changed, 1286 insertions, 941 deletions
diff --git a/examples/qml/referenceexamples/adding/main.cpp b/examples/qml/referenceexamples/adding/main.cpp index e312149da1..87a7b75764 100644 --- a/examples/qml/referenceexamples/adding/main.cpp +++ b/examples/qml/referenceexamples/adding/main.cpp @@ -70,5 +70,5 @@ int main(int argc, char ** argv) qWarning() << component.errors(); } - return 0; + return EXIT_SUCCESS; } diff --git a/examples/qml/referenceexamples/attached/main.cpp b/examples/qml/referenceexamples/attached/main.cpp index 581b033dfc..eb70625bea 100644 --- a/examples/qml/referenceexamples/attached/main.cpp +++ b/examples/qml/referenceexamples/attached/main.cpp @@ -58,10 +58,10 @@ int main(int argc, char ** argv) { QCoreApplication app(argc, argv); - qmlRegisterType<BirthdayPartyAttached>(); + qmlRegisterAnonymousType<BirthdayPartyAttached>("People", 1); qmlRegisterType<BirthdayParty>("People", 1,0, "BirthdayParty"); - qmlRegisterType<ShoeDescription>(); - qmlRegisterType<Person>(); + qmlRegisterAnonymousType<ShoeDescription>("People", 1); + qmlRegisterAnonymousType<Person>("People", 1); qmlRegisterType<Boy>("People", 1,0, "Boy"); qmlRegisterType<Girl>("People", 1,0, "Girl"); @@ -93,9 +93,9 @@ int main(int argc, char ** argv) qWarning() << " " << guest->name() << "RSVP date:" << qPrintable(rsvpDate.toString()); } - } else { - qWarning() << component.errors(); + return EXIT_SUCCESS; } - return 0; + qWarning() << component.errors(); + return EXIT_FAILURE; } diff --git a/examples/qml/referenceexamples/binding/main.cpp b/examples/qml/referenceexamples/binding/main.cpp index 99187eba3e..eca524dfe2 100644 --- a/examples/qml/referenceexamples/binding/main.cpp +++ b/examples/qml/referenceexamples/binding/main.cpp @@ -58,11 +58,11 @@ int main(int argc, char ** argv) { QCoreApplication app(argc, argv); - qmlRegisterType<BirthdayPartyAttached>(); + qmlRegisterAnonymousType<BirthdayPartyAttached>("People", 1); qmlRegisterType<BirthdayParty>("People", 1,0, "BirthdayParty"); qmlRegisterType<HappyBirthdaySong>("People", 1,0, "HappyBirthdaySong"); - qmlRegisterType<ShoeDescription>(); - qmlRegisterType<Person>(); + qmlRegisterAnonymousType<ShoeDescription>("People", 1); + qmlRegisterAnonymousType<Person>("People", 1); qmlRegisterType<Boy>("People", 1,0, "Boy"); qmlRegisterType<Girl>("People", 1,0, "Girl"); @@ -94,9 +94,9 @@ int main(int argc, char ** argv) } party->startParty(); - } else { - qWarning() << component.errors(); + return QCoreApplication::exec(); } - return app.exec(); + qWarning() << component.errors(); + return EXIT_FAILURE; } diff --git a/examples/qml/referenceexamples/coercion/main.cpp b/examples/qml/referenceexamples/coercion/main.cpp index 262cdf6320..39064f8b89 100644 --- a/examples/qml/referenceexamples/coercion/main.cpp +++ b/examples/qml/referenceexamples/coercion/main.cpp @@ -60,7 +60,7 @@ int main(int argc, char ** argv) qmlRegisterType<BirthdayParty>("People", 1,0, "BirthdayParty"); //![0] - qmlRegisterType<Person>(); + qmlRegisterAnonymousType<Person>("People", 1); //![0] //![register boy girl] @@ -82,9 +82,10 @@ int main(int argc, char ** argv) for (int ii = 0; ii < party->guestCount(); ++ii) qWarning() << " " << party->guest(ii)->name(); - } else { - qWarning() << component.errors(); + + return EXIT_SUCCESS; } - return 0; + qWarning() << component.errors(); + return EXIT_FAILURE; } diff --git a/examples/qml/referenceexamples/default/main.cpp b/examples/qml/referenceexamples/default/main.cpp index 017d6495cd..f6ca77e82d 100644 --- a/examples/qml/referenceexamples/default/main.cpp +++ b/examples/qml/referenceexamples/default/main.cpp @@ -59,7 +59,7 @@ int main(int argc, char ** argv) QCoreApplication app(argc, argv); qmlRegisterType<BirthdayParty>("People", 1,0, "BirthdayParty"); - qmlRegisterType<Person>(); + qmlRegisterAnonymousType<Person>("People", 1); qmlRegisterType<Boy>("People", 1,0, "Boy"); qmlRegisterType<Girl>("People", 1,0, "Girl"); @@ -77,9 +77,10 @@ int main(int argc, char ** argv) for (int ii = 0; ii < party->guestCount(); ++ii) qWarning() << " " << party->guest(ii)->name(); - } else { - qWarning() << component.errors(); + + return EXIT_SUCCESS; } - return 0; + qWarning() << component.errors(); + return EXIT_FAILURE; } diff --git a/examples/qml/referenceexamples/extended/main.cpp b/examples/qml/referenceexamples/extended/main.cpp index f91cec76b1..5f8582d08f 100644 --- a/examples/qml/referenceexamples/extended/main.cpp +++ b/examples/qml/referenceexamples/extended/main.cpp @@ -70,9 +70,9 @@ int main(int argc, char ** argv) if (edit) { edit->show(); - return app.exec(); - } else { - qWarning() << component.errors(); - return 0; + return QApplication::exec(); } + + qWarning() << component.errors(); + return EXIT_FAILURE; } diff --git a/examples/qml/referenceexamples/grouped/main.cpp b/examples/qml/referenceexamples/grouped/main.cpp index 14cd64fe68..4f4b828cef 100644 --- a/examples/qml/referenceexamples/grouped/main.cpp +++ b/examples/qml/referenceexamples/grouped/main.cpp @@ -59,8 +59,8 @@ int main(int argc, char ** argv) QCoreApplication app(argc, argv); qmlRegisterType<BirthdayParty>("People", 1,0, "BirthdayParty"); - qmlRegisterType<ShoeDescription>(); - qmlRegisterType<Person>(); + qmlRegisterAnonymousType<ShoeDescription>("People", 1); + qmlRegisterAnonymousType<Person>("People", 1); qmlRegisterType<Boy>("People", 1,0, "Boy"); qmlRegisterType<Girl>("People", 1,0, "Girl"); @@ -87,9 +87,9 @@ int main(int argc, char ** argv) if (bestShoe) qWarning() << bestShoe->name() << "is wearing the best shoes!"; - } else { - qWarning() << component.errors(); + return EXIT_SUCCESS; } - return 0; + qWarning() << component.errors(); + return EXIT_FAILURE; } diff --git a/examples/qml/referenceexamples/methods/main.cpp b/examples/qml/referenceexamples/methods/main.cpp index 89404ec822..e2a1a28c8b 100644 --- a/examples/qml/referenceexamples/methods/main.cpp +++ b/examples/qml/referenceexamples/methods/main.cpp @@ -70,9 +70,9 @@ int main(int argc, char ** argv) qWarning() << "They are inviting:"; for (int ii = 0; ii < party->guestCount(); ++ii) qWarning() << " " << party->guest(ii)->name(); - } else { - qWarning() << component.errors(); + return EXIT_SUCCESS; } - return 0; + qWarning() << component.errors(); + return EXIT_FAILURE; } diff --git a/examples/qml/referenceexamples/properties/main.cpp b/examples/qml/referenceexamples/properties/main.cpp index a0a2335034..60b56bd247 100644 --- a/examples/qml/referenceexamples/properties/main.cpp +++ b/examples/qml/referenceexamples/properties/main.cpp @@ -72,9 +72,9 @@ int main(int argc, char ** argv) qWarning() << "They are inviting:"; for (int ii = 0; ii < party->guestCount(); ++ii) qWarning() << " " << party->guest(ii)->name(); - } else { - qWarning() << component.errors(); + return EXIT_SUCCESS; } - return 0; + qWarning() << component.errors(); + return EXIT_FAILURE; } diff --git a/examples/qml/referenceexamples/signal/main.cpp b/examples/qml/referenceexamples/signal/main.cpp index bb75e02bc2..f55b4cb419 100644 --- a/examples/qml/referenceexamples/signal/main.cpp +++ b/examples/qml/referenceexamples/signal/main.cpp @@ -58,10 +58,10 @@ int main(int argc, char ** argv) { QCoreApplication app(argc, argv); - qmlRegisterType<BirthdayPartyAttached>(); + qmlRegisterAnonymousType<BirthdayPartyAttached>("People", 1); qmlRegisterType<BirthdayParty>("People", 1,0, "BirthdayParty"); - qmlRegisterType<ShoeDescription>(); - qmlRegisterType<Person>(); + qmlRegisterAnonymousType<ShoeDescription>("People", 1); + qmlRegisterAnonymousType<Person>("People", 1); qmlRegisterType<Boy>("People", 1,0, "Boy"); qmlRegisterType<Girl>("People", 1,0, "Girl"); @@ -93,9 +93,9 @@ int main(int argc, char ** argv) } party->startParty(); - } else { - qWarning() << component.errors(); + return EXIT_SUCCESS; } - return 0; + qWarning() << component.errors(); + return EXIT_FAILURE; } diff --git a/examples/qml/referenceexamples/valuesource/main.cpp b/examples/qml/referenceexamples/valuesource/main.cpp index 4bef695fe2..ab50f00696 100644 --- a/examples/qml/referenceexamples/valuesource/main.cpp +++ b/examples/qml/referenceexamples/valuesource/main.cpp @@ -59,11 +59,11 @@ int main(int argc, char ** argv) { QCoreApplication app(argc, argv); - qmlRegisterType<BirthdayPartyAttached>(); + qmlRegisterAnonymousType<BirthdayPartyAttached>("People", 1); qmlRegisterType<BirthdayParty>("People", 1,0, "BirthdayParty"); qmlRegisterType<HappyBirthdaySong>("People", 1,0, "HappyBirthdaySong"); - qmlRegisterType<ShoeDescription>(); - qmlRegisterType<Person>(); + qmlRegisterAnonymousType<ShoeDescription>("People", 1); + qmlRegisterAnonymousType<Person>("People", 1); qmlRegisterType<Boy>("People", 1,0, "Boy"); qmlRegisterType<Girl>("People", 1,0, "Girl"); @@ -95,9 +95,9 @@ int main(int argc, char ** argv) } party->startParty(); - } else { - qWarning() << component.errors(); + return QCoreApplication::exec(); } - return app.exec(); + qWarning() << component.errors(); + return EXIT_FAILURE; } diff --git a/src/plugins/qmltooling/qmldbg_debugger/qqmlenginedebugservice.cpp b/src/plugins/qmltooling/qmldbg_debugger/qqmlenginedebugservice.cpp index d0f9833c2e..be83f63bfc 100644 --- a/src/plugins/qmltooling/qmldbg_debugger/qqmlenginedebugservice.cpp +++ b/src/plugins/qmltooling/qmldbg_debugger/qqmlenginedebugservice.cpp @@ -191,22 +191,18 @@ QQmlEngineDebugServiceImpl::propertyData(QObject *obj, int propIdx) if (binding) rv.binding = binding->expression(); - if (QQmlValueTypeFactory::isValueType(prop.userType())) { - rv.type = QQmlObjectProperty::Basic; - } else if (QQmlMetaType::isQObject(prop.userType())) { + rv.value = valueContents(prop.read(obj)); + + if (QQmlMetaType::isQObject(prop.userType())) { rv.type = QQmlObjectProperty::Object; } else if (QQmlMetaType::isList(prop.userType())) { rv.type = QQmlObjectProperty::List; } else if (prop.userType() == QMetaType::QVariant) { rv.type = QQmlObjectProperty::Variant; + } else if (rv.value.isValid()) { + rv.type = QQmlObjectProperty::Basic; } - QVariant value; - if (rv.type != QQmlObjectProperty::Unknown && prop.userType() != 0) { - value = prop.read(obj); - } - rv.value = valueContents(value); - return rv; } @@ -269,10 +265,10 @@ QVariant QQmlEngineDebugServiceImpl::valueContents(QVariant value) const return s; } } - - if (isSaveable(value)) - return value; } + + if (isSaveable(value)) + return value; } if (QQmlMetaType::isQObject(userType)) { diff --git a/src/plugins/qmltooling/qmldbg_debugger/qqmlwatcher.cpp b/src/plugins/qmltooling/qmldbg_debugger/qqmlwatcher.cpp index 86571e6cbe..8caa5ac23e 100644 --- a/src/plugins/qmltooling/qmldbg_debugger/qqmlwatcher.cpp +++ b/src/plugins/qmltooling/qmldbg_debugger/qqmlwatcher.cpp @@ -109,12 +109,7 @@ QQmlWatchProxy::QQmlWatchProxy(int id, void QQmlWatchProxy::notifyValueChanged() { - QVariant v; - if (m_expr) - v = m_expr->evaluate(); - else if (QQmlValueTypeFactory::isValueType(m_property.userType())) - v = m_property.read(m_object); - + const QVariant v = m_expr ? m_expr->evaluate() : m_property.read(m_object); emit m_watch->propertyChanged(m_id, m_debugId, m_property, v); } diff --git a/src/qml/common/qv4compileddata_p.h b/src/qml/common/qv4compileddata_p.h index c3ddce5884..11de506a53 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 0x24 // Collect function parameter types +#define QV4_DATA_STRUCTURE_VERSION 0x26// support required properties class QIODevice; class QQmlTypeNameCache; @@ -656,7 +656,8 @@ struct Property { quint32_le nameIndex; union { - quint32_le_bitfield<0, 29> builtinTypeOrTypeNameIndex; + quint32_le_bitfield<0, 28> builtinTypeOrTypeNameIndex; + quint32_le_bitfield<28, 1> isRequired; quint32_le_bitfield<29, 1> isBuiltinType; quint32_le_bitfield<30, 1> isList; quint32_le_bitfield<31, 1> isReadOnly; diff --git a/src/qml/compiler/qqmlirbuilder.cpp b/src/qml/compiler/qqmlirbuilder.cpp index 171dc641d3..fbb6a07b1b 100644 --- a/src/qml/compiler/qqmlirbuilder.cpp +++ b/src/qml/compiler/qqmlirbuilder.cpp @@ -792,7 +792,7 @@ bool IRBuilder::visit(QQmlJS::AST::UiPublicMember *node) { if (node->type == QQmlJS::AST::UiPublicMember::Signal) { Signal *signal = New<Signal>(); - QString signalName = node->name.toString(); + const QString signalName = node->name.toString(); signal->nameIndex = registerString(signalName); QQmlJS::AST::SourceLocation loc = node->typeToken; @@ -821,8 +821,14 @@ bool IRBuilder::visit(QQmlJS::AST::UiPublicMember *node) p = p->next; } - if (signalName.at(0).isUpper()) - COMPILE_EXCEPTION(node->identifierToken, tr("Signal names cannot begin with an upper case letter")); + for (const QChar &ch : signalName) { + if (ch.isLower()) + break; + if (ch.isUpper()) { + COMPILE_EXCEPTION(node->identifierToken, + tr("Signal names cannot begin with an upper case letter")); + } + } if (illegalNames.contains(signalName)) COMPILE_EXCEPTION(node->identifierToken, tr("Illegal signal name")); @@ -841,6 +847,7 @@ bool IRBuilder::visit(QQmlJS::AST::UiPublicMember *node) Property *property = New<Property>(); property->isReadOnly = node->isReadonlyMember; + property->isRequired = node->isRequired; QV4::CompiledData::BuiltinType builtinPropertyType = Parameter::stringToBuiltinType(memberType); bool typeFound = builtinPropertyType != QV4::CompiledData::BuiltinType::InvalidBuiltin; diff --git a/src/qml/jsruntime/qv4qobjectwrapper.cpp b/src/qml/jsruntime/qv4qobjectwrapper.cpp index c36da3815d..2b5e8bd2b9 100644 --- a/src/qml/jsruntime/qv4qobjectwrapper.cpp +++ b/src/qml/jsruntime/qv4qobjectwrapper.cpp @@ -2077,7 +2077,7 @@ ReturnedValue QObjectMethod::callInternal(const Value *thisObject, const Value * if (!d()->valueTypeWrapper) return Encode::undefined(); - object = QQmlObjectOrGadget(d()->propertyCache(), d()->valueTypeWrapper->gadgetPtr); + object = QQmlObjectOrGadget(d()->propertyCache(), d()->valueTypeWrapper->gadgetPtr()); } QQmlPropertyData method; diff --git a/src/qml/parser/qqmljs.g b/src/qml/parser/qqmljs.g index 8ac7633ae0..cc560c5912 100644 --- a/src/qml/parser/qqmljs.g +++ b/src/qml/parser/qqmljs.g @@ -49,6 +49,7 @@ %token T_DIVIDE_EQ "/=" T_DO "do" T_DOT "." %token T_ELSE "else" T_EQ "=" T_EQ_EQ "==" %token T_EQ_EQ_EQ "===" T_FINALLY "finally" T_FOR "for" +%token T_FUNCTION_STAR "function *" %token T_FUNCTION "function" T_GE ">=" T_GT ">" %token T_GT_GT ">>" T_GT_GT_EQ ">>=" T_GT_GT_GT ">>>" %token T_GT_GT_GT_EQ ">>>=" T_IDENTIFIER "identifier" T_IF "if" @@ -88,6 +89,7 @@ %token T_STATIC "static" %token T_EXPORT "export" %token T_FROM "from" +%token T_REQUIRED "required" --- template strings %token T_NO_SUBSTITUTION_TEMPLATE"(no subst template)" @@ -120,8 +122,9 @@ %token T_FOR_LOOKAHEAD_OK "(for lookahead ok)" --%left T_PLUS T_MINUS -%nonassoc T_IDENTIFIER T_COLON T_SIGNAL T_PROPERTY T_READONLY T_ON T_SET T_GET T_OF T_STATIC T_FROM T_AS +%nonassoc T_IDENTIFIER T_COLON T_SIGNAL T_PROPERTY T_READONLY T_ON T_SET T_GET T_OF T_STATIC T_FROM T_AS T_REQUIRED %nonassoc REDUCE_HERE +%right T_THEN T_ELSE %start TopLevel @@ -1175,7 +1178,7 @@ UiObjectMember: T_SIGNAL T_IDENTIFIER Semicolon; } break; ./ -UiObjectMember: T_PROPERTY T_IDENTIFIER T_LT UiPropertyType T_GT QmlIdentifier Semicolon; +UiObjectMemberListPropertyNoInitialiser: T_PROPERTY T_IDENTIFIER T_LT UiPropertyType T_GT QmlIdentifier Semicolon; /. case $rule_number: { AST::UiPublicMember *node = new (pool) AST::UiPublicMember(sym(4).UiQualifiedId->finish(), stringRef(6)); @@ -1189,23 +1192,19 @@ UiObjectMember: T_PROPERTY T_IDENTIFIER T_LT UiPropertyType T_GT QmlIdentifier S } break; ./ -UiObjectMember: T_READONLY T_PROPERTY T_IDENTIFIER T_LT UiPropertyType T_GT QmlIdentifier Semicolon; +UiObjectMember: UiObjectMemberListPropertyNoInitialiser; + +UiObjectMember: T_READONLY UiObjectMemberListPropertyNoInitialiser; /. case $rule_number: { - AST::UiPublicMember *node = new (pool) AST::UiPublicMember(sym(5).UiQualifiedId->finish(), stringRef(7)); + AST::UiPublicMember *node = sym(2).UiPublicMember; node->isReadonlyMember = true; node->readonlyToken = loc(1); - node->typeModifier = stringRef(3); - node->propertyToken = loc(2); - node->typeModifierToken = loc(3); - node->typeToken = loc(5); - node->identifierToken = loc(7); - node->semicolonToken = loc(8); sym(1).Node = node; } break; ./ -UiObjectMember: T_PROPERTY UiPropertyType QmlIdentifier Semicolon; +UiObjectMemberPropertyNoInitialiser: T_PROPERTY UiPropertyType QmlIdentifier Semicolon; /. case $rule_number: { AST::UiPublicMember *node = new (pool) AST::UiPublicMember(sym(2).UiQualifiedId->finish(), stringRef(3)); @@ -1217,42 +1216,47 @@ UiObjectMember: T_PROPERTY UiPropertyType QmlIdentifier Semicolon; } break; ./ -UiObjectMember: T_DEFAULT T_PROPERTY UiPropertyType QmlIdentifier Semicolon; + +UiObjectMember: UiObjectMemberPropertyNoInitialiser; + +UiObjectMember: T_DEFAULT UiObjectMemberPropertyNoInitialiser; /. case $rule_number: { - AST::UiPublicMember *node = new (pool) AST::UiPublicMember(sym(3).UiQualifiedId->finish(), stringRef(4)); + AST::UiPublicMember *node = sym(2).UiPublicMember; node->isDefaultMember = true; node->defaultToken = loc(1); - node->propertyToken = loc(2); - node->typeToken = loc(3); - node->identifierToken = loc(4); - node->semicolonToken = loc(5); sym(1).Node = node; } break; ./ -UiObjectMember: T_DEFAULT T_PROPERTY T_IDENTIFIER T_LT UiPropertyType T_GT QmlIdentifier Semicolon; +UiObjectMember: T_DEFAULT UiObjectMemberListPropertyNoInitialiser; /. case $rule_number: { - AST::UiPublicMember *node = new (pool) AST::UiPublicMember(sym(5).UiQualifiedId->finish(), stringRef(7)); + AST::UiPublicMember *node = sym(2).UiPublicMember; node->isDefaultMember = true; node->defaultToken = loc(1); - node->typeModifier = stringRef(3); - node->propertyToken = loc(2); - node->typeModifierToken = loc(2); - node->typeToken = loc(4); - node->identifierToken = loc(7); - node->semicolonToken = loc(8); sym(1).Node = node; } break; ./ + OptionalSemicolon: | Semicolon; /. /* we need OptionalSemicolon because UiScriptStatement might already parse the last semicolon and then we would miss a semicolon (see tests/auto/quick/qquickvisualdatamodel/data/objectlist.qml)*/ ./ -UiObjectMember: T_PROPERTY UiPropertyType QmlIdentifier T_COLON UiScriptStatement OptionalSemicolon; +UiObjectMember: T_REQUIRED UiObjectMemberPropertyNoInitialiser; +/. + case $rule_number: { + AST::UiPublicMember *node = sym(2).UiPublicMember; + node->requiredToken = loc(1); + node->isRequired = true; + sym(1).Node = node; + } break; +./ + + +UiObjectMemberWithScriptStatement: T_PROPERTY UiPropertyType QmlIdentifier T_COLON UiScriptStatement OptionalSemicolon; /. case $rule_number: { AST::UiPublicMember *node = new (pool) AST::UiPublicMember(sym(2).UiQualifiedId->finish(), stringRef(3), sym(5).Statement); @@ -1264,35 +1268,29 @@ UiObjectMember: T_PROPERTY UiPropertyType QmlIdentifier T_COLON UiScriptStatemen } break; ./ -UiObjectMember: T_READONLY T_PROPERTY UiPropertyType QmlIdentifier T_COLON UiScriptStatement OptionalSemicolon; +UiObjectMember: UiObjectMemberWithScriptStatement; + +UiObjectMember: T_READONLY UiObjectMemberWithScriptStatement; /. case $rule_number: { - AST::UiPublicMember *node = new (pool) AST::UiPublicMember(sym(3).UiQualifiedId->finish(), stringRef(4), sym(6).Statement); + AST::UiPublicMember *node = sym(2).UiPublicMember; node->isReadonlyMember = true; node->readonlyToken = loc(1); - node->propertyToken = loc(2); - node->typeToken = loc(3); - node->identifierToken = loc(4); - node->colonToken = loc(5); sym(1).Node = node; } break; ./ -UiObjectMember: T_DEFAULT T_PROPERTY UiPropertyType QmlIdentifier T_COLON UiScriptStatement OptionalSemicolon; +UiObjectMember: T_DEFAULT UiObjectMemberWithScriptStatement; /. case $rule_number: { - AST::UiPublicMember *node = new (pool) AST::UiPublicMember(sym(3).UiQualifiedId->finish(), stringRef(4), sym(6).Statement); + AST::UiPublicMember *node = sym(2).UiPublicMember; node->isDefaultMember = true; node->defaultToken = loc(1); - node->propertyToken = loc(2); - node->typeToken = loc(3); - node->identifierToken = loc(4); - node->colonToken = loc(5); sym(1).Node = node; } break; ./ -UiObjectMember: T_PROPERTY T_IDENTIFIER T_LT UiPropertyType T_GT QmlIdentifier T_COLON T_LBRACKET UiArrayMemberList T_RBRACKET Semicolon; +UiObjectMemberWithArray: T_PROPERTY T_IDENTIFIER T_LT UiPropertyType T_GT QmlIdentifier T_COLON T_LBRACKET UiArrayMemberList T_RBRACKET Semicolon; /. case $rule_number: { AST::UiPublicMember *node = new (pool) AST::UiPublicMember(sym(4).UiQualifiedId->finish(), stringRef(6)); @@ -1318,35 +1316,19 @@ UiObjectMember: T_PROPERTY T_IDENTIFIER T_LT UiPropertyType T_GT QmlIdentifier T } break; ./ -UiObjectMember: T_READONLY T_PROPERTY T_IDENTIFIER T_LT UiPropertyType T_GT QmlIdentifier T_COLON T_LBRACKET UiArrayMemberList T_RBRACKET Semicolon; +UiObjectMember: UiObjectMemberWithArray; + +UiObjectMember: T_READONLY UiObjectMemberWithArray; /. case $rule_number: { - AST::UiPublicMember *node = new (pool) AST::UiPublicMember(sym(5).UiQualifiedId->finish(), stringRef(7)); + AST::UiPublicMember *node = sym(2).UiPublicMember; node->isReadonlyMember = true; node->readonlyToken = loc(1); - node->typeModifier = stringRef(3); - node->propertyToken = loc(2); - node->typeModifierToken = loc(3); - node->typeToken = loc(5); - node->identifierToken = loc(7); - node->semicolonToken = loc(8); // insert a fake ';' before ':' - - AST::UiQualifiedId *propertyName = new (pool) AST::UiQualifiedId(stringRef(7)); - propertyName->identifierToken = loc(7); - propertyName->next = 0; - - AST::UiArrayBinding *binding = new (pool) AST::UiArrayBinding(propertyName, sym(10).UiArrayMemberList->finish()); - binding->colonToken = loc(8); - binding->lbracketToken = loc(9); - binding->rbracketToken = loc(11); - - node->binding = binding; - sym(1).Node = node; } break; ./ -UiObjectMember: T_PROPERTY UiPropertyType QmlIdentifier T_COLON ExpressionStatementLookahead UiQualifiedId UiObjectInitializer Semicolon; +UiObjectMemberExpressionStatementLookahead: T_PROPERTY UiPropertyType QmlIdentifier T_COLON ExpressionStatementLookahead UiQualifiedId UiObjectInitializer Semicolon; /. case $rule_number: { AST::UiPublicMember *node = new (pool) AST::UiPublicMember(sym(2).UiQualifiedId->finish(), stringRef(3)); @@ -1369,43 +1351,30 @@ UiObjectMember: T_PROPERTY UiPropertyType QmlIdentifier T_COLON ExpressionStatem } break; ./ -UiObjectMember: T_READONLY T_PROPERTY UiPropertyType QmlIdentifier T_COLON ExpressionStatementLookahead UiQualifiedId UiObjectInitializer Semicolon; +UiObjectMember: UiObjectMemberExpressionStatementLookahead; + +UiObjectMember: T_READONLY UiObjectMemberExpressionStatementLookahead; /. case $rule_number: { - AST::UiPublicMember *node = new (pool) AST::UiPublicMember(sym(3).UiQualifiedId->finish(), stringRef(4)); + AST::UiPublicMember *node = sym(2).UiPublicMember; node->isReadonlyMember = true; node->readonlyToken = loc(1); - node->propertyToken = loc(2); - node->typeToken = loc(3); - node->identifierToken = loc(4); - node->semicolonToken = loc(5); // insert a fake ';' before ':' - - AST::UiQualifiedId *propertyName = new (pool) AST::UiQualifiedId(stringRef(4)); - propertyName->identifierToken = loc(4); - propertyName->next = 0; - - AST::UiObjectBinding *binding = new (pool) AST::UiObjectBinding( - propertyName, sym(7).UiQualifiedId, sym(8).UiObjectInitializer); - binding->colonToken = loc(5); - - node->binding = binding; - sym(1).Node = node; } break; ./ -UiObjectMember: FunctionDeclarationWithTypes; +UiObjectMember: GeneratorDeclaration; /. case $rule_number: { - sym(1).Node = new (pool) AST::UiSourceElement(sym(1).Node); + auto node = new (pool) AST::UiSourceElement(sym(1).Node); + sym(1).Node = node; } break; ./ -UiObjectMember: GeneratorExpression; +UiObjectMember: FunctionDeclarationWithTypes; /. case $rule_number: { - auto node = new (pool) AST::UiSourceElement(sym(1).Node); - sym(1).Node = node; + sym(1).Node = new (pool) AST::UiSourceElement(sym(1).Node); } break; ./ @@ -1501,6 +1470,7 @@ QmlIdentifier: T_GET; QmlIdentifier: T_SET; QmlIdentifier: T_FROM; QmlIdentifier: T_OF; +QmlIdentifier: T_REQUIRED; JsIdentifier: T_IDENTIFIER; JsIdentifier: T_PROPERTY; @@ -1513,6 +1483,7 @@ JsIdentifier: T_FROM; JsIdentifier: T_STATIC; JsIdentifier: T_OF; JsIdentifier: T_AS; +JsIdentifier: T_REQUIRED; IdentifierReference: JsIdentifier; BindingIdentifier: IdentifierReference; @@ -3232,7 +3203,7 @@ ExpressionStatementLookahead: ; int token = lookaheadToken(lexer); if (token == T_LBRACE) pushToken(T_FORCE_BLOCK); - else if (token == T_FUNCTION || token == T_CLASS || token == T_LET || token == T_CONST) + else if (token == T_FUNCTION || token == T_FUNCTION_STAR || token == T_CLASS || token == T_LET || token == T_CONST) pushToken(T_FORCE_DECLARATION); } break; ./ @@ -3980,27 +3951,14 @@ GeneratorRBrace: T_RBRACE; } break; ./ -GeneratorDeclaration: Function T_STAR BindingIdentifier GeneratorLParen FormalParameters T_RPAREN FunctionLBrace GeneratorBody GeneratorRBrace; -/. - case $rule_number: { - AST::FunctionDeclaration *node = new (pool) AST::FunctionDeclaration(stringRef(3), sym(5).FormalParameterList, sym(8).StatementList); - node->functionToken = loc(1); - node->identifierToken = loc(3); - node->lparenToken = loc(4); - node->rparenToken = loc(6); - node->lbraceToken = loc(7); - node->rbraceToken = loc(9); - node->isGenerator = true; - sym(1).Node = node; - } break; -./ +FunctionStar: T_FUNCTION_STAR %prec REDUCE_HERE; -GeneratorDeclaration_Default: GeneratorDeclaration; -GeneratorDeclaration_Default: Function T_STAR GeneratorLParen FormalParameters T_RPAREN FunctionLBrace GeneratorBody GeneratorRBrace; +GeneratorDeclaration: FunctionStar BindingIdentifier GeneratorLParen FormalParameters T_RPAREN FunctionLBrace GeneratorBody GeneratorRBrace; /. case $rule_number: { - AST::FunctionDeclaration *node = new (pool) AST::FunctionDeclaration(QStringRef(), sym(4).FormalParameterList, sym(7).StatementList); + AST::FunctionDeclaration *node = new (pool) AST::FunctionDeclaration(stringRef(2), sym(4).FormalParameterList, sym(7).StatementList); node->functionToken = loc(1); + node->identifierToken = loc(2); node->lparenToken = loc(3); node->rparenToken = loc(5); node->lbraceToken = loc(6); @@ -4010,27 +3968,28 @@ GeneratorDeclaration_Default: Function T_STAR GeneratorLParen FormalParameters T } break; ./ -GeneratorExpression: T_FUNCTION T_STAR BindingIdentifier GeneratorLParen FormalParameters T_RPAREN FunctionLBrace GeneratorBody GeneratorRBrace; +GeneratorDeclaration_Default: GeneratorDeclaration; +GeneratorDeclaration_Default: FunctionStar GeneratorLParen FormalParameters T_RPAREN FunctionLBrace GeneratorBody GeneratorRBrace; /. case $rule_number: { - AST::FunctionExpression *node = new (pool) AST::FunctionExpression(stringRef(3), sym(5).FormalParameterList, sym(8).StatementList); + AST::FunctionDeclaration *node = new (pool) AST::FunctionDeclaration(QStringRef(), sym(3).FormalParameterList, sym(6).StatementList); node->functionToken = loc(1); - if (!stringRef(3).isNull()) - node->identifierToken = loc(3); - node->lparenToken = loc(4); - node->rparenToken = loc(6); - node->lbraceToken = loc(7); - node->rbraceToken = loc(9); + node->lparenToken = loc(2); + node->rparenToken = loc(4); + node->lbraceToken = loc(5); + node->rbraceToken = loc(7); node->isGenerator = true; sym(1).Node = node; } break; ./ -GeneratorExpression: T_FUNCTION T_STAR GeneratorLParen FormalParameters T_RPAREN FunctionLBrace GeneratorBody GeneratorRBrace; +GeneratorExpression: T_FUNCTION_STAR BindingIdentifier GeneratorLParen FormalParameters T_RPAREN FunctionLBrace GeneratorBody GeneratorRBrace; /. case $rule_number: { - AST::FunctionExpression *node = new (pool) AST::FunctionExpression(QStringRef(), sym(4).FormalParameterList, sym(7).StatementList); + AST::FunctionExpression *node = new (pool) AST::FunctionExpression(stringRef(2), sym(4).FormalParameterList, sym(7).StatementList); node->functionToken = loc(1); + if (!stringRef(2).isNull()) + node->identifierToken = loc(2); node->lparenToken = loc(3); node->rparenToken = loc(5); node->lbraceToken = loc(6); @@ -4040,6 +3999,20 @@ GeneratorExpression: T_FUNCTION T_STAR GeneratorLParen FormalParameters T_RPAREN } break; ./ +GeneratorExpression: T_FUNCTION_STAR GeneratorLParen FormalParameters T_RPAREN FunctionLBrace GeneratorBody GeneratorRBrace; +/. + case $rule_number: { + AST::FunctionExpression *node = new (pool) AST::FunctionExpression(QStringRef(), sym(3).FormalParameterList, sym(6).StatementList); + node->functionToken = loc(1); + node->lparenToken = loc(2); + node->rparenToken = loc(4); + node->lbraceToken = loc(5); + node->rbraceToken = loc(7); + node->isGenerator = true; + sym(1).Node = node; + } break; +./ + GeneratorBody: FunctionBody; YieldExpression: T_YIELD; @@ -4419,7 +4392,7 @@ ExportDeclarationLookahead: ; /. case $rule_number: { int token = lookaheadToken(lexer); - if (token == T_FUNCTION || token == T_CLASS) + if (token == T_FUNCTION || token == T_FUNCTION_STAR || token == T_CLASS) pushToken(T_FORCE_DECLARATION); } break; ./ diff --git a/src/qml/parser/qqmljsast_p.h b/src/qml/parser/qqmljsast_p.h index 39194068bf..4247785905 100644 --- a/src/qml/parser/qqmljsast_p.h +++ b/src/qml/parser/qqmljsast_p.h @@ -307,6 +307,14 @@ public: int kind = Kind_Undefined; }; +template<typename T> +T lastListElement(T head) +{ + auto current = head; + while (current->next) + current = current->next; + return current; +} class QML_PARSER_EXPORT UiQualifiedId: public Node { @@ -338,7 +346,7 @@ public: { return identifierToken; } SourceLocation lastSourceLocation() const override - { return next ? next->lastSourceLocation() : identifierToken; } + { return lastListElement(this)->identifierToken; } // attributes UiQualifiedId *next; @@ -397,7 +405,7 @@ public: { return typeId->firstSourceLocation(); } SourceLocation lastSourceLocation() const override - { return next ? next->lastSourceLocation() : typeId->lastSourceLocation(); } + { return lastListElement(this)->typeId->lastSourceLocation(); } inline TypeArgumentList *finish() { @@ -678,7 +686,10 @@ public: { return literalToken; } SourceLocation lastSourceLocation() const override - { return next ? next->lastSourceLocation() : (expression ? expression->lastSourceLocation() : literalToken); } + { + auto last = lastListElement(this); + return (last->expression ? last->expression->lastSourceLocation() : last->literalToken); + } void accept0(Visitor *visitor) override; @@ -800,7 +811,7 @@ public: { return commaToken; } SourceLocation lastSourceLocation() const override - { return next ? next->lastSourceLocation() : commaToken; } + { return lastListElement(this)->commaToken; } inline Elision *finish () { @@ -961,7 +972,10 @@ public: { return elision ? elision->firstSourceLocation() : element->firstSourceLocation(); } SourceLocation lastSourceLocation() const override - { return next ? next->lastSourceLocation() : (element ? element->lastSourceLocation() : elision->lastSourceLocation()); } + { + auto last = lastListElement(this); + return last->element ? last->element->lastSourceLocation() : last->elision->lastSourceLocation(); + } Elision *elision = nullptr; PatternElement *element = nullptr; @@ -1036,7 +1050,7 @@ public: { return property->firstSourceLocation(); } SourceLocation lastSourceLocation() const override - { return next ? next->lastSourceLocation() : property->lastSourceLocation(); } + { return lastListElement(this)->property->lastSourceLocation(); } PatternProperty *property; PatternPropertyList *next; @@ -1645,7 +1659,9 @@ public: { return statement->firstSourceLocation(); } SourceLocation lastSourceLocation() const override - { return next ? next->lastSourceLocation() : statement->lastSourceLocation(); } + { + return lastListElement(this)->statement->lastSourceLocation(); + } inline StatementList *finish () { @@ -2138,7 +2154,9 @@ public: { return clause->firstSourceLocation(); } SourceLocation lastSourceLocation() const override - { return next ? next->lastSourceLocation() : clause->lastSourceLocation(); } + { + return lastListElement(this)->clause->lastSourceLocation(); + } inline CaseClauses *finish () { @@ -2429,7 +2447,9 @@ public: { return element->firstSourceLocation(); } SourceLocation lastSourceLocation() const override - { return next ? next->lastSourceLocation() : element->lastSourceLocation(); } + { + return lastListElement(this)->element->lastSourceLocation(); + } FormalParameterList *finish(MemoryPool *pool); @@ -2606,7 +2626,9 @@ public: { return importSpecifierToken; } SourceLocation lastSourceLocation() const override - { return next ? next->lastSourceLocation() : importSpecifierToken; } + { + return lastListElement(this)->importSpecifierToken; + } // attributes SourceLocation importSpecifierToken; @@ -2843,7 +2865,7 @@ public: SourceLocation firstSourceLocation() const override { return exportSpecifier->firstSourceLocation(); } SourceLocation lastSourceLocation() const override - { return next ? next->lastSourceLocation() : exportSpecifier->lastSourceLocation(); } + { return lastListElement(this)->exportSpecifier->lastSourceLocation(); } // attributes ExportSpecifier *exportSpecifier; @@ -3036,7 +3058,7 @@ public: { return member->firstSourceLocation(); } SourceLocation lastSourceLocation() const override - { return next ? next->lastSourceLocation() : member->lastSourceLocation(); } + { return lastListElement(this)->member->lastSourceLocation(); } UiObjectMemberList *finish() { @@ -3115,7 +3137,7 @@ public: { return headerItem->firstSourceLocation(); } SourceLocation lastSourceLocation() const override - { return next ? next->lastSourceLocation() : headerItem->lastSourceLocation(); } + { return lastListElement(this)->headerItem->lastSourceLocation(); } // attributes Node *headerItem; @@ -3179,7 +3201,7 @@ public: { return member->firstSourceLocation(); } SourceLocation lastSourceLocation() const override - { return next ? next->lastSourceLocation() : member->lastSourceLocation(); } + { return lastListElement(this)->member->lastSourceLocation(); } UiArrayMemberList *finish() { @@ -3240,7 +3262,10 @@ public: { return colonToken.isValid() ? identifierToken : propertyTypeToken; } SourceLocation lastSourceLocation() const override - { return next ? next->lastSourceLocation() : (colonToken.isValid() ? propertyTypeToken : identifierToken); } + { + auto last = lastListElement(this); + return (last->colonToken.isValid() ? last->propertyTypeToken : last->identifierToken); + } inline UiParameterList *finish () { @@ -3283,6 +3308,8 @@ public: return defaultToken; else if (readonlyToken.isValid()) return readonlyToken; + else if (requiredToken.isValid()) + return requiredToken; return propertyToken; } @@ -3306,10 +3333,13 @@ public: UiObjectMember *binding; // initialized with a QML object or array. bool isDefaultMember; bool isReadonlyMember; + bool isRequired = false; UiParameterList *parameters; + // TODO: merge source locations SourceLocation defaultToken; SourceLocation readonlyToken; SourceLocation propertyToken; + SourceLocation requiredToken; SourceLocation typeModifierToken; SourceLocation typeToken; SourceLocation identifierToken; @@ -3493,8 +3523,10 @@ public: { return memberToken; } SourceLocation lastSourceLocation() const override - { return next ? next->lastSourceLocation() : - valueToken.isValid() ? valueToken : memberToken; } + { + auto last = lastListElement(this); + return last->valueToken.isValid() ? last->valueToken : last->memberToken; + } void accept0(Visitor *visitor) override; diff --git a/src/qml/parser/qqmljskeywords_p.h b/src/qml/parser/qqmljskeywords_p.h index 96b3709162..3eb054341f 100644 --- a/src/qml/parser/qqmljskeywords_p.h +++ b/src/qml/parser/qqmljskeywords_p.h @@ -743,6 +743,18 @@ static inline int classify8(const QChar *s, int parseModeFlags) { } } } + } else if (s[2].unicode() == 'q') { + if (s[3].unicode() == 'u') { + if (s[4].unicode() == 'i') { + if (s[5].unicode() == 'r') { + if (s[6].unicode() == 'e') { + if (s[7].unicode() == 'd') { + return Lexer::T_REQUIRED; + } + } + } + } + } } } } diff --git a/src/qml/parser/qqmljslexer.cpp b/src/qml/parser/qqmljslexer.cpp index 1e0ac72bd1..443e1a7476 100644 --- a/src/qml/parser/qqmljslexer.cpp +++ b/src/qml/parser/qqmljslexer.cpp @@ -492,6 +492,42 @@ int Lexer::scanToken() again: _validTokenText = false; + // handle comment can be called after a '/' has been read + // and returns true if it actually encountered a comment + auto handleComment = [this](){ + if (_char == QLatin1Char('*')) { + scanChar(); + while (_codePtr <= _endPtr) { + if (_char == QLatin1Char('*')) { + scanChar(); + if (_char == QLatin1Char('/')) { + scanChar(); + + if (_engine) { + _engine->addComment(tokenOffset() + 2, _codePtr - _tokenStartPtr - 1 - 4, + tokenStartLine(), tokenStartColumn() + 2); + } + + return true; + } + } else { + scanChar(); + } + } + } else if (_char == QLatin1Char('/')) { + while (_codePtr <= _endPtr && !isLineTerminator()) { + scanChar(); + } + if (_engine) { + _engine->addComment(tokenOffset() + 2, _codePtr - _tokenStartPtr - 1 - 2, + tokenStartLine(), tokenStartColumn() + 2); + } + return true; + } + return false; + }; + + while (_char.isSpace()) { if (isLineTerminator()) { if (_restrictedKeyword) { @@ -599,35 +635,9 @@ again: case ':': return T_COLON; case '/': - if (_char == QLatin1Char('*')) { - scanChar(); - while (_codePtr <= _endPtr) { - if (_char == QLatin1Char('*')) { - scanChar(); - if (_char == QLatin1Char('/')) { - scanChar(); - - if (_engine) { - _engine->addComment(tokenOffset() + 2, _codePtr - _tokenStartPtr - 1 - 4, - tokenStartLine(), tokenStartColumn() + 2); - } - - goto again; - } - } else { - scanChar(); - } - } - } else if (_char == QLatin1Char('/')) { - while (_codePtr <= _endPtr && !isLineTerminator()) { - scanChar(); - } - if (_engine) { - _engine->addComment(tokenOffset() + 2, _codePtr - _tokenStartPtr - 1 - 2, - tokenStartLine(), tokenStartColumn() + 2); - } + if (handleComment()) goto again; - } if (_char == QLatin1Char('=')) { + else if (_char == QLatin1Char('=')) { scanChar(); return T_DIVIDE_EQ; } @@ -829,6 +839,21 @@ again: if (!identifierWithEscapeChars) kind = classify(_tokenStartPtr, _tokenLength, parseModeFlags()); + if (kind == T_FUNCTION) { + continue_skipping: + while (_codePtr < _endPtr && _char.isSpace()) + scanChar(); + if (_char == QLatin1Char('*')) { + _tokenLength = _codePtr - _tokenStartPtr - 1; + kind = T_FUNCTION_STAR; + scanChar(); + } else if (_char == QLatin1Char('/')) { + scanChar(); + if (handleComment()) + goto continue_skipping; + } + } + if (_engine) { if (kind == T_IDENTIFIER && identifierWithEscapeChars) _tokenSpell = _engine->newStringRef(_tokenText); @@ -1407,6 +1432,7 @@ static const int uriTokens[] = { QQmlJSGrammar::T_FINALLY, QQmlJSGrammar::T_FOR, QQmlJSGrammar::T_FUNCTION, + QQmlJSGrammar::T_FUNCTION_STAR, QQmlJSGrammar::T_IF, QQmlJSGrammar::T_IN, QQmlJSGrammar::T_OF, diff --git a/src/qml/qml/qqmlbinding.cpp b/src/qml/qml/qqmlbinding.cpp index 3a437eab8d..2f6aabf61e 100644 --- a/src/qml/qml/qqmlbinding.cpp +++ b/src/qml/qml/qqmlbinding.cpp @@ -315,7 +315,7 @@ protected: break; default: if (const QV4::QQmlValueTypeWrapper *vtw = result.as<const QV4::QQmlValueTypeWrapper>()) { - if (vtw->d()->valueType->metaType.id() == pd->propType()) { + if (vtw->d()->valueType()->metaType.id() == pd->propType()) { return vtw->write(m_target.data(), pd->coreIndex()); } } diff --git a/src/qml/qml/qqmlcomponent.cpp b/src/qml/qml/qqmlcomponent.cpp index ed8c41a582..b72c745490 100644 --- a/src/qml/qml/qqmlcomponent.cpp +++ b/src/qml/qml/qqmlcomponent.cpp @@ -340,6 +340,11 @@ void QQmlComponentPrivate::fromTypeData(const QQmlRefPointer<QQmlTypeData> &data } } +RequiredProperties &QQmlComponentPrivate::requiredProperties() +{ + return state.creator->requiredProperties(); +} + void QQmlComponentPrivate::clear() { if (typeData) { @@ -364,8 +369,8 @@ QObject *QQmlComponentPrivate::doBeginCreate(QQmlComponent *q, QQmlContext *cont bool QQmlComponentPrivate::setInitialProperty(QObject *component, const QString& name, const QVariant &value) { - QQmlProperty prop(component, name); - auto privProp = QQmlPropertyPrivate::get(prop); + QQmlProperty prop = QQmlComponentPrivate::removePropertyFromRequired(component, name, requiredProperties()); + QQmlPropertyPrivate *privProp = QQmlPropertyPrivate::get(prop); if (!prop.isValid() || !privProp->writeValueProperty(value, nullptr)) { QQmlError error{}; error.setUrl(url); @@ -809,6 +814,10 @@ QObject *QQmlComponent::create(QQmlContext *context) QObject *rv = d->doBeginCreate(this, context); if (rv) completeCreate(); + if (rv && !d->requiredProperties().empty()) { + delete rv; + return nullptr; + } return rv; } @@ -828,6 +837,10 @@ QObject *QQmlComponent::createWithInitialProperties(const QVariantMap& initialPr setInitialProperties(rv, initialProperties); completeCreate(); } + if (!d->requiredProperties().empty()) { + d->requiredProperties().clear(); + return nullptr; + } return rv; } @@ -980,6 +993,57 @@ void QQmlComponentPrivate::complete(QQmlEnginePrivate *enginePriv, ConstructionS } /*! + * \internal + * Finds the matching toplevel property with name \a name of the component \a createdComponent. + * If it was a required property or an alias to a required property contained in \a + * requiredProperties, it is removed from it. + * + * If wasInRequiredProperties is non-null, the referenced boolean is set to true iff the property + * was found in requiredProperties. + * + * Returns the QQmlProperty with name \a name (which might be invalid if there is no such property), + * for further processing (for instance, actually setting the property value). + * + * Note: This method is used in QQmlComponent and QQmlIncubator to manage required properties. Most + * classes which create components should not need it and should only need to call + * setInitialProperties. + */ +QQmlProperty QQmlComponentPrivate::removePropertyFromRequired(QObject *createdComponent, const QString &name, RequiredProperties &requiredProperties, bool* wasInRequiredProperties) +{ + QQmlProperty prop(createdComponent, name); + auto privProp = QQmlPropertyPrivate::get(prop); + if (prop.isValid()) { + // resolve outstanding required properties + auto targetProp = &privProp->core; + if (targetProp->isAlias()) { + auto target = createdComponent; + QQmlPropertyIndex originalIndex(targetProp->coreIndex()); + QQmlPropertyIndex propIndex; + QQmlPropertyPrivate::findAliasTarget(target, originalIndex, &target, &propIndex); + QQmlData *data = QQmlData::get(target); + Q_ASSERT(data && data->propertyCache); + targetProp = data->propertyCache->property(propIndex.coreIndex()); + } else { + // we need to get the pointer from the property cache instead of directly using + // targetProp else the lookup will fail + QQmlData *data = QQmlData::get(createdComponent); + Q_ASSERT(data && data->propertyCache); + targetProp = data->propertyCache->property(targetProp->coreIndex()); + } + auto it = requiredProperties.find(targetProp); + if (it != requiredProperties.end()) { + if (wasInRequiredProperties) + *wasInRequiredProperties = true; + requiredProperties.erase(it); + } else { + if (wasInRequiredProperties) + *wasInRequiredProperties = false; + } + } + return prop; +} + +/*! This method provides advanced control over component instance creation. In general, programmers should use QQmlComponent::create() to create a component. @@ -998,6 +1062,11 @@ void QQmlComponent::completeCreate() void QQmlComponentPrivate::completeCreate() { + const RequiredProperties& unsetRequiredProperties = requiredProperties(); + for (const auto& unsetRequiredProperty: unsetRequiredProperties) { + QQmlError error = unsetRequiredPropertyToQQmlError(unsetRequiredProperty); + state.errors.push_back(error); + } if (state.completePending) { ++creationDepth.localData(); QQmlEnginePrivate *ep = QQmlEnginePrivate::get(engine); @@ -1185,7 +1254,7 @@ struct QmlIncubatorObject : public QV4::Object static ReturnedValue method_forceCompletion(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); void statusChanged(QQmlIncubator::Status); - void setInitialState(QObject *); + void setInitialState(QObject *, RequiredProperties &requiredProperties); }; } @@ -1210,7 +1279,8 @@ public: void setInitialState(QObject *o) override { QV4::Scope scope(incubatorObject.engine()); QV4::Scoped<QV4::QmlIncubatorObject> i(scope, incubatorObject.as<QV4::QmlIncubatorObject>()); - i->setInitialState(o); + auto d = QQmlIncubatorPrivate::get(this); + i->setInitialState(o, d->requiredProperties()); } QV4::PersistentValue incubatorObject; // keep a strong internal reference while incubating @@ -1282,7 +1352,7 @@ static void QQmlComponent_setQmlParent(QObject *me, QObject *parent) */ -void QQmlComponentPrivate::setInitialProperties(QV4::ExecutionEngine *engine, QV4::QmlContext *qmlContext, const QV4::Value &o, const QV4::Value &v) +void QQmlComponentPrivate::setInitialProperties(QV4::ExecutionEngine *engine, QV4::QmlContext *qmlContext, const QV4::Value &o, const QV4::Value &v, RequiredProperties &requiredProperties, QObject *createdComponent) { QV4::Scope scope(engine); QV4::ScopedObject object(scope); @@ -1301,6 +1371,7 @@ void QQmlComponentPrivate::setInitialProperties(QV4::ExecutionEngine *engine, QV break; object = o; const QStringList properties = name->toQString().split(QLatin1Char('.')); + bool isTopLevelProperty = properties.size() == 1; for (int i = 0; i < properties.length() - 1; ++i) { name = engine->newString(properties.at(i)); object = object->get(name); @@ -1317,12 +1388,40 @@ void QQmlComponentPrivate::setInitialProperties(QV4::ExecutionEngine *engine, QV if (engine->hasException) { engine->hasException = false; continue; + } else if (isTopLevelProperty) { + auto prop = removePropertyFromRequired(createdComponent, name->toQString(), requiredProperties); } } engine->hasException = false; } +QQmlError QQmlComponentPrivate::unsetRequiredPropertyToQQmlError(const RequiredPropertyInfo &unsetRequiredProperty) +{ + QQmlError error; + QString description = QLatin1String("Required property %1 was not initialized").arg(unsetRequiredProperty.propertyName); + switch (unsetRequiredProperty.aliasesToRequired.size()) { + case 0: + break; + case 1: { + const auto info = unsetRequiredProperty.aliasesToRequired.first(); + description += QLatin1String("\nIt can be set via the alias property %1 from %2\n").arg(info.propertyName, info.fileUrl.toString()); + break; + } + default: + description += QLatin1String("\nIt can be set via one of the following alias properties:"); + for (auto aliasInfo: unsetRequiredProperty.aliasesToRequired) { + description += QLatin1String("\n- %1 (%2)").arg(aliasInfo.propertyName, aliasInfo.fileUrl.toString()); + } + description += QLatin1Char('\n'); + } + error.setDescription(description); + error.setUrl(unsetRequiredProperty.fileUrl); + error.setLine(unsetRequiredProperty.location.line); + error.setColumn(unsetRequiredProperty.location.column); + return error; +} + /*! \internal */ @@ -1370,7 +1469,17 @@ void QQmlComponent::createObject(QQmlV4Function *args) if (!valuemap->isUndefined()) { QV4::Scoped<QV4::QmlContext> qmlContext(scope, v4->qmlContext()); - QQmlComponentPrivate::setInitialProperties(v4, qmlContext, object, valuemap); + QQmlComponentPrivate::setInitialProperties(v4, qmlContext, object, valuemap, d->requiredProperties(), rv); + } + if (!d->requiredProperties().empty()) { + QList<QQmlError> errors; + for (const auto &requiredProperty: d->requiredProperties()) { + errors.push_back(QQmlComponentPrivate::unsetRequiredPropertyToQQmlError(requiredProperty)); + } + qmlWarning(rv, errors); + args->setReturnValue(QV4::Encode::null()); + delete rv; + return; } d->completeCreate(); @@ -1502,7 +1611,7 @@ void QQmlComponent::incubateObject(QQmlV4Function *args) } // XXX used by QSGLoader -void QQmlComponentPrivate::initializeObjectWithInitialProperties(QV4::QmlContext *qmlContext, const QV4::Value &valuemap, QObject *toCreate) +void QQmlComponentPrivate::initializeObjectWithInitialProperties(QV4::QmlContext *qmlContext, const QV4::Value &valuemap, QObject *toCreate, RequiredProperties &requiredProperties) { QV4::ExecutionEngine *v4engine = engine->handle(); QV4::Scope scope(v4engine); @@ -1511,7 +1620,7 @@ void QQmlComponentPrivate::initializeObjectWithInitialProperties(QV4::QmlContext Q_ASSERT(object->as<QV4::Object>()); if (!valuemap.isUndefined()) - setInitialProperties(v4engine, qmlContext, object, valuemap); + setInitialProperties(v4engine, qmlContext, object, valuemap, requiredProperties, toCreate); } QQmlComponentExtension::QQmlComponentExtension(QV4::ExecutionEngine *v4) @@ -1601,7 +1710,7 @@ void QV4::Heap::QmlIncubatorObject::destroy() { Object::destroy(); } -void QV4::QmlIncubatorObject::setInitialState(QObject *o) +void QV4::QmlIncubatorObject::setInitialState(QObject *o, RequiredProperties &requiredProperties) { QQmlComponent_setQmlParent(o, d()->parent); @@ -1610,7 +1719,7 @@ void QV4::QmlIncubatorObject::setInitialState(QObject *o) QV4::Scope scope(v4); QV4::ScopedObject obj(scope, QV4::QObjectWrapper::wrap(v4, o)); QV4::Scoped<QV4::QmlContext> qmlCtxt(scope, d()->qmlContext); - QQmlComponentPrivate::setInitialProperties(v4, qmlCtxt, obj, d()->valuemap); + QQmlComponentPrivate::setInitialProperties(v4, qmlCtxt, obj, d()->valuemap, requiredProperties, o); } } diff --git a/src/qml/qml/qqmlcomponent_p.h b/src/qml/qml/qqmlcomponent_p.h index 2170646b89..8ae7672c19 100644 --- a/src/qml/qml/qqmlcomponent_p.h +++ b/src/qml/qml/qqmlcomponent_p.h @@ -86,8 +86,9 @@ public: QObject *beginCreate(QQmlContextData *); void completeCreate(); - void initializeObjectWithInitialProperties(QV4::QmlContext *qmlContext, const QV4::Value &valuemap, QObject *toCreate); - static void setInitialProperties(QV4::ExecutionEngine *engine, QV4::QmlContext *qmlContext, const QV4::Value &o, const QV4::Value &v); + void initializeObjectWithInitialProperties(QV4::QmlContext *qmlContext, const QV4::Value &valuemap, QObject *toCreate, RequiredProperties &requiredProperties); + static void setInitialProperties(QV4::ExecutionEngine *engine, QV4::QmlContext *qmlContext, const QV4::Value &o, const QV4::Value &v, RequiredProperties &requiredProperties, QObject *createdComponent); + static QQmlError unsetRequiredPropertyToQQmlError(const RequiredPropertyInfo &unsetRequiredProperty); virtual void incubateObject( QQmlIncubator *incubationTask, @@ -106,6 +107,7 @@ public: qreal progress; int start; + RequiredProperties& requiredProperties(); QQmlRefPointer<QV4::ExecutableCompilationUnit> compilationUnit; struct ConstructionState { @@ -134,6 +136,7 @@ public: static void completeDeferred(QQmlEnginePrivate *enginePriv, DeferredState *deferredState); static void complete(QQmlEnginePrivate *enginePriv, ConstructionState *state); + static QQmlProperty removePropertyFromRequired(QObject *createdComponent, const QString &name, RequiredProperties& requiredProperties, bool *wasInRequiredProperties = nullptr); QQmlEngine *engine; QQmlGuardedContextData creationContext; diff --git a/src/qml/qml/qqmlincubator.cpp b/src/qml/qml/qqmlincubator.cpp index bc06226cbf..7b3ae31c08 100644 --- a/src/qml/qml/qqmlincubator.cpp +++ b/src/qml/qml/qqmlincubator.cpp @@ -43,6 +43,7 @@ #include "qqmlexpression_p.h" #include "qqmlobjectcreator_p.h" +#include <private/qqmlcomponent_p.h> void QQmlEnginePrivate::incubate(QQmlIncubator &i, QQmlContextData *forContext) { @@ -296,6 +297,20 @@ void QQmlIncubatorPrivate::incubate(QQmlInstantiationInterrupt &i) tresult = creator->create(subComponentToCreate, /*parent*/nullptr, &i); if (!tresult) errors = creator->errors; + else { + RequiredProperties& requiredProperties = creator->requiredProperties(); + for (auto it = initialProperties.cbegin(); it != initialProperties.cend(); ++it) { + auto component = tresult; + auto name = it.key(); + QQmlProperty prop = QQmlComponentPrivate::removePropertyFromRequired(component, name, requiredProperties); + if (!prop.isValid() || !prop.write(it.value())) { + QQmlError error{}; + error.setUrl(compilationUnit->url()); + error.setDescription(QLatin1String("Could not set property %1").arg(name)); + errors.push_back(error); + } + } + } enginePriv->dereferenceScarceResources(); if (watcher.hasRecursed()) @@ -312,8 +327,14 @@ void QQmlIncubatorPrivate::incubate(QQmlInstantiationInterrupt &i) ddata->indestructible = true; ddata->explicitIndestructibleSet = true; ddata->rootObjectInCreation = false; - if (q) + if (q) { q->setInitialState(result); + if (!creator->requiredProperties().empty()) { + const auto& unsetRequiredProperties = creator->requiredProperties(); + for (const auto& unsetRequiredProperty: unsetRequiredProperties) + errors << QQmlComponentPrivate::unsetRequiredPropertyToQQmlError(unsetRequiredProperty); + } + } } if (watcher.hasRecursed()) @@ -657,6 +678,31 @@ QObject *QQmlIncubator::object() const } /*! +Return a list of properties which are required but haven't been set yet. +This list can be modified, so that subclasses which implement special logic +setInitialProperties can mark properties set there as no longer required. + +\sa QQmlIncubator::setInitialProperties +\since 5.15 +*/ +RequiredProperties &QQmlIncubatorPrivate::requiredProperties() +{ + return creator->requiredProperties(); +} + +/*! +Stores a mapping from property names to initial values with which the incubated +component will be initialized + +\sa QQmlComponent::setInitialProperties +\since 5.15 +*/ +void QQmlIncubator::setInitialProperties(const QVariantMap &initialProperties) +{ + d->initialProperties = initialProperties; +} + +/*! Called when the status of the incubator changes. \a status is the new status. The default implementation does nothing. diff --git a/src/qml/qml/qqmlincubator.h b/src/qml/qml/qqmlincubator.h index e68f6e3c45..f075407e73 100644 --- a/src/qml/qml/qqmlincubator.h +++ b/src/qml/qml/qqmlincubator.h @@ -47,6 +47,9 @@ QT_BEGIN_NAMESPACE class QQmlEngine; +class QQmlPropertyData; +class QVariant; +using QVariantMap = QMap<QString, QVariant>; class QQmlIncubatorPrivate; class Q_QML_EXPORT QQmlIncubator @@ -84,6 +87,8 @@ public: QObject *object() const; + void setInitialProperties(const QVariantMap &initialProperties); + protected: virtual void statusChanged(Status); virtual void setInitialState(QObject *); diff --git a/src/qml/qml/qqmlincubator_p.h b/src/qml/qml/qqmlincubator_p.h index 57ec8249cb..731db7aad3 100644 --- a/src/qml/qml/qqmlincubator_p.h +++ b/src/qml/qml/qqmlincubator_p.h @@ -61,8 +61,12 @@ QT_BEGIN_NAMESPACE +class QQmlPropertyData; +struct RequiredPropertyInfo; +using RequiredProperties = QHash<QQmlPropertyData*, RequiredPropertyInfo>; + class QQmlIncubator; -class QQmlIncubatorPrivate : public QQmlEnginePrivate::Incubator +class Q_QML_PRIVATE_EXPORT QQmlIncubatorPrivate : public QQmlEnginePrivate::Incubator { public: QQmlIncubatorPrivate(QQmlIncubator *q, QQmlIncubator::IncubationMode m); @@ -97,11 +101,13 @@ public: QIntrusiveList<QIPBase, &QIPBase::nextWaitingFor> waitingFor; QRecursionNode recursion; + QVariantMap initialProperties; void clear(); void forceCompletion(QQmlInstantiationInterrupt &i); void incubate(QQmlInstantiationInterrupt &i); + RequiredProperties &requiredProperties(); }; QT_END_NAMESPACE diff --git a/src/qml/qml/qqmlobjectcreator.cpp b/src/qml/qml/qqmlobjectcreator.cpp index f89608cd5d..b723ddb381 100644 --- a/src/qml/qml/qqmlobjectcreator.cpp +++ b/src/qml/qml/qqmlobjectcreator.cpp @@ -783,6 +783,23 @@ void QQmlObjectCreator::setupBindings(bool applyDeferredBindings) const QV4::CompiledData::Binding *binding = _compiledObject->bindingTable(); for (quint32 i = 0; i < _compiledObject->nBindings; ++i, ++binding) { + QQmlPropertyData *const property = propertyData.at(i); + if (property) { + QQmlPropertyData* targetProperty = property; + if (targetProperty->isAlias()) { + // follow alias + auto target = _bindingTarget; + QQmlPropertyIndex originalIndex(targetProperty->coreIndex(), _valueTypeProperty ? _valueTypeProperty->coreIndex() : -1); + QQmlPropertyIndex propIndex; + QQmlPropertyPrivate::findAliasTarget(target, originalIndex, &target, &propIndex); + QQmlData *data = QQmlData::get(target); + Q_ASSERT(data && data->propertyCache); + targetProperty = data->propertyCache->property(propIndex.coreIndex()); + } + sharedState->requiredProperties.remove(targetProperty); + } + + if (binding->flags & QV4::CompiledData::Binding::IsCustomParserBinding) continue; @@ -794,8 +811,6 @@ void QQmlObjectCreator::setupBindings(bool applyDeferredBindings) continue; } - const QQmlPropertyData *property = propertyData.at(i); - if (property && property->isQList()) { if (property->coreIndex() != currentListPropertyIndex) { void *argv[1] = { (void*)&_currentList }; @@ -1504,10 +1519,42 @@ bool QQmlObjectCreator::populateInstance(int index, QObject *instance, QObject * if (_compiledObject->flags & QV4::CompiledData::Object::HasDeferredBindings) _ddata->deferData(_compiledObjectIndex, compilationUnit, context); + 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->requiredProperties.insert(propertyData, + RequiredPropertyInfo {compilationUnit->stringAt(property->nameIndex), compilationUnit->finalUrl(), property->location, {}}); + } + } + if (_compiledObject->nFunctions > 0) setupFunctions(); setupBindings(); + for (int aliasIndex = 0; aliasIndex != _compiledObject->aliasCount(); ++aliasIndex) { + const QV4::CompiledData::Alias* alias = _compiledObject->aliasesBegin() + aliasIndex; + const auto originalAlias = alias; + while (alias->aliasToLocalAlias) + alias = _compiledObject->aliasesBegin() + alias->localAliasIndex; + Q_ASSERT(alias->flags & QV4::CompiledData::Alias::Resolved); + if (!context->idValues->wasSet()) + continue; + QObject *target = context->idValues[alias->targetObjectId].data(); + if (!target) + continue; + QQmlData *targetDData = QQmlData::get(target, /*create*/false); + if (!targetDData) + continue; + int coreIndex = QQmlPropertyIndex::fromEncoded(alias->encodedMetaPropertyIndex).coreIndex(); + QQmlPropertyData *const targetProperty = targetDData->propertyCache->property(coreIndex); + if (!targetProperty) + continue; + auto it = sharedState->requiredProperties.find(targetProperty); + if (it != sharedState->requiredProperties.end()) + it->aliasesToRequired.push_back(AliasToRequiredInfo {compilationUnit->stringAt(originalAlias->nameIndex), compilationUnit->finalUrl()}); + } + qSwap(_vmeMetaObject, vmeMetaObject); qSwap(_bindingTarget, bindingTarget); qSwap(_ddata, declarativeData); diff --git a/src/qml/qml/qqmlobjectcreator_p.h b/src/qml/qml/qqmlobjectcreator_p.h index ecdbcc56dd..ee1d82d4e3 100644 --- a/src/qml/qml/qqmlobjectcreator_p.h +++ b/src/qml/qml/qqmlobjectcreator_p.h @@ -66,6 +66,28 @@ class QQmlAbstractBinding; class QQmlInstantiationInterrupt; class QQmlIncubatorPrivate; +struct AliasToRequiredInfo { + QString propertyName; + QUrl fileUrl; +}; + +/*! +\internal +This struct contains information solely used for displaying error messages +\variable aliasesToRequired allows us to give the user a way to know which (aliasing) properties +can be set to set the required property +\sa QQmlComponentPrivate::unsetRequiredPropertyToQQmlError +*/ +struct RequiredPropertyInfo +{ + QString propertyName; + QUrl fileUrl; + QV4::CompiledData::Location location; + QVector<AliasToRequiredInfo> aliasesToRequired; +}; + +using RequiredProperties = QHash<QQmlPropertyData*, RequiredPropertyInfo>; + struct QQmlObjectCreatorSharedState : public QSharedData { QQmlContextData *rootContext; @@ -78,6 +100,7 @@ struct QQmlObjectCreatorSharedState : public QSharedData QList<QQmlEnginePrivate::FinalizeCallback> finalizeCallbacks; QQmlVmeProfiler profiler; QRecursionNode recursionNode; + RequiredProperties requiredProperties; }; class Q_QML_PRIVATE_EXPORT QQmlObjectCreator @@ -102,6 +125,8 @@ public: QQmlContextData *parentContextData() const { return parentContext.contextData(); } QFiniteStack<QPointer<QObject> > &allCreatedObjects() { return sharedState->allCreatedObjects; } + RequiredProperties &requiredProperties() {return sharedState->requiredProperties;} + private: QQmlObjectCreator(QQmlContextData *contextData, const QQmlRefPointer<QV4::ExecutableCompilationUnit> &compilationUnit, QQmlObjectCreatorSharedState *inheritedSharedState); diff --git a/src/qml/qml/qqmlproperty.cpp b/src/qml/qml/qqmlproperty.cpp index 5f57e0eca1..eff3e94fbd 100644 --- a/src/qml/qml/qqmlproperty.cpp +++ b/src/qml/qml/qqmlproperty.cpp @@ -885,7 +885,7 @@ void QQmlPropertyPrivate::setBinding(QQmlAbstractBinding *binding, BindingFlags QQmlData *data = QQmlData::get(object, true); if (data->propertyCache) { QQmlPropertyData *propertyData = data->propertyCache->property(coreIndex); - Q_ASSERT(propertyData && !propertyData->isAlias()); + Q_ASSERT(propertyData); } #endif diff --git a/src/qml/qml/qqmlpropertyvalidator.cpp b/src/qml/qml/qqmlpropertyvalidator.cpp index 7dbcbe986b..6959b05105 100644 --- a/src/qml/qml/qqmlpropertyvalidator.cpp +++ b/src/qml/qml/qqmlpropertyvalidator.cpp @@ -49,6 +49,19 @@ QT_BEGIN_NAMESPACE +static bool isPrimitiveType(int typeId) +{ + switch (typeId) { +#define HANDLE_PRIMITIVE(Type, id, T) \ + case QMetaType::Type: +QT_FOR_EACH_STATIC_PRIMITIVE_TYPE(HANDLE_PRIMITIVE); +#undef HANDLE_PRIMITIVE + return true; + default: + return false; + } +} + QQmlPropertyValidator::QQmlPropertyValidator(QQmlEnginePrivate *enginePrivate, const QQmlImports &imports, const QQmlRefPointer<QV4::ExecutableCompilationUnit> &compilationUnit) : enginePrivate(enginePrivate) , compilationUnit(compilationUnit) @@ -281,11 +294,21 @@ QVector<QQmlJS::DiagnosticMessage> QQmlPropertyValidator::validateObject( return recordError(binding->location, tr("Invalid grouped property access")); } } else { - if (!enginePrivate->propertyCacheForType(pd->propType())) { + const int typeId = pd->propType(); + if (isPrimitiveType(typeId)) { + return recordError( + binding->location, + tr("Invalid grouped property access: Property \"%1\" with primitive type \"%2\".") + .arg(name) + .arg(QString::fromLatin1(QMetaType::typeName(typeId))) + ); + } + + if (!enginePrivate->propertyCacheForType(typeId)) { return recordError(binding->location, tr("Invalid grouped property access: Property \"%1\" with type \"%2\", which is not a value type") .arg(name) - .arg(QString::fromLatin1(QMetaType::typeName(pd->propType()))) + .arg(QString::fromLatin1(QMetaType::typeName(typeId))) ); } } @@ -679,15 +702,21 @@ QQmlJS::DiagnosticMessage QQmlPropertyValidator::validateObjectBinding(QQmlPrope return noError; } - if (QQmlMetaType::isInterface(property->propType())) { + const int propType = property->propType(); + const auto rhsType = [&]() { + return stringAt(compilationUnit->objectAt(binding->value.objectIndex) + ->inheritedTypeNameIndex); + }; + + if (QQmlMetaType::isInterface(propType)) { // Can only check at instantiation time if the created sub-object successfully casts to the // target interface. return noError; - } else if (property->propType() == QMetaType::QVariant || property->propType() == qMetaTypeId<QJSValue>()) { + } else if (propType == QMetaType::QVariant || propType == qMetaTypeId<QJSValue>()) { // We can convert everything to QVariant :) return noError; } else if (property->isQList()) { - const int listType = enginePrivate->listType(property->propType()); + const int listType = enginePrivate->listType(propType); if (!QQmlMetaType::isInterface(listType)) { QQmlPropertyCache *source = propertyCaches.at(binding->value.objectIndex); if (!canCoerce(listType, source)) { @@ -697,19 +726,23 @@ QQmlJS::DiagnosticMessage QQmlPropertyValidator::validateObjectBinding(QQmlPrope return noError; } else if (binding->flags & QV4::CompiledData::Binding::IsSignalHandlerObject && property->isFunction()) { return noError; - } else if (QQmlValueTypeFactory::isValueType(property->propType())) { - auto typeName = QMetaType::typeName(property->propType()); + } else if (isPrimitiveType(propType)) { + auto typeName = QMetaType::typeName(propType); + return qQmlCompileError(binding->location, tr("Can not assign value of type \"%1\" to property \"%2\", expecting \"%3\"") + .arg(rhsType()) + .arg(propertyName) + .arg(typeName)); + } else if (QQmlValueTypeFactory::isValueType(propType)) { return qQmlCompileError(binding->location, tr("Can not assign value of type \"%1\" to property \"%2\", expecting an object") - .arg(typeName ? QString::fromLatin1(typeName) : QString::fromLatin1("<unknown type>")) - .arg(propertyName)); - } else if (property->propType() == qMetaTypeId<QQmlScriptString>()) { + .arg(rhsType()).arg(propertyName)); + } else if (propType == qMetaTypeId<QQmlScriptString>()) { return qQmlCompileError(binding->valueLocation, tr("Invalid property assignment: script expected")); } else { // We want to use the raw metaObject here as the raw metaobject is the // actual property type before we applied any extensions that might // effect the properties on the type, but don't effect assignability // Using -1 for the minor version ensures that we get the raw metaObject. - QQmlPropertyCache *propertyMetaObject = enginePrivate->rawPropertyCacheForType(property->propType(), -1); + QQmlPropertyCache *propertyMetaObject = enginePrivate->rawPropertyCacheForType(propType, -1); if (propertyMetaObject) { // Will be true if the assigned type inherits propertyMetaObject @@ -723,11 +756,11 @@ QQmlJS::DiagnosticMessage QQmlPropertyValidator::validateObjectBinding(QQmlPrope if (!isAssignable) { return qQmlCompileError(binding->valueLocation, tr("Cannot assign object of type \"%1\" to property of type \"%2\" as the former is neither the same as the latter nor a sub-class of it.") - .arg(stringAt(compilationUnit->objectAt(binding->value.objectIndex)->inheritedTypeNameIndex)).arg(QLatin1String(QMetaType::typeName(property->propType())))); + .arg(rhsType()).arg(QLatin1String(QMetaType::typeName(propType)))); } } else { return qQmlCompileError(binding->valueLocation, tr("Cannot assign to property of unknown type \"%1\".") - .arg(QLatin1String(QMetaType::typeName(property->propType())))); + .arg(QLatin1String(QMetaType::typeName(propType)))); } } diff --git a/src/qml/qml/qqmltype.cpp b/src/qml/qml/qqmltype.cpp index 2a6831d898..252ff26a64 100644 --- a/src/qml/qml/qqmltype.cpp +++ b/src/qml/qml/qqmltype.cpp @@ -154,23 +154,6 @@ bool QQmlType::availableInVersion(const QHashedStringRef &module, int vmajor, in return module == d->module && vmajor == d->version_maj && vminor >= d->version_min; } -// returns the nearest _registered_ super class -QQmlType QQmlType::superType() const -{ - if (!d) - return QQmlType(); - if (!d->haveSuperType && d->baseMetaObject) { - const QMetaObject *mo = d->baseMetaObject->superClass(); - while (mo && !d->superType.isValid()) { - d->superType = QQmlMetaType::qmlType(mo, d->module, d->version_maj, d->version_min); - mo = mo->superClass(); - } - d->haveSuperType = true; - } - - return d->superType; -} - QQmlType QQmlType::resolveCompositeBaseType(QQmlEnginePrivate *engine) const { Q_ASSERT(isComposite()); diff --git a/src/qml/qml/qqmltype_p.h b/src/qml/qml/qqmltype_p.h index 158fefad2c..4dec20600b 100644 --- a/src/qml/qml/qqmltype_p.h +++ b/src/qml/qml/qqmltype_p.h @@ -182,7 +182,6 @@ public: }; private: - QQmlType superType() const; QQmlType resolveCompositeBaseType(QQmlEnginePrivate *engine) const; int resolveCompositeEnumValue(QQmlEnginePrivate *engine, const QString &name, bool *ok) const; QQmlPropertyCache *compositePropertyCache(QQmlEnginePrivate *engine) const; diff --git a/src/qml/qml/qqmlvaluetype.cpp b/src/qml/qml/qqmlvaluetype.cpp index 2225191a9d..d83fc4bb48 100644 --- a/src/qml/qml/qqmlvaluetype.cpp +++ b/src/qml/qml/qqmlvaluetype.cpp @@ -65,11 +65,13 @@ struct QQmlValueTypeFactoryImpl QQmlValueType *valueTypes[QVariant::UserType]; QHash<int, QQmlValueType *> userTypes; QMutex mutex; + + QQmlValueType invalidValueType; }; QQmlValueTypeFactoryImpl::QQmlValueTypeFactoryImpl() { - std::fill_n(valueTypes, int(QVariant::UserType), nullptr); + std::fill_n(valueTypes, int(QVariant::UserType), &invalidValueType); #if QT_CONFIG(qml_itemmodel) // See types wrapped in qqmlmodelindexvaluetype_p.h @@ -79,20 +81,18 @@ QQmlValueTypeFactoryImpl::QQmlValueTypeFactoryImpl() QQmlValueTypeFactoryImpl::~QQmlValueTypeFactoryImpl() { - qDeleteAll(valueTypes, valueTypes + QVariant::UserType); + for (QQmlValueType *type : valueTypes) { + if (type != &invalidValueType) + delete type; + } qDeleteAll(userTypes); } -bool QQmlValueTypeFactoryImpl::isValueType(int idx) +bool isInternalType(int idx) { - if (idx >= QMetaType::User) - return valueType(idx) != nullptr; - - if (idx < 0) - return false; - // Qt internal types switch (idx) { + case QMetaType::UnknownType: case QMetaType::QStringList: case QMetaType::QObjectStar: case QMetaType::VoidStar: @@ -101,12 +101,20 @@ bool QQmlValueTypeFactoryImpl::isValueType(int idx) case QMetaType::QLocale: case QMetaType::QImage: // scarce type, keep as QVariant case QMetaType::QPixmap: // scarce type, keep as QVariant - return false; - default: return true; + default: + return false; } } +bool QQmlValueTypeFactoryImpl::isValueType(int idx) +{ + if (idx < 0 || isInternalType(idx)) + return false; + + return valueType(idx) != nullptr; +} + const QMetaObject *QQmlValueTypeFactoryImpl::metaObjectForMetaType(int t) { switch (t) { @@ -168,15 +176,17 @@ QQmlValueType *QQmlValueTypeFactoryImpl::valueType(int idx) } QQmlValueType *rv = valueTypes[idx]; - if (!rv) { + if (rv == &invalidValueType) { // No need for mutex protection - the most we can lose is a valueType instance // TODO: Investigate the performance/memory characteristics of // removing the preallocated array - if (const QMetaObject *mo = metaObjectForMetaType(idx)) { - rv = new QQmlValueType(idx, mo); - valueTypes[idx] = rv; - } + if (isInternalType(idx)) + rv = valueTypes[idx] = nullptr; + else if (const QMetaObject *mo = metaObjectForMetaType(idx)) + rv = valueTypes[idx] = new QQmlValueType(idx, mo); + else + rv = valueTypes[idx] = nullptr; } return rv; @@ -208,6 +218,13 @@ void QQmlValueTypeFactory::registerValueTypes(const char *uri, int versionMajor, #endif } +QQmlValueType::QQmlValueType() : + _metaObject(nullptr), + gadgetPtr(nullptr), + metaType(QMetaType::UnknownType) +{ +} + QQmlValueType::QQmlValueType(int typeId, const QMetaObject *gadgetMetaObject) : gadgetPtr(QMetaType::create(typeId)) , metaType(typeId) @@ -225,7 +242,7 @@ QQmlValueType::QQmlValueType(int typeId, const QMetaObject *gadgetMetaObject) QQmlValueType::~QQmlValueType() { QObjectPrivate *op = QObjectPrivate::get(this); - Q_ASSERT(op->metaObject == this); + Q_ASSERT(op->metaObject == nullptr || op->metaObject == this); op->metaObject = nullptr; ::free(const_cast<QMetaObject *>(_metaObject)); metaType.destroy(gadgetPtr); diff --git a/src/qml/qml/qqmlvaluetype_p.h b/src/qml/qml/qqmlvaluetype_p.h index 75150b3f32..95ad81d045 100644 --- a/src/qml/qml/qqmlvaluetype_p.h +++ b/src/qml/qml/qqmlvaluetype_p.h @@ -68,6 +68,7 @@ QT_BEGIN_NAMESPACE class Q_QML_PRIVATE_EXPORT QQmlValueType : public QObject, public QAbstractDynamicMetaObject { public: + QQmlValueType(); QQmlValueType(int userType, const QMetaObject *metaObject); ~QQmlValueType() override; void read(QObject *, int); @@ -92,7 +93,7 @@ public: class Q_QML_PRIVATE_EXPORT QQmlValueTypeFactory { public: - static bool isValueType(int); + static bool isValueType(int idx); static QQmlValueType *valueType(int idx); static const QMetaObject *metaObjectForMetaType(int type); diff --git a/src/qml/qml/qqmlvaluetypewrapper.cpp b/src/qml/qml/qqmlvaluetypewrapper.cpp index cf6553d129..f23921497c 100644 --- a/src/qml/qml/qqmlvaluetypewrapper.cpp +++ b/src/qml/qml/qqmlvaluetypewrapper.cpp @@ -96,29 +96,29 @@ using namespace QV4; void Heap::QQmlValueTypeWrapper::destroy() { - if (gadgetPtr) { - valueType->metaType.destruct(gadgetPtr); - ::operator delete(gadgetPtr); + if (m_gadgetPtr) { + m_valueType->metaType.destruct(m_gadgetPtr); + ::operator delete(m_gadgetPtr); } - if (_propertyCache) - _propertyCache->release(); + if (m_propertyCache) + m_propertyCache->release(); Object::destroy(); } void Heap::QQmlValueTypeWrapper::setValue(const QVariant &value) const { - Q_ASSERT(valueType->metaType.id() == value.userType()); - if (gadgetPtr) - valueType->metaType.destruct(gadgetPtr); - if (!gadgetPtr) - gadgetPtr = ::operator new(valueType->metaType.sizeOf()); - valueType->metaType.construct(gadgetPtr, value.constData()); + Q_ASSERT(valueType()->metaType.id() == value.userType()); + if (auto *gadget = gadgetPtr()) + valueType()->metaType.destruct(gadget); + if (!gadgetPtr()) + setGadgetPtr(::operator new(valueType()->metaType.sizeOf())); + valueType()->metaType.construct(gadgetPtr(), value.constData()); } QVariant Heap::QQmlValueTypeWrapper::toVariant() const { - Q_ASSERT(gadgetPtr); - return QVariant(valueType->metaType.id(), gadgetPtr); + Q_ASSERT(gadgetPtr()); + return QVariant(valueType()->metaType.id(), gadgetPtr()); } @@ -146,13 +146,13 @@ bool QQmlValueTypeReference::readReferenceValue() const QQmlPropertyCache *cache = nullptr; if (const QMetaObject *mo = QQmlValueTypeFactory::metaObjectForMetaType(variantReferenceType)) cache = QJSEnginePrivate::get(engine())->cache(mo); - if (d()->gadgetPtr) { - d()->valueType->metaType.destruct(d()->gadgetPtr); - ::operator delete(d()->gadgetPtr); + if (d()->gadgetPtr()) { + d()->valueType()->metaType.destruct(d()->gadgetPtr()); + ::operator delete(d()->gadgetPtr()); } - d()->gadgetPtr =nullptr; + d()->setGadgetPtr(nullptr); d()->setPropertyCache(cache); - d()->valueType = QQmlValueTypeFactory::valueType(variantReferenceType); + d()->setValueType(QQmlValueTypeFactory::valueType(variantReferenceType)); if (!cache) return false; } else { @@ -161,12 +161,12 @@ bool QQmlValueTypeReference::readReferenceValue() const } d()->setValue(variantReferenceValue); } else { - if (!d()->gadgetPtr) { - d()->gadgetPtr = ::operator new(d()->valueType->metaType.sizeOf()); - d()->valueType->metaType.construct(d()->gadgetPtr, nullptr); + if (!d()->gadgetPtr()) { + d()->setGadgetPtr(::operator new(d()->valueType()->metaType.sizeOf())); + d()->valueType()->metaType.construct(d()->gadgetPtr(), nullptr); } // value-type reference - void *args[] = { d()->gadgetPtr, nullptr }; + void *args[] = { d()->gadgetPtr(), nullptr }; QMetaObject::metacall(d()->object, QMetaObject::ReadProperty, d()->property, args); } return true; @@ -192,8 +192,8 @@ ReturnedValue QQmlValueTypeWrapper::create(ExecutionEngine *engine, QObject *obj r->d()->object = object; r->d()->property = property; r->d()->setPropertyCache(QJSEnginePrivate::get(engine)->cache(metaObject)); - r->d()->valueType = QQmlValueTypeFactory::valueType(typeId); - r->d()->gadgetPtr = nullptr; + r->d()->setValueType(QQmlValueTypeFactory::valueType(typeId)); + r->d()->setGadgetPtr(nullptr); return r->asReturnedValue(); } @@ -204,8 +204,8 @@ ReturnedValue QQmlValueTypeWrapper::create(ExecutionEngine *engine, const QVaria Scoped<QQmlValueTypeWrapper> r(scope, engine->memoryManager->allocate<QQmlValueTypeWrapper>()); r->d()->setPropertyCache(QJSEnginePrivate::get(engine)->cache(metaObject)); - r->d()->valueType = QQmlValueTypeFactory::valueType(typeId); - r->d()->gadgetPtr = nullptr; + r->d()->setValueType(QQmlValueTypeFactory::valueType(typeId)); + r->d()->setGadgetPtr(nullptr); r->d()->setValue(value); return r->asReturnedValue(); } @@ -223,9 +223,9 @@ bool QQmlValueTypeWrapper::toGadget(void *data) const if (const QQmlValueTypeReference *ref = as<const QQmlValueTypeReference>()) if (!ref->readReferenceValue()) return false; - const int typeId = d()->valueType->metaType.id(); + const int typeId = d()->valueType()->metaType.id(); QMetaType::destruct(typeId, data); - QMetaType::construct(typeId, data, d()->gadgetPtr); + QMetaType::construct(typeId, data, d()->gadgetPtr()); return true; } @@ -307,7 +307,7 @@ bool QQmlValueTypeWrapper::isEqual(const QVariant& value) const int QQmlValueTypeWrapper::typeId() const { - return d()->valueType->metaType.id(); + return d()->valueType()->metaType.id(); } bool QQmlValueTypeWrapper::write(QObject *target, int propertyIndex) const @@ -315,10 +315,10 @@ bool QQmlValueTypeWrapper::write(QObject *target, int propertyIndex) const bool destructGadgetOnExit = false; Q_ALLOCA_DECLARE(void, gadget); if (const QQmlValueTypeReference *ref = as<const QQmlValueTypeReference>()) { - if (!d()->gadgetPtr) { - Q_ALLOCA_ASSIGN(void, gadget, d()->valueType->metaType.sizeOf()); - d()->gadgetPtr = gadget; - d()->valueType->metaType.construct(d()->gadgetPtr, nullptr); + if (!d()->gadgetPtr()) { + Q_ALLOCA_ASSIGN(void, gadget, d()->valueType()->metaType.sizeOf()); + d()->setGadgetPtr(gadget); + d()->valueType()->metaType.construct(d()->gadgetPtr(), nullptr); destructGadgetOnExit = true; } if (!ref->readReferenceValue()) @@ -327,12 +327,12 @@ bool QQmlValueTypeWrapper::write(QObject *target, int propertyIndex) const int flags = 0; int status = -1; - void *a[] = { d()->gadgetPtr, nullptr, &status, &flags }; + void *a[] = { d()->gadgetPtr(), nullptr, &status, &flags }; QMetaObject::metacall(target, QMetaObject::WriteProperty, propertyIndex, a); if (destructGadgetOnExit) { - d()->valueType->metaType.destruct(d()->gadgetPtr); - d()->gadgetPtr = nullptr; + d()->valueType()->metaType.destruct(d()->gadgetPtr()); + d()->setGadgetPtr(nullptr); } return true; } @@ -354,16 +354,16 @@ ReturnedValue QQmlValueTypeWrapper::method_toString(const FunctionObject *b, con // Prepare a buffer to pass to QMetaType::convert() QString convertResult; convertResult.~QString(); - if (QMetaType::convert(w->d()->gadgetPtr, w->d()->valueType->metaType.id(), &convertResult, QMetaType::QString)) { + if (QMetaType::convert(w->d()->gadgetPtr(), w->d()->valueType()->metaType.id(), &convertResult, QMetaType::QString)) { result = convertResult; } else { - result += QString::fromUtf8(QMetaType::typeName(w->d()->valueType->metaType.id())) + result += QString::fromUtf8(QMetaType::typeName(w->d()->valueType()->metaType.id())) + QLatin1Char('('); const QMetaObject *mo = w->d()->propertyCache()->metaObject(); const int propCount = mo->propertyCount(); for (int i = 0; i < propCount; ++i) { if (mo->property(i).isDesignable()) { - QVariant value = mo->property(i).readOnGadget(w->d()->gadgetPtr); + QVariant value = mo->property(i).readOnGadget(w->d()->gadgetPtr()); if (i > 0) result += QLatin1String(", "); result += value.toString(); @@ -387,7 +387,7 @@ Q_ALWAYS_INLINE static ReturnedValue getGadgetProperty(ExecutionEngine *engine, if (property->propType() == metatype) { \ cpptype v; \ void *args[] = { &v, nullptr }; \ - metaObject->d.static_metacall(reinterpret_cast<QObject*>(valueTypeWrapper->gadgetPtr), \ + metaObject->d.static_metacall(reinterpret_cast<QObject*>(valueTypeWrapper->gadgetPtr()), \ QMetaObject::ReadProperty, index, args); \ return QV4::Encode(constructor(v)); \ } @@ -412,7 +412,7 @@ Q_ALWAYS_INLINE static ReturnedValue getGadgetProperty(ExecutionEngine *engine, v = QVariant(property->propType(), static_cast<void *>(nullptr)); args[0] = v.data(); } - metaObject->d.static_metacall(reinterpret_cast<QObject*>(valueTypeWrapper->gadgetPtr), QMetaObject::ReadProperty, + metaObject->d.static_metacall(reinterpret_cast<QObject*>(valueTypeWrapper->gadgetPtr()), QMetaObject::ReadProperty, index, args); return engine->fromVariant(v); #undef VALUE_TYPE_LOAD @@ -604,7 +604,7 @@ bool QQmlValueTypeWrapper::virtualPut(Managed *m, PropertyKey id, const Value &v if (property.isEnumType() && (QMetaType::Type)v.type() == QMetaType::Double) v = v.toInt(); - void *gadget = r->d()->gadgetPtr; + void *gadget = r->d()->gadgetPtr(); property.writeOnGadget(gadget, v); @@ -620,7 +620,7 @@ bool QQmlValueTypeWrapper::virtualPut(Managed *m, PropertyKey id, const Value &v } else { int flags = 0; int status = -1; - void *a[] = { r->d()->gadgetPtr, nullptr, &status, &flags }; + void *a[] = { r->d()->gadgetPtr(), nullptr, &status, &flags }; QMetaObject::metacall(reference->d()->object, QMetaObject::WriteProperty, reference->d()->property, a); } } diff --git a/src/qml/qml/qqmlvaluetypewrapper_p.h b/src/qml/qml/qqmlvaluetypewrapper_p.h index baac129afa..60079aa623 100644 --- a/src/qml/qml/qqmlvaluetypewrapper_p.h +++ b/src/qml/qml/qqmlvaluetypewrapper_p.h @@ -69,22 +69,45 @@ namespace Heap { struct QQmlValueTypeWrapper : Object { void init() { Object::init(); } void destroy(); - QQmlPropertyCache *propertyCache() const { return _propertyCache; } + + QQmlPropertyCache *propertyCache() const { return m_propertyCache; } void setPropertyCache(QQmlPropertyCache *c) { if (c) c->addref(); - if (_propertyCache) - _propertyCache->release(); - _propertyCache = c; + if (m_propertyCache) + m_propertyCache->release(); + m_propertyCache = c; + } + + void setValueType(QQmlValueType *valueType) + { + Q_ASSERT(valueType != nullptr); + m_valueType = valueType; + } + + QQmlValueType *valueType() const + { + Q_ASSERT(m_valueType != nullptr); + return m_valueType; + } + + void setGadgetPtr(void *gadgetPtr) const + { + m_gadgetPtr = gadgetPtr; + } + + void *gadgetPtr() const + { + return m_gadgetPtr; } - mutable void *gadgetPtr; - QQmlValueType *valueType; void setValue(const QVariant &value) const; QVariant toVariant() const; private: - QQmlPropertyCache *_propertyCache; + mutable void *m_gadgetPtr; + QQmlValueType *m_valueType; + QQmlPropertyCache *m_propertyCache; }; } diff --git a/src/qml/qml/qqmlvmemetaobject.cpp b/src/qml/qml/qqmlvmemetaobject.cpp index b9d8fed243..a67ac7384d 100644 --- a/src/qml/qml/qqmlvmemetaobject.cpp +++ b/src/qml/qml/qqmlvmemetaobject.cpp @@ -1180,6 +1180,8 @@ bool QQmlVMEMetaObject::aliasTarget(int index, QObject **target, int *coreIndex, const int aliasId = index - propOffset() - compiledObject->nProperties; const QV4::CompiledData::Alias *aliasData = &compiledObject->aliasTable()[aliasId]; + while (aliasData->aliasToLocalAlias) + aliasData = &compiledObject->aliasTable()[aliasData->localAliasIndex]; *target = ctxt->idValues[aliasData->targetObjectId].data(); if (!*target) return false; diff --git a/src/quick/items/qquickloader.cpp b/src/quick/items/qquickloader.cpp index d0e29c204e..819a3a73e3 100644 --- a/src/quick/items/qquickloader.cpp +++ b/src/quick/items/qquickloader.cpp @@ -45,6 +45,7 @@ #include <private/qqmlglobal_p.h> #include <private/qqmlcomponent_p.h> +#include <private/qqmlincubator_p.h> QT_BEGIN_NAMESPACE @@ -664,7 +665,8 @@ void QQuickLoaderPrivate::setInitialState(QObject *obj) QV4::Scope scope(v4); QV4::ScopedValue ipv(scope, initialPropertyValues.value()); QV4::Scoped<QV4::QmlContext> qmlContext(scope, qmlCallingContext.value()); - d->initializeObjectWithInitialProperties(qmlContext, ipv, obj); + auto incubatorPriv = QQmlIncubatorPrivate::get(incubator); + d->initializeObjectWithInitialProperties(qmlContext, ipv, obj, incubatorPriv->requiredProperties()); } void QQuickLoaderIncubator::statusChanged(Status status) diff --git a/tests/auto/qml/qmlcachegen/tst_qmlcachegen.cpp b/tests/auto/qml/qmlcachegen/tst_qmlcachegen.cpp index 102acf73d6..04f45bb902 100644 --- a/tests/auto/qml/qmlcachegen/tst_qmlcachegen.cpp +++ b/tests/auto/qml/qmlcachegen/tst_qmlcachegen.cpp @@ -360,7 +360,7 @@ static QQmlPrivate::CachedQmlUnit *temporaryModifiedCachedUnit = nullptr; void tst_qmlcachegen::versionChecksForAheadOfTimeUnits() { QVERIFY(QFile::exists(":/data/versionchecks.qml")); - QCOMPARE(QFileInfo(":/data/versionchecks.qml").size(), 0); + QVERIFY(QFileInfo(":/data/versionchecks.qml").size() > 0); Q_ASSERT(!temporaryModifiedCachedUnit); QQmlMetaType::CachedUnitLookupError error = QQmlMetaType::CachedUnitLookupError::NoError; @@ -387,12 +387,8 @@ void tst_qmlcachegen::versionChecksForAheadOfTimeUnits() { QQmlEngine engine; - QQmlComponent component(&engine, QUrl("qrc:/data/versionchecks.qml")); - QCOMPARE(component.status(), QQmlComponent::Error); - QCOMPARE(component.errorString(), - QString("qrc:/data/versionchecks.qml:-1 File was compiled ahead of time with an " - "incompatible version of Qt and the original file cannot be found. Please " - "recompile\n")); + CleanlyLoadingComponent component(&engine, QUrl("qrc:/data/versionchecks.qml")); + QCOMPARE(component.status(), QQmlComponent::Ready); } Q_ASSERT(temporaryModifiedCachedUnit); @@ -414,7 +410,7 @@ void tst_qmlcachegen::workerScripts() { QVERIFY(QFile::exists(":/workerscripts/data/worker.js")); QVERIFY(QFile::exists(":/workerscripts/data/worker.qml")); - QCOMPARE(QFileInfo(":/workerscripts/data/worker.js").size(), 0); + QVERIFY(QFileInfo(":/workerscripts/data/worker.js").size() > 0); QQmlEngine engine; CleanlyLoadingComponent component(&engine, QUrl("qrc:///workerscripts/data/worker.qml")); @@ -503,7 +499,7 @@ void tst_qmlcachegen::trickyPaths() { QFETCH(QString, filePath); QVERIFY2(QFile::exists(filePath), qPrintable(filePath)); - QCOMPARE(QFileInfo(filePath).size(), 0); + QVERIFY(QFileInfo(filePath).size() > 0); QQmlEngine engine; QQmlComponent component(&engine, QUrl("qrc" + filePath)); QScopedPointer<QObject> obj(component.create()); @@ -584,7 +580,7 @@ void tst_qmlcachegen::moduleScriptImport() QTRY_VERIFY(obj->property("ok").toBool()); QVERIFY(QFile::exists(":/data/script.mjs")); - QCOMPARE(QFileInfo(":/data/script.mjs").size(), 0); + QVERIFY(QFileInfo(":/data/script.mjs").size() > 0); { auto componentPrivate = QQmlComponentPrivate::get(&component); @@ -617,7 +613,7 @@ void tst_qmlcachegen::enums() void tst_qmlcachegen::sourceFileIndices() { QVERIFY(QFile::exists(":/data/versionchecks.qml")); - QCOMPARE(QFileInfo(":/data/versionchecks.qml").size(), 0); + QVERIFY(QFileInfo(":/data/versionchecks.qml").size() > 0); QQmlMetaType::CachedUnitLookupError error = QQmlMetaType::CachedUnitLookupError::NoError; const QV4::CompiledData::Unit *unitFromResources = QQmlMetaType::findCachedCompilationUnit( diff --git a/tests/auto/qml/qmllint/data/Form.ui.qml b/tests/auto/qml/qmllint/data/Form.ui.qml new file mode 100644 index 0000000000..459c82afbb --- /dev/null +++ b/tests/auto/qml/qmllint/data/Form.ui.qml @@ -0,0 +1,4 @@ +import QtQuick 2.0 + +Item { +} diff --git a/tests/auto/qml/qmllint/data/FormUser.qml b/tests/auto/qml/qmllint/data/FormUser.qml new file mode 100644 index 0000000000..ea3621586f --- /dev/null +++ b/tests/auto/qml/qmllint/data/FormUser.qml @@ -0,0 +1,7 @@ +import QtQuick 2.0 + +Form { + x: 12 + y: 13 + objectName: "horst" +} diff --git a/tests/auto/qml/qmllint/data/ImportWithPrefix.qml b/tests/auto/qml/qmllint/data/ImportWithPrefix.qml new file mode 100644 index 0000000000..6d070da21a --- /dev/null +++ b/tests/auto/qml/qmllint/data/ImportWithPrefix.qml @@ -0,0 +1,5 @@ +import "." as MyStuff + +MyStuff.Simple { + property bool something: contains(Qt.point(12, 34)) +} diff --git a/tests/auto/qml/qmllint/data/MethodInItem.qml b/tests/auto/qml/qmllint/data/MethodInItem.qml new file mode 100644 index 0000000000..dbdaf8bcc1 --- /dev/null +++ b/tests/auto/qml/qmllint/data/MethodInItem.qml @@ -0,0 +1,5 @@ +import QtQml 2.0 + +QtObject { + function doThings() { console.log("things") } +} diff --git a/tests/auto/qml/qmllint/data/MethodInScope.qml b/tests/auto/qml/qmllint/data/MethodInScope.qml new file mode 100644 index 0000000000..7ba0829f61 --- /dev/null +++ b/tests/auto/qml/qmllint/data/MethodInScope.qml @@ -0,0 +1,5 @@ +import QtQml 2.0 + +MethodInItem { + Component.onCompleted: doThings() +} diff --git a/tests/auto/qml/qmllint/data/UnmatchedSignalHandler.qml b/tests/auto/qml/qmllint/data/UnmatchedSignalHandler.qml new file mode 100644 index 0000000000..064444e182 --- /dev/null +++ b/tests/auto/qml/qmllint/data/UnmatchedSignalHandler.qml @@ -0,0 +1,15 @@ +import QtQuick 2.12 + +Item { + width: 640 + height: 480 + + MouseArea { + anchors.fill: parent + onClicked: console.log("okok") + + Connections { + onClicked: console.log(mouse.x) + } + } +} diff --git a/tests/auto/qml/qmllint/tst_qmllint.cpp b/tests/auto/qml/qmllint/tst_qmllint.cpp index 582f146dca..2d225aebd3 100644 --- a/tests/auto/qml/qmllint/tst_qmllint.cpp +++ b/tests/auto/qml/qmllint/tst_qmllint.cpp @@ -44,7 +44,14 @@ private Q_SLOTS: void testUnqualified_data(); void testUnqualifiedNoSpuriousParentWarning(); void catchIdentifierNoFalsePositive(); + void testUnmatchedSignalHandler(); + void uiQml(); + void methodInScope(); + void importWithPrefix(); + private: + QString runQmllint(const QString &fileToLint, bool shouldSucceed); + QString m_qmllintPath; }; @@ -83,15 +90,8 @@ void TestQmllint::testUnqualified() QFETCH(QString, warningMessage); QFETCH(int, warningLine); QFETCH(int, warningColumn); - QStringList args; - args << QStringLiteral("-U") << testFile(filename) << QStringLiteral("-I") << qmlImportDir; - QProcess process; - process.start(m_qmllintPath, args); - QVERIFY(process.waitForFinished()); - QVERIFY(process.exitStatus() == QProcess::NormalExit); - QVERIFY(process.exitCode()); - QString output = process.readAllStandardError(); + const QString output = runQmllint(filename, false); QVERIFY(output.contains(QString::asprintf("Warning: unqualified access at %d:%d", warningLine, warningColumn))); QVERIFY(output.contains(warningMessage)); } @@ -122,41 +122,39 @@ void TestQmllint::testUnqualified_data() void TestQmllint::testUnqualifiedNoSpuriousParentWarning() { - auto qmlImportDir = QLibraryInfo::location(QLibraryInfo::Qml2ImportsPath); - { - QString filename = testFile("spuriousParentWarning.qml"); - QStringList args; - args << QStringLiteral("-U") << filename << QStringLiteral("-I") << qmlImportDir; - QProcess process; - process.start(m_qmllintPath, args); - QVERIFY(process.waitForFinished()); - QVERIFY(process.exitStatus() == QProcess::NormalExit); - QVERIFY(process.exitCode() == 0); - } - { - QString filename = testFile("nonSpuriousParentWarning.qml"); - QStringList args; - args << QStringLiteral("-U") << filename << QStringLiteral("-I") << qmlImportDir; - QProcess process; - process.start(m_qmllintPath, args); - QVERIFY(process.waitForFinished()); - QVERIFY(process.exitStatus() == QProcess::NormalExit); - QVERIFY(process.exitCode()); - } + runQmllint("spuriousParentWarning.qml", true); + runQmllint("nonSpuriousParentWarning.qml", false); } void TestQmllint::catchIdentifierNoFalsePositive() { - auto qmlImportDir = QLibraryInfo::location(QLibraryInfo::Qml2ImportsPath); - QString filename = QLatin1String("catchIdentifierNoWarning.qml"); - filename.prepend(QStringLiteral("data/")); - QStringList args; - args << QStringLiteral("-U") << filename << QStringLiteral("-I") << qmlImportDir; - QProcess process; - process.start(m_qmllintPath, args); - QVERIFY(process.waitForFinished()); - QVERIFY(process.exitStatus() == QProcess::NormalExit); - QVERIFY(process.exitCode() == 0); + runQmllint("catchIdentifierNoWarning.qml", true); +} + +void TestQmllint::testUnmatchedSignalHandler() +{ + const QString output = runQmllint("UnmatchedSignalHandler.qml", false); + QVERIFY(output.contains(QString::asprintf( + "Warning: no matching signal found for handler \"onClicked\" at %d:%d", 12, 13))); + QVERIFY(!output.contains(QStringLiteral("onMouseXChanged"))); +} + +void TestQmllint::uiQml() +{ + const QString output = runQmllint("FormUser.qml", true); + QVERIFY(output.isEmpty()); +} + +void TestQmllint::methodInScope() +{ + const QString output = runQmllint("MethodInScope.qml", true); + QVERIFY(output.isEmpty()); +} + +void TestQmllint::importWithPrefix() +{ + const QString output = runQmllint("ImportWithPrefix.qml", true); + QVERIFY(output.isEmpty()); } void TestQmllint::test() @@ -170,5 +168,24 @@ void TestQmllint::test() QCOMPARE(success, isValid); } +QString TestQmllint::runQmllint(const QString &fileToLint, bool shouldSucceed) +{ + auto qmlImportDir = QLibraryInfo::location(QLibraryInfo::Qml2ImportsPath); + QStringList args; + args << QStringLiteral("-U") << testFile(fileToLint) + << QStringLiteral("-I") << qmlImportDir; + QProcess process; + process.start(m_qmllintPath, args); + [&]() { + QVERIFY(process.waitForFinished()); + QCOMPARE(process.exitStatus(), QProcess::NormalExit); + if (shouldSucceed) + QCOMPARE(process.exitCode(), 0); + else + QVERIFY(process.exitCode() != 0); + }(); + return process.readAllStandardError(); +} + QTEST_MAIN(TestQmllint) #include "tst_qmllint.moc" diff --git a/tests/auto/qml/qmlmin/tst_qmlmin.cpp b/tests/auto/qml/qmlmin/tst_qmlmin.cpp index cae833cd60..79a73299a4 100644 --- a/tests/auto/qml/qmlmin/tst_qmlmin.cpp +++ b/tests/auto/qml/qmlmin/tst_qmlmin.cpp @@ -130,6 +130,8 @@ void tst_qmlmin::initTestCase() invalidFiles << "tests/auto/qml/qjsengine/script/com/trolltech/syntaxerror/__init__.js"; invalidFiles << "tests/auto/qml/debugger/qqmlpreview/data/broken.qml"; invalidFiles << "tests/auto/qml/qqmllanguage/data/fuzzed.2.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/qqmlcomponent/data/AliasToSubcomponentRequiredBase.qml b/tests/auto/qml/qqmlcomponent/data/AliasToSubcomponentRequiredBase.qml new file mode 100644 index 0000000000..f693cb4c2e --- /dev/null +++ b/tests/auto/qml/qqmlcomponent/data/AliasToSubcomponentRequiredBase.qml @@ -0,0 +1,10 @@ +import QtQuick 2.13 + +Item { + property alias i_alias: sub.i + + Item { + id: sub + required property int i + } +} diff --git a/tests/auto/qml/qqmlcomponent/data/BaseWithRequired.qml b/tests/auto/qml/qqmlcomponent/data/BaseWithRequired.qml new file mode 100644 index 0000000000..0ce61c8d9d --- /dev/null +++ b/tests/auto/qml/qqmlcomponent/data/BaseWithRequired.qml @@ -0,0 +1,6 @@ +import QtQuick 2.14 + +Item { + id: base + required property int i +} diff --git a/tests/auto/qml/qqmlcomponent/data/aliasToSubcomponentNotSet.qml b/tests/auto/qml/qqmlcomponent/data/aliasToSubcomponentNotSet.qml new file mode 100644 index 0000000000..527eb417e5 --- /dev/null +++ b/tests/auto/qml/qqmlcomponent/data/aliasToSubcomponentNotSet.qml @@ -0,0 +1,4 @@ +import QtQuick 2.13 + +AliasToSubcomponentRequiredBase { +} diff --git a/tests/auto/qml/qqmlcomponent/data/createdFromQml.qml b/tests/auto/qml/qqmlcomponent/data/createdFromQml.qml new file mode 100644 index 0000000000..60a2077606 --- /dev/null +++ b/tests/auto/qml/qqmlcomponent/data/createdFromQml.qml @@ -0,0 +1,11 @@ +import QtQuick 2.14 + +Item { + id: root + property Item it + Component.onCompleted: function() { + let component = Qt.createComponent("requiredNotSet.qml", Component.PreferSynchronous, root) + console.assert(component.status == Component.Ready) + it = component.createObject(component, {i: 42}) + } +} diff --git a/tests/auto/qml/qqmlcomponent/data/createdFromQmlFail.qml b/tests/auto/qml/qqmlcomponent/data/createdFromQmlFail.qml new file mode 100644 index 0000000000..e09ddcccc1 --- /dev/null +++ b/tests/auto/qml/qqmlcomponent/data/createdFromQmlFail.qml @@ -0,0 +1,11 @@ +import QtQuick 2.14 + +Item { + id: root + property Item it + Component.onCompleted: function() { + let component = Qt.createComponent("requiredNotSet.qml", Component.PreferSynchronous, root) + console.assert(component.status == Component.Ready) + root.it = component.createObject(component) + } +} diff --git a/tests/auto/qml/qqmlcomponent/data/requiredNotSet.qml b/tests/auto/qml/qqmlcomponent/data/requiredNotSet.qml new file mode 100644 index 0000000000..c0b5d695f1 --- /dev/null +++ b/tests/auto/qml/qqmlcomponent/data/requiredNotSet.qml @@ -0,0 +1,5 @@ +import QtQuick 2.14 + +Item { + required property int i +} diff --git a/tests/auto/qml/qqmlcomponent/data/requiredSetInSameFile.qml b/tests/auto/qml/qqmlcomponent/data/requiredSetInSameFile.qml new file mode 100644 index 0000000000..76dfcd87e5 --- /dev/null +++ b/tests/auto/qml/qqmlcomponent/data/requiredSetInSameFile.qml @@ -0,0 +1,6 @@ +import QtQuick 2.14 + +Item { + required property int i + i: 42 +} diff --git a/tests/auto/qml/qqmlcomponent/data/requiredSetLater.qml b/tests/auto/qml/qqmlcomponent/data/requiredSetLater.qml new file mode 100644 index 0000000000..3b7811e453 --- /dev/null +++ b/tests/auto/qml/qqmlcomponent/data/requiredSetLater.qml @@ -0,0 +1,5 @@ +import QtQuick 2.14 + +BaseWithRequired { + i: 13 +} diff --git a/tests/auto/qml/qqmlcomponent/data/requiredSetViaAliasAfterSameFile.qml b/tests/auto/qml/qqmlcomponent/data/requiredSetViaAliasAfterSameFile.qml new file mode 100644 index 0000000000..163616bc8a --- /dev/null +++ b/tests/auto/qml/qqmlcomponent/data/requiredSetViaAliasAfterSameFile.qml @@ -0,0 +1,8 @@ +import QtQuick 2.14 + +Item { + id: withAlias + j: 42 + required property int i + property alias j: withAlias.i +} diff --git a/tests/auto/qml/qqmlcomponent/data/requiredSetViaAliasBeforeSameFile.qml b/tests/auto/qml/qqmlcomponent/data/requiredSetViaAliasBeforeSameFile.qml new file mode 100644 index 0000000000..e59bccf379 --- /dev/null +++ b/tests/auto/qml/qqmlcomponent/data/requiredSetViaAliasBeforeSameFile.qml @@ -0,0 +1,8 @@ +import QtQuick 2.14 + +Item { + id: withAlias + required property int i + j: 42 + property alias j: withAlias.i +} diff --git a/tests/auto/qml/qqmlcomponent/data/requiredSetViaAliasParentFile.qml b/tests/auto/qml/qqmlcomponent/data/requiredSetViaAliasParentFile.qml new file mode 100644 index 0000000000..0bc2f8a7df --- /dev/null +++ b/tests/auto/qml/qqmlcomponent/data/requiredSetViaAliasParentFile.qml @@ -0,0 +1,7 @@ +import QtQuick 2.14 + +BaseWithRequired { + id: withAlias + property alias j: withAlias.i + j: 42 +} diff --git a/tests/auto/qml/qqmlcomponent/data/requiredSetViaChainedAlias.qml b/tests/auto/qml/qqmlcomponent/data/requiredSetViaChainedAlias.qml new file mode 100644 index 0000000000..45cf02d3d8 --- /dev/null +++ b/tests/auto/qml/qqmlcomponent/data/requiredSetViaChainedAlias.qml @@ -0,0 +1,9 @@ +import QtQml 2.12 + +QtObject { + id: stuff + required property int x; + property alias y: stuff.x + property alias z: stuff.y + z: 5 +} diff --git a/tests/auto/qml/qqmlcomponent/data/setViaAliasToSubcomponent.qml b/tests/auto/qml/qqmlcomponent/data/setViaAliasToSubcomponent.qml new file mode 100644 index 0000000000..2dc182ea82 --- /dev/null +++ b/tests/auto/qml/qqmlcomponent/data/setViaAliasToSubcomponent.qml @@ -0,0 +1,5 @@ +import QtQuick 2.13 + +AliasToSubcomponentRequiredBase { + i_alias: 42 +} diff --git a/tests/auto/qml/qqmlcomponent/data/shadowing.qml b/tests/auto/qml/qqmlcomponent/data/shadowing.qml new file mode 100644 index 0000000000..4d119d8884 --- /dev/null +++ b/tests/auto/qml/qqmlcomponent/data/shadowing.qml @@ -0,0 +1,5 @@ +import QtQuick 2.14 + +BaseWithRequired { + property int i: 13 +} diff --git a/tests/auto/qml/qqmlcomponent/tst_qqmlcomponent.cpp b/tests/auto/qml/qqmlcomponent/tst_qqmlcomponent.cpp index 79ec507388..b6cc7fd866 100644 --- a/tests/auto/qml/qqmlcomponent/tst_qqmlcomponent.cpp +++ b/tests/auto/qml/qqmlcomponent/tst_qqmlcomponent.cpp @@ -121,6 +121,9 @@ private slots: void relativeUrl_data(); void relativeUrl(); void setDataNoEngineNoSegfault(); + void testRequiredProperties_data(); + void testRequiredProperties(); + void testRequiredPropertiesFromQml(); void testSetInitialProperties(); private: @@ -668,35 +671,80 @@ void tst_qqmlcomponent::setDataNoEngineNoSegfault() QVERIFY(!c); } -void tst_qqmlcomponent::testSetInitialProperties() +void tst_qqmlcomponent::testRequiredProperties_data() +{ + QTest::addColumn<QUrl>("testFile"); + QTest::addColumn<bool>("shouldSucceed"); + QTest::addColumn<QString>("errorMsg"); + + QTest::addRow("requiredSetViaChainedAlias") << testFileUrl("requiredSetViaChainedAlias.qml") << true << ""; + QTest::addRow("requiredNotSet") << testFileUrl("requiredNotSet.qml") << false << "Required property i was not initialized"; + QTest::addRow("requiredSetInSameFile") << testFileUrl("requiredSetInSameFile.qml") << true << ""; + QTest::addRow("requiredSetViaAlias1") << testFileUrl("requiredSetViaAliasBeforeSameFile.qml") << true << ""; + QTest::addRow("requiredSetViaAlias2") << testFileUrl("requiredSetViaAliasAfterSameFile.qml") << true << ""; + QTest::addRow("requiredSetViaAlias3") << testFileUrl("requiredSetViaAliasParentFile.qml") << true << ""; + QTest::addRow("shadowing") << testFileUrl("shadowing.qml") << false << "Required property i was not initialized"; + QTest::addRow("setLater") << testFileUrl("requiredSetLater.qml") << true << ""; + QTest::addRow("setViaAliasToSubcomponent") << testFileUrl("setViaAliasToSubcomponent.qml") << true << ""; + QTest::addRow("aliasToSubcomponentNotSet") << testFileUrl("aliasToSubcomponentNotSet.qml") << false << "It can be set via the alias property i_alias"; +} + + +void tst_qqmlcomponent::testRequiredProperties() +{ + QQmlEngine eng; + using QScopedObjPointer = QScopedPointer<QObject>; + QFETCH(QUrl, testFile); + QFETCH(bool, shouldSucceed); + QQmlComponent comp(&eng); + comp.loadUrl(testFile); + QScopedObjPointer obj {comp.create()}; + if (shouldSucceed) + QVERIFY(obj); + else { + QVERIFY(!obj); + QFETCH(QString, errorMsg); + QVERIFY(comp.errorString().contains(errorMsg)); + } +} + +void tst_qqmlcomponent::testRequiredPropertiesFromQml() { QQmlEngine eng; { - // JSON based initialization QQmlComponent comp(&eng); - comp.loadUrl(testFileUrl("allJSONTypes.qml")); - QScopedPointer<QObject> obj { comp.beginCreate(eng.rootContext()) }; + comp.loadUrl(testFileUrl("createdFromQml.qml")); + QScopedPointer<QObject> obj { comp.create() }; QVERIFY(obj); - comp.setInitialProperties(obj.get(), QVariantMap { - {QLatin1String("i"), 42}, - {QLatin1String("b"), true}, - {QLatin1String("d"), 3.1416}, - {QLatin1String("s"), QLatin1String("hello world")}, - {QLatin1String("nothing"), QVariant::fromValue(nullptr)} - }); - comp.completeCreate(); - if (!comp.errors().empty()) - qDebug() << comp.errorString() << comp.errors(); - QVERIFY(comp.errors().empty()); - QCOMPARE(obj->property("i"), 42); - QCOMPARE(obj->property("b"), true); - QCOMPARE(obj->property("d"), 3.1416); - QCOMPARE(obj->property("s"), QLatin1String("hello world")); - QCOMPARE(obj->property("nothing"), QVariant::fromValue(nullptr)); + auto root = qvariant_cast<QQuickItem*>(obj->property("it")); + QVERIFY(root); + QCOMPARE(root->property("i").toInt(), 42); } { - // QVariant + QTest::ignoreMessage(QtMsgType::QtWarningMsg, QRegularExpression(".*requiredNotSet.qml:4:5: Required property i was not initialized")); QQmlComponent comp(&eng); + comp.loadUrl(testFileUrl("createdFromQmlFail.qml")); + QScopedPointer<QObject> obj { comp.create() }; + QVERIFY(obj); + QCOMPARE(qvariant_cast<QQuickItem *>(obj->property("it")), nullptr); + } +} + +struct ComponentWithPublicSetInitial : QQmlComponent +{ + using QQmlComponent::QQmlComponent; + void setInitialProperties(QObject *o, QVariantMap map) + { + QQmlComponent::setInitialProperties(o, map); + } +}; + +void tst_qqmlcomponent::testSetInitialProperties() +{ + QQmlEngine eng; + { + // QVariant + ComponentWithPublicSetInitial comp(&eng); comp.loadUrl(testFileUrl("variantBasedInitialization.qml")); QScopedPointer<QObject> obj { comp.beginCreate(eng.rootContext()) }; QVERIFY(obj); @@ -728,8 +776,6 @@ void tst_qqmlcomponent::testSetInitialProperties() }); #undef ASJSON comp.completeCreate(); - if (!comp.errors().empty()) - qDebug() << comp.errorString() << comp.errors(); QVERIFY(comp.errors().empty()); QCOMPARE(obj->property("i"), 42); QCOMPARE(obj->property("b"), true); @@ -751,13 +797,20 @@ void tst_qqmlcomponent::testSetInitialProperties() } { + // createWithInitialProperties convenience function + QQmlComponent comp(&eng); + comp.loadUrl(testFileUrl("requiredNotSet.qml")); + QScopedPointer<QObject> obj {comp.createWithInitialProperties( QVariantMap { {QLatin1String("i"), QJsonValue{42}} })}; + QVERIFY(obj); + QCOMPARE(obj->property("i"), 42); + } + { // createWithInitialProperties: setting a nonexistent property QQmlComponent comp(&eng); comp.loadUrl(testFileUrl("allJSONTypes.qml")); QScopedPointer<QObject> obj { comp.createWithInitialProperties(QVariantMap { {"notThePropertiesYoureLookingFor", 42} }) }; - qDebug() << comp.errorString(); QVERIFY(obj); QVERIFY(comp.errorString().contains("Could not set property notThePropertiesYoureLookingFor")); } diff --git a/tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp b/tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp index 269d90c891..cfdb15304d 100644 --- a/tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp +++ b/tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp @@ -6357,6 +6357,8 @@ void tst_qqmlecmascript::topLevelGeneratorFunction() QQmlComponent component(&engine, testFileUrl("generatorFunction.qml")); QScopedPointer<QObject> o {component.create()}; + if (!o) + qDebug() << component.errorString(); QVERIFY(o != nullptr); // check that generator works correctly in QML diff --git a/tests/auto/qml/qqmlincubator/data/requiredProperty.qml b/tests/auto/qml/qqmlincubator/data/requiredProperty.qml new file mode 100644 index 0000000000..9e355dce72 --- /dev/null +++ b/tests/auto/qml/qqmlincubator/data/requiredProperty.qml @@ -0,0 +1,5 @@ +import QtQuick 2.12 + +Item { + required property int requiredProperty +} diff --git a/tests/auto/qml/qqmlincubator/tst_qqmlincubator.cpp b/tests/auto/qml/qqmlincubator/tst_qqmlincubator.cpp index 8e25079703..756b3b1d7c 100644 --- a/tests/auto/qml/qqmlincubator/tst_qqmlincubator.cpp +++ b/tests/auto/qml/qqmlincubator/tst_qqmlincubator.cpp @@ -70,6 +70,7 @@ private slots: void selfDelete(); void contextDelete(); void garbageCollection(); + void requiredProperties(); private: QQmlIncubationController controller; @@ -1174,6 +1175,44 @@ void tst_qqmlincubator::garbageCollection() QVERIFY(weakIncubatorRef.isNullOrUndefined()); } +void tst_qqmlincubator::requiredProperties() +{ + { + QQmlComponent component(&engine, testFileUrl("requiredProperty.qml")); + QVERIFY(component.isReady()); + // forceCompletion immediately after creating an asynchronous object completes it + QQmlIncubator incubator; + incubator.setInitialProperties({{"requiredProperty", 42}}); + QVERIFY(incubator.isNull()); + component.create(incubator); + QVERIFY(incubator.isLoading()); + + incubator.forceCompletion(); + + QVERIFY(incubator.isReady()); + QVERIFY(incubator.object() != nullptr); + QCOMPARE(incubator.object()->property("requiredProperty").toInt(), 42); + + delete incubator.object(); + } + { + QQmlComponent component(&engine, testFileUrl("requiredProperty.qml")); + QVERIFY(component.isReady()); + // forceCompletion immediately after creating an asynchronous object completes it + QQmlIncubator incubator; + QVERIFY(incubator.isNull()); + component.create(incubator); + QVERIFY(incubator.isLoading()); + + incubator.forceCompletion(); + + QVERIFY(incubator.isError()); + auto error = incubator.errors().first(); + QVERIFY(error.description().contains(QLatin1String("Required property requiredProperty was not initialized"))); + QVERIFY(incubator.object() == nullptr); + } +} + QTEST_MAIN(tst_qqmlincubator) #include "tst_qqmlincubator.moc" diff --git a/tests/auto/qml/qqmllanguage/data/fakeDotProperty.errors.txt b/tests/auto/qml/qqmllanguage/data/fakeDotProperty.errors.txt index 30748234bc..5a144f2db5 100644 --- a/tests/auto/qml/qqmllanguage/data/fakeDotProperty.errors.txt +++ b/tests/auto/qml/qqmllanguage/data/fakeDotProperty.errors.txt @@ -1 +1 @@ -3:5:Invalid grouped property access +3:5:Invalid grouped property access: Property "value" with primitive type "int". diff --git a/tests/auto/qml/qqmllanguage/data/invalidGroupedProperty.2.errors.txt b/tests/auto/qml/qqmllanguage/data/invalidGroupedProperty.2.errors.txt index 810fd31b41..5deec4ccf9 100644 --- a/tests/auto/qml/qqmllanguage/data/invalidGroupedProperty.2.errors.txt +++ b/tests/auto/qml/qqmllanguage/data/invalidGroupedProperty.2.errors.txt @@ -1 +1 @@ -5:5:Invalid grouped property access +5:5:Invalid grouped property access: Property "o" with primitive type "int". diff --git a/tests/auto/qml/qqmllanguage/data/objectValueTypeProperty.errors.txt b/tests/auto/qml/qqmllanguage/data/objectValueTypeProperty.errors.txt index 945dacf8ab..043f714636 100644 --- a/tests/auto/qml/qqmllanguage/data/objectValueTypeProperty.errors.txt +++ b/tests/auto/qml/qqmllanguage/data/objectValueTypeProperty.errors.txt @@ -1 +1 @@ -4:18:Can not assign value of type "int" to property "x", expecting an object +4:18:Can not assign value of type "MyTypeObject" to property "x", expecting "int" diff --git a/tests/auto/qml/qqmllanguage/data/requiredProperties.1.qml b/tests/auto/qml/qqmllanguage/data/requiredProperties.1.qml new file mode 100644 index 0000000000..dac43c6d88 --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/requiredProperties.1.qml @@ -0,0 +1,8 @@ +import QtQuick 2.13 +Item { + property var required: 32 // required is still allowed as an identifier for properties + function f(required) { // for javascript + required = required + required; + console.log(required); + } +} diff --git a/tests/auto/qml/qqmllanguage/data/requiredProperties.2.qml b/tests/auto/qml/qqmllanguage/data/requiredProperties.2.qml new file mode 100644 index 0000000000..4c12c7b602 --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/requiredProperties.2.qml @@ -0,0 +1,4 @@ +import QtQuick 2.13 +Item { + required property int test: 42 // cannot specify value for required property +} 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..534322215f --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/requiredProperties.3.qml @@ -0,0 +1,4 @@ +import QtQuick 2.13 +Item { + default required property int test // cannot have required default property +} diff --git a/tests/auto/qml/qqmllanguage/testtypes.cpp b/tests/auto/qml/qqmllanguage/testtypes.cpp index 6956533196..226a78b960 100644 --- a/tests/auto/qml/qqmllanguage/testtypes.cpp +++ b/tests/auto/qml/qqmllanguage/testtypes.cpp @@ -57,7 +57,7 @@ void registerTypes() qmlRegisterType<MyNamespace::MySecondNamespacedType>("Test",1,0,"MySecondNamespacedType"); qmlRegisterUncreatableMetaObject(MyNamespace::staticMetaObject, "Test", 1, 0, "MyNamespace", "Access to enums & flags only"); qmlRegisterType<MyParserStatus>("Test",1,0,"MyParserStatus"); - qmlRegisterType<MyGroupedObject>(); + qmlRegisterAnonymousType<MyGroupedObject>("Test", 1); qmlRegisterType<MyRevisionedClass>("Test",1,0,"MyRevisionedClass"); qmlRegisterType<MyRevisionedClass,1>("Test",1,1,"MyRevisionedClass"); qmlRegisterType<MyRevisionedIllegalOverload>("Test",1,0,"MyRevisionedIllegalOverload"); diff --git a/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp b/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp index 8adacd8829..674da19afc 100644 --- a/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp +++ b/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp @@ -132,6 +132,7 @@ private slots: void autoComponentCreation(); void autoComponentCreationInGroupProperty(); void propertyValueSource(); + void requiredProperty(); void attachedProperties(); void dynamicObjects(); void customVariantTypes(); @@ -1634,6 +1635,25 @@ void tst_qqmllanguage::propertyValueSource() } } +void tst_qqmllanguage::requiredProperty() +{ + QQmlEngine engine; + { + QQmlComponent component(&engine, testFileUrl("requiredProperties.1.qml")); + VERIFY_ERRORS(0); + QScopedPointer<QObject> object(component.create()); + QVERIFY(object); + } + { + QQmlComponent component(&engine, testFileUrl("requiredProperties.2.qml")); + QVERIFY(!component.errors().empty()); + } + { + QQmlComponent component(&engine, testFileUrl("requiredProperties.3.qml")); + QVERIFY(!component.errors().empty()); + } +} + void tst_qqmllanguage::attachedProperties() { QQmlComponent component(&engine, testFileUrl("attachedProperties.qml")); diff --git a/tests/auto/qml/qqmllistreference/tst_qqmllistreference.cpp b/tests/auto/qml/qqmllistreference/tst_qqmllistreference.cpp index 199f7bc7e4..8efaedf5b5 100644 --- a/tests/auto/qml/qqmllistreference/tst_qqmllistreference.cpp +++ b/tests/auto/qml/qqmllistreference/tst_qqmllistreference.cpp @@ -87,7 +87,7 @@ public: void tst_qqmllistreference::initTestCase() { QQmlDataTest::initTestCase(); - qmlRegisterType<TestType>(); + qmlRegisterAnonymousType<TestType>("Test", 1); } void tst_qqmllistreference::qmllistreference() diff --git a/tests/auto/qml/qqmlproperty/data/aliasToBinding.qml b/tests/auto/qml/qqmlproperty/data/aliasToBinding.qml new file mode 100644 index 0000000000..54f9e3f944 --- /dev/null +++ b/tests/auto/qml/qqmlproperty/data/aliasToBinding.qml @@ -0,0 +1,23 @@ +import QtQuick 2.7 + +Item { + id: _window + property bool userFontStrikeout: true + + Component.onCompleted: { + _box.font.strikeout = Qt.binding(function() { return _window.userFontStrikeout; }); + } + + Rectangle { + id: _box + width: 100 + height: 100 + property alias font: _text.font + + Text { + id: _text + anchors.fill: parent + text: "Text" + } + } +} diff --git a/tests/auto/qml/qqmlproperty/tst_qqmlproperty.cpp b/tests/auto/qml/qqmlproperty/tst_qqmlproperty.cpp index 67da768f73..ad901c4eba 100644 --- a/tests/auto/qml/qqmlproperty/tst_qqmlproperty.cpp +++ b/tests/auto/qml/qqmlproperty/tst_qqmlproperty.cpp @@ -150,6 +150,8 @@ private slots: void floatToStringPrecision(); void copy(); + + void bindingToAlias(); private: QQmlEngine engine; }; @@ -2123,6 +2125,15 @@ void tst_qqmlproperty::initTestCase() qmlRegisterType<MyContainer>("Test",1,0,"MyContainer"); } +// QTBUG-60908 +void tst_qqmlproperty::bindingToAlias() +{ + QQmlEngine engine; + QQmlComponent component(&engine, testFileUrl("aliasToBinding.qml")); + QScopedPointer<QObject> o(component.create()); + QVERIFY(!o.isNull()); +} + QTEST_MAIN(tst_qqmlproperty) #include "tst_qqmlproperty.moc" diff --git a/tests/auto/qml/qqmlvaluetypes/tst_qqmlvaluetypes.cpp b/tests/auto/qml/qqmlvaluetypes/tst_qqmlvaluetypes.cpp index 8a602a0356..a55da25a91 100644 --- a/tests/auto/qml/qqmlvaluetypes/tst_qqmlvaluetypes.cpp +++ b/tests/auto/qml/qqmlvaluetypes/tst_qqmlvaluetypes.cpp @@ -97,6 +97,7 @@ private slots: void enumerableProperties(); void enumProperties(); void scarceTypes(); + void nonValueTypes(); private: QQmlEngine engine; @@ -1844,6 +1845,16 @@ void tst_qqmlvaluetypes::scarceTypes() QCOMPARE(QByteArray(pixmapValue->vtable()->className), QByteArray("VariantObject")); } +#define CHECK_TYPE_IS_NOT_VALUETYPE(Type, typeId, cppType) \ + QVERIFY(!QQmlValueTypeFactory::isValueType(QMetaType::Type)); + +void tst_qqmlvaluetypes::nonValueTypes() +{ + CHECK_TYPE_IS_NOT_VALUETYPE(UnknownType, 0, void) + QT_FOR_EACH_STATIC_PRIMITIVE_TYPE(CHECK_TYPE_IS_NOT_VALUETYPE); +} + +#undef CHECK_TYPE_IS_NOT_VALUETYPE QTEST_MAIN(tst_qqmlvaluetypes) diff --git a/tests/auto/quick/qquicklistview/tst_qquicklistview.cpp b/tests/auto/quick/qquicklistview/tst_qquicklistview.cpp index 08149a1786..7c26c22217 100644 --- a/tests/auto/quick/qquicklistview/tst_qquicklistview.cpp +++ b/tests/auto/quick/qquicklistview/tst_qquicklistview.cpp @@ -389,7 +389,7 @@ void tst_QQuickListView::init() m_view = nullptr; } #endif - qmlRegisterType<QAbstractItemModel>(); + qmlRegisterAnonymousType<QAbstractItemModel>("Proxy", 1); qmlRegisterType<ProxyTestInnerModel>("Proxy", 1, 0, "ProxyTestInnerModel"); qmlRegisterType<QSortFilterProxyModel>("Proxy", 1, 0, "QSortFilterProxyModel"); } diff --git a/tests/auto/quick/qquickloader/data/RequiredPropertyValuesComponent.qml b/tests/auto/quick/qquickloader/data/RequiredPropertyValuesComponent.qml new file mode 100644 index 0000000000..7bb21e8b93 --- /dev/null +++ b/tests/auto/quick/qquickloader/data/RequiredPropertyValuesComponent.qml @@ -0,0 +1,8 @@ +import QtQuick 2.0 + +Item { + id: behaviorCounter + required property int i + required property string s + +} diff --git a/tests/auto/quick/qquickloader/data/initialPropertyValues.10.qml b/tests/auto/quick/qquickloader/data/initialPropertyValues.10.qml new file mode 100644 index 0000000000..4728346ca1 --- /dev/null +++ b/tests/auto/quick/qquickloader/data/initialPropertyValues.10.qml @@ -0,0 +1,20 @@ +import QtQuick 2.0 + +Item { + id: root + property int i: 0 + property string s: "" + + Loader { + id: loader + objectName: "loader" + onLoaded: { + root.i = loader.item.i; // should be 42 + root.s = loader.item.s; // should be 11 + } + } + + Component.onCompleted: { + loader.setSource("RequiredPropertyValuesComponent.qml", {"i": 42}); + } +} diff --git a/tests/auto/quick/qquickloader/data/initialPropertyValues.9.qml b/tests/auto/quick/qquickloader/data/initialPropertyValues.9.qml new file mode 100644 index 0000000000..5d6e3171a0 --- /dev/null +++ b/tests/auto/quick/qquickloader/data/initialPropertyValues.9.qml @@ -0,0 +1,20 @@ +import QtQuick 2.0 + +Item { + id: root + property int i: 0 + property string s: "" + + Loader { + id: loader + objectName: "loader" + onLoaded: { + root.i = loader.item.i; // should be 42 + root.s = loader.item.s; // should be 11 + } + } + + Component.onCompleted: { + loader.setSource("RequiredPropertyValuesComponent.qml", {"i": 42, "s": "hello world"}); + } +} diff --git a/tests/auto/quick/qquickloader/tst_qquickloader.cpp b/tests/auto/quick/qquickloader/tst_qquickloader.cpp index fbdd87905b..da923d4d41 100644 --- a/tests/auto/quick/qquickloader/tst_qquickloader.cpp +++ b/tests/auto/quick/qquickloader/tst_qquickloader.cpp @@ -681,6 +681,16 @@ void tst_QQuickLoader::initialPropertyValues_data() << QStringList() << (QStringList() << "initialValue") << (QVariantList() << 6); + + QTest::newRow("ensure required properties are set correctly") << testFileUrl("initialPropertyValues.9.qml") + << QStringList() + << (QStringList() << "i" << "s") + << (QVariantList() << 42 << QLatin1String("hello world")); + + QTest::newRow("required properties only partially set =") << testFileUrl("initialPropertyValues.10.qml") + << (QStringList() << QString(testFileUrl("RequiredPropertyValuesComponent.qml").toString() + QLatin1String(":6:5: Required property s was not initialized"))) + << (QStringList() << "i" << "s") + << (QVariantList() << 0 << QLatin1String("")); } void tst_QQuickLoader::initialPropertyValues() diff --git a/tools/qmlcachegen/Qt5QuickCompilerConfig.cmake.in b/tools/qmlcachegen/Qt5QuickCompilerConfig.cmake.in index 75fbb0fcf3..baa437a947 100644 --- a/tools/qmlcachegen/Qt5QuickCompilerConfig.cmake.in +++ b/tools/qmlcachegen/Qt5QuickCompilerConfig.cmake.in @@ -50,13 +50,9 @@ but not all the files it references. get_filename_component(input_resource ${_resource} ABSOLUTE) - execute_process(COMMAND ${compiler_path} -filter-resource-file ${input_resource} -o ${new_resource_file} OUTPUT_VARIABLE remaining_files) - if(remaining_files) - list(APPEND filtered_rcc_files ${new_resource_file}) - list(APPEND loader_flags \"--resource-file-mapping=${_resource}=${new_resource_file}\") - else() - list(APPEND loader_flags \"--resource-file-mapping=${_resource}\") - endif() + configure_file(${input_resource} ${new_resource_file} COPYONLY) + list(APPEND filtered_rcc_files ${new_resource_file}) + list(APPEND loader_flags \"--resource-file-mapping=${_resource}=${new_resource_file}\") set(rcc_file_with_compilation_units) diff --git a/tools/qmlcachegen/generateloader.cpp b/tools/qmlcachegen/generateloader.cpp index 1c8a5a016a..71286137eb 100644 --- a/tools/qmlcachegen/generateloader.cpp +++ b/tools/qmlcachegen/generateloader.cpp @@ -100,228 +100,6 @@ QString symbolNamespaceForPath(const QString &relativePath) return mangledIdentifier(symbol); } -struct VirtualDirectoryEntry -{ - QString name; - QVector<VirtualDirectoryEntry*> dirEntries; - int firstChildIndex = -1; // node index inside generated data - bool isDirectory = true; - - VirtualDirectoryEntry() - {} - - ~VirtualDirectoryEntry() - { - qDeleteAll(dirEntries); - } - - VirtualDirectoryEntry *append(const QString &name) - { - for (QVector<VirtualDirectoryEntry*>::Iterator it = dirEntries.begin(), end = dirEntries.end(); - it != end; ++it) { - if ((*it)->name == name) - return *it; - } - - VirtualDirectoryEntry *subEntry = new VirtualDirectoryEntry; - subEntry->name = name; - dirEntries.append(subEntry); - return subEntry; - } - - void appendEmptyFile(const QString &name) - { - VirtualDirectoryEntry *subEntry = new VirtualDirectoryEntry; - subEntry->name = name; - subEntry->isDirectory = false; - dirEntries.append(subEntry); - } - - bool isEmpty() const { return dirEntries.isEmpty(); } -}; - -struct DataStream -{ - DataStream(QVector<unsigned char > *data = nullptr) - : data(data) - {} - - qint64 currentOffset() const { return data->size(); } - - DataStream &operator<<(quint16 value) - { - unsigned char d[2]; - qToBigEndian(value, d); - data->append(d[0]); - data->append(d[1]); - return *this; - } - DataStream &operator<<(quint32 value) - { - unsigned char d[4]; - qToBigEndian(value, d); - data->append(d[0]); - data->append(d[1]); - data->append(d[2]); - data->append(d[3]); - return *this; - } -private: - QVector<unsigned char> *data; -}; - -static bool resource_sort_order(const VirtualDirectoryEntry *lhs, const VirtualDirectoryEntry *rhs) -{ - return qt_hash(lhs->name) < qt_hash(rhs->name); -} - -struct ResourceTree -{ - ResourceTree() - {} - - void serialize(VirtualDirectoryEntry &root, QVector<unsigned char> *treeData, QVector<unsigned char> *stringData) - { - treeStream = DataStream(treeData); - stringStream = DataStream(stringData); - - QStack<VirtualDirectoryEntry *> directories; - - { - directories.push(&root); - while (!directories.isEmpty()) { - VirtualDirectoryEntry *entry = directories.pop(); - registerString(entry->name); - if (entry->isDirectory) - directories << entry->dirEntries; - } - } - - { - quint32 currentDirectoryIndex = 1; - directories.push(&root); - while (!directories.isEmpty()) { - VirtualDirectoryEntry *entry = directories.pop(); - entry->firstChildIndex = currentDirectoryIndex; - currentDirectoryIndex += entry->dirEntries.count(); - std::sort(entry->dirEntries.begin(), entry->dirEntries.end(), resource_sort_order); - - for (QVector<VirtualDirectoryEntry*>::ConstIterator child = entry->dirEntries.constBegin(), end = entry->dirEntries.constEnd(); - child != end; ++child) { - if ((*child)->isDirectory) - directories << *child; - } - } - } - - { - writeTreeEntry(&root); - directories.push(&root); - while (!directories.isEmpty()) { - VirtualDirectoryEntry *entry = directories.pop(); - - for (QVector<VirtualDirectoryEntry*>::ConstIterator child = entry->dirEntries.constBegin(), end = entry->dirEntries.constEnd(); - child != end; ++child) { - writeTreeEntry(*child); - if ((*child)->isDirectory) - directories << (*child); - } - } - } - } - -private: - DataStream treeStream; - DataStream stringStream; - QHash<QString, qint64> stringOffsets; - - void registerString(const QString &name) - { - if (stringOffsets.contains(name)) - return; - const qint64 offset = stringStream.currentOffset(); - stringOffsets.insert(name, offset); - - stringStream << quint16(name.length()) - << quint32(qt_hash(name)); - for (int i = 0; i < name.length(); ++i) - stringStream << quint16(name.at(i).unicode()); - } - - void writeTreeEntry(VirtualDirectoryEntry *entry) - { - treeStream << quint32(stringOffsets.value(entry->name)) - << quint16(entry->isDirectory ? 0x2 : 0x0); // Flags: File or Directory - - if (entry->isDirectory) { - treeStream << quint32(entry->dirEntries.count()) - << quint32(entry->firstChildIndex); - } else { - treeStream << quint16(QLocale::AnyCountry) << quint16(QLocale::C) - << quint32(0x0); - } - } -}; - -static QByteArray generateResourceDirectoryTree(QTextStream &code, const QStringList &qrcFiles, - const QStringList &sortedRetainedFiles) -{ - QByteArray call; - if (qrcFiles.isEmpty()) - return call; - - VirtualDirectoryEntry resourceDirs; - resourceDirs.name = QStringLiteral("/"); - - for (const QString &entry : qrcFiles) { - const QStringList segments = entry.split(QLatin1Char('/'), QString::SkipEmptyParts); - - VirtualDirectoryEntry *dirEntry = &resourceDirs; - - for (int i = 0; i < segments.count() - 1; ++i) - dirEntry = dirEntry->append(segments.at(i)); - if (!std::binary_search(sortedRetainedFiles.begin(), sortedRetainedFiles.end(), entry)) - dirEntry->appendEmptyFile(segments.last()); - } - - if (resourceDirs.isEmpty()) - return call; - - QVector<unsigned char> names; - QVector<unsigned char> tree; - ResourceTree().serialize(resourceDirs, &tree, &names); - - code << "static const unsigned char qt_resource_tree[] = {\n"; - for (int i = 0; i < tree.count(); ++i) { - code << uint(tree.at(i)); - if (i < tree.count() - 1) - code << ','; - if (i % 16 == 0) - code << '\n'; - } - code << "};\n"; - - code << "static const unsigned char qt_resource_names[] = {\n"; - for (int i = 0; i < names.count(); ++i) { - code << uint(names.at(i)); - if (i < names.count() - 1) - code << ','; - if (i % 16 == 0) - code << '\n'; - } - code << "};\n"; - - code << "static const unsigned char qt_resource_empty_payout[] = { 0, 0, 0, 0, 0 };\n"; - - code << "QT_BEGIN_NAMESPACE\n"; - code << "extern Q_CORE_EXPORT bool qRegisterResourceData(int, const unsigned char *, const unsigned char *, const unsigned char *);\n"; - code << "QT_END_NAMESPACE\n"; - - call = "QT_PREPEND_NAMESPACE(qRegisterResourceData)(/*version*/0x01, qt_resource_tree, qt_resource_names, qt_resource_empty_payout);\n"; - - return call; -} - static QString qtResourceNameForFile(const QString &fileName) { QFileInfo fi(fileName); @@ -332,9 +110,8 @@ static QString qtResourceNameForFile(const QString &fileName) return name; } -bool generateLoader(const QStringList &compiledFiles, const QStringList &sortedRetainedFiles, - const QString &outputFileName, const QStringList &resourceFileMappings, - QString *errorString) +bool generateLoader(const QStringList &compiledFiles, const QString &outputFileName, + const QStringList &resourceFileMappings, QString *errorString) { QByteArray generatedLoaderCode; @@ -345,9 +122,6 @@ bool generateLoader(const QStringList &compiledFiles, const QStringList &sortedR stream << "#include <QtCore/qurl.h>\n"; stream << "\n"; - QByteArray resourceRegisterCall = generateResourceDirectoryTree(stream, compiledFiles, - sortedRetainedFiles); - stream << "namespace QmlCacheGeneratedCode {\n"; for (int i = 0; i < compiledFiles.count(); ++i) { const QString compiledFile = compiledFiles.at(i); @@ -385,9 +159,6 @@ bool generateLoader(const QStringList &compiledFiles, const QStringList &sortedR stream << " registration.lookupCachedQmlUnit = &lookupCachedUnit;\n"; stream << " QQmlPrivate::qmlregister(QQmlPrivate::QmlUnitCacheHookRegistration, ®istration);\n"; - if (!resourceRegisterCall.isEmpty()) - stream << resourceRegisterCall; - stream << "}\n\n"; stream << "Registry::~Registry() {\n"; stream << " QQmlPrivate::qmlunregister(QQmlPrivate::QmlUnitCacheHookRegistration, quintptr(&lookupCachedUnit));\n"; diff --git a/tools/qmlcachegen/qmlcachegen.cpp b/tools/qmlcachegen/qmlcachegen.cpp index ac9cf039d3..41171b3f07 100644 --- a/tools/qmlcachegen/qmlcachegen.cpp +++ b/tools/qmlcachegen/qmlcachegen.cpp @@ -47,10 +47,8 @@ using namespace QQmlJS; -int filterResourceFile(const QString &input, const QString &output); -bool generateLoader(const QStringList &compiledFiles, const QStringList &retainedFiles, - const QString &output, const QStringList &resourceFileMappings, - QString *errorString); +bool generateLoader(const QStringList &compiledFiles, const QString &output, + const QStringList &resourceFileMappings, QString *errorString); QString symbolNamespaceForPath(const QString &relativePath); QSet<QString> illegalNames; @@ -419,14 +417,10 @@ int main(int argc, char **argv) parser.addHelpOption(); parser.addVersionOption(); - QCommandLineOption filterResourceFileOption(QStringLiteral("filter-resource-file"), QCoreApplication::translate("main", "Filter out QML/JS files from a resource file that can be cached ahead of time instead")); - parser.addOption(filterResourceFileOption); QCommandLineOption resourceFileMappingOption(QStringLiteral("resource-file-mapping"), QCoreApplication::translate("main", "Path from original resource file to new one"), QCoreApplication::translate("main", "old-name:new-name")); parser.addOption(resourceFileMappingOption); QCommandLineOption resourceOption(QStringLiteral("resource"), QCoreApplication::translate("main", "Qt resource file that might later contain one of the compiled files"), QCoreApplication::translate("main", "resource-file-name")); parser.addOption(resourceOption); - QCommandLineOption retainOption(QStringLiteral("retain"), QCoreApplication::translate("main", "Qt resource file the contents of which should not be replaced by empty stubs"), QCoreApplication::translate("main", "resource-file-name")); - parser.addOption(retainOption); QCommandLineOption resourcePathOption(QStringLiteral("resource-path"), QCoreApplication::translate("main", "Qt resource file path corresponding to the file being compiled"), QCoreApplication::translate("main", "resource-path")); parser.addOption(resourcePathOption); @@ -468,18 +462,11 @@ int main(int argc, char **argv) if (outputFileName.isEmpty()) outputFileName = inputFile + QLatin1Char('c'); - if (parser.isSet(filterResourceFileOption)) { - return filterResourceFile(inputFile, outputFileName); - } - if (target == GenerateLoader) { ResourceFileMapper mapper(sources); - ResourceFileMapper retain(parser.values(retainOption)); Error error; - QStringList retainedFiles = retain.qmlCompilerFiles(); - std::sort(retainedFiles.begin(), retainedFiles.end()); - if (!generateLoader(mapper.qmlCompilerFiles(), retainedFiles, outputFileName, + if (!generateLoader(mapper.qmlCompilerFiles(), outputFileName, parser.values(resourceFileMappingOption), &error.message)) { error.augment(QLatin1String("Error generating loader stub: ")).print(); return EXIT_FAILURE; diff --git a/tools/qmlcachegen/qmlcachegen.pro b/tools/qmlcachegen/qmlcachegen.pro index bee0b9a37e..910cb657d7 100644 --- a/tools/qmlcachegen/qmlcachegen.pro +++ b/tools/qmlcachegen/qmlcachegen.pro @@ -4,7 +4,6 @@ QT = qmldevtools-private DEFINES += QT_NO_CAST_TO_ASCII QT_NO_CAST_FROM_ASCII SOURCES = qmlcachegen.cpp \ - resourcefilter.cpp \ generateloader.cpp \ resourcefilemapper.cpp TARGET = qmlcachegen diff --git a/tools/qmlcachegen/qtquickcompiler.prf b/tools/qmlcachegen/qtquickcompiler.prf index 2f98aadefe..0129122157 100644 --- a/tools/qmlcachegen/qtquickcompiler.prf +++ b/tools/qmlcachegen/qtquickcompiler.prf @@ -16,20 +16,6 @@ defineReplace(qmlCacheResourceFileOutputName) { return($${name}) } -defineTest(qtQuickRetainSources) { - for(retainedRes, QTQUICK_COMPILER_RETAINED_RESOURCES) { - equals(1, $$retainedRes): return(true) - } - return(false) -} - -defineTest(qtQuickSkippedResourceFile) { - for(skippedRes, QTQUICK_COMPILER_SKIPPED_RESOURCES) { - equals(1, $$skippedRes): return(true) - } - return(false) -} - # Flatten RESOURCES that may contain individual files or objects load(resources) @@ -37,29 +23,14 @@ NEWRESOURCES = QMLCACHE_RESOURCE_FILES = for(res, RESOURCES) { - qtQuickSkippedResourceFile($$res) { - NEWRESOURCES += $$res - next() - } - absRes = $$absolute_path($$res, $$_PRO_FILE_PWD_) rccContents = $$system($$QMAKE_RCC_DEP -list $$system_quote($$absRes),lines) contains(rccContents,.*\\.js$)|contains(rccContents,.*\\.qml$)|contains(rccContents,.*\\.mjs$) { new_resource = $$qmlCacheResourceFileOutputName($$res) mkpath($$dirname(new_resource)) - qtQuickRetainSources($$res) { - NEWRESOURCES += $$res - QMLCACHE_LOADER_FLAGS += --retain=$$shell_quote($$absRes) - } else { - remaining_files = $$system($$QML_CACHEGEN_FILTER -filter-resource-file \ - -o $$system_quote($$new_resource) $$system_quote($$absRes),lines) - !isEmpty(remaining_files) { - NEWRESOURCES += $$new_resource - QMLCACHE_LOADER_FLAGS += --resource-file-mapping=$$shell_quote($$absRes=$$new_resource) - } else { - QMLCACHE_LOADER_FLAGS += --resource-file-mapping=$$shell_quote($$absRes) - } - } + system($$QMAKE_QMAKE -install qinstall $$system_quote($$absRes) $$system_quote($$new_resource)) + NEWRESOURCES += $$new_resource + QMLCACHE_LOADER_FLAGS += --resource-file-mapping=$$shell_quote($$absRes=$$new_resource) QMLCACHE_RESOURCE_FILES += $$absRes diff --git a/tools/qmlcachegen/resourcefilter.cpp b/tools/qmlcachegen/resourcefilter.cpp deleted file mode 100644 index 3ad6e9ca0d..0000000000 --- a/tools/qmlcachegen/resourcefilter.cpp +++ /dev/null @@ -1,185 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtQml module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ -#include <QString> -#include <QXmlStreamReader> -#include <QFile> -#include <QDir> - -int filterResourceFile(const QString &input, const QString &output) -{ - enum State { - InitialState, - InRCC, - InResource, - InFile - }; - State state = InitialState; - - QString prefix; - QString currentFileName; - QXmlStreamAttributes fileAttributes; - - QFile file(input); - if (!file.open(QIODevice::ReadOnly)) { - fprintf(stderr, "Cannot open %s for reading.\n", qPrintable(input)); - return EXIT_FAILURE; - } - - QDir inputDirectory = QFileInfo(file).absoluteDir(); - QDir outputDirectory = QFileInfo(output).absoluteDir(); - - QString outputString; - QXmlStreamWriter writer(&outputString); - writer.setAutoFormatting(true); - - QStringList remainingFiles; - - QXmlStreamReader reader(&file); - while (!reader.atEnd()) { - switch (reader.readNext()) { - case QXmlStreamReader::StartDocument: { - QStringRef version = reader.documentVersion(); - if (!version.isEmpty()) - writer.writeStartDocument(version.toString()); - else - writer.writeStartDocument(); - break; - } - case QXmlStreamReader::EndDocument: - writer.writeEndDocument(); - break; - case QXmlStreamReader::StartElement: - if (reader.name() == QStringLiteral("RCC")) { - if (state != InitialState) { - fprintf(stderr, "Unexpected RCC tag in line %d\n", int(reader.lineNumber())); - return EXIT_FAILURE; - } - state = InRCC; - } else if (reader.name() == QStringLiteral("qresource")) { - if (state != InRCC) { - fprintf(stderr, "Unexpected qresource tag in line %d\n", int(reader.lineNumber())); - return EXIT_FAILURE; - } - state = InResource; - QXmlStreamAttributes attributes = reader.attributes(); - if (attributes.hasAttribute(QStringLiteral("prefix"))) - prefix = attributes.value(QStringLiteral("prefix")).toString(); - if (!prefix.startsWith(QLatin1Char('/'))) - prefix.prepend(QLatin1Char('/')); - if (!prefix.endsWith(QLatin1Char('/'))) - prefix.append(QLatin1Char('/')); - } else if (reader.name() == QStringLiteral("file")) { - if (state != InResource) { - fprintf(stderr, "Unexpected file tag in line %d\n", int(reader.lineNumber())); - return EXIT_FAILURE; - } - state = InFile; - fileAttributes = reader.attributes(); - continue; - } - writer.writeStartElement(reader.name().toString()); - writer.writeAttributes(reader.attributes()); - continue; - - case QXmlStreamReader::EndElement: - if (reader.name() == QStringLiteral("file")) { - if (state != InFile) { - fprintf(stderr, "Unexpected end of file tag in line %d\n", int(reader.lineNumber())); - return EXIT_FAILURE; - } - state = InResource; - continue; - } else if (reader.name() == QStringLiteral("qresource")) { - if (state != InResource) { - fprintf(stderr, "Unexpected end of qresource tag in line %d\n", int(reader.lineNumber())); - return EXIT_FAILURE; - } - state = InRCC; - } else if (reader.name() == QStringLiteral("RCC")) { - if (state != InRCC) { - fprintf(stderr, "Unexpected end of RCC tag in line %d\n", int(reader.lineNumber())); - return EXIT_FAILURE; - } - state = InitialState; - } - writer.writeEndElement(); - continue; - - case QXmlStreamReader::Characters: - if (reader.isWhitespace()) - break; - if (state != InFile) - return EXIT_FAILURE; - currentFileName = reader.text().toString(); - if (currentFileName.isEmpty()) - continue; - - if (!currentFileName.endsWith(QStringLiteral(".qml")) - && !currentFileName.endsWith(QStringLiteral(".js")) - && !currentFileName.endsWith(QStringLiteral(".mjs"))) { - writer.writeStartElement(QStringLiteral("file")); - - if (!fileAttributes.hasAttribute(QStringLiteral("alias"))) - fileAttributes.append(QStringLiteral("alias"), currentFileName); - - currentFileName = inputDirectory.absoluteFilePath(currentFileName); - currentFileName = outputDirectory.relativeFilePath(currentFileName); - - remainingFiles << currentFileName; - - writer.writeAttributes(fileAttributes); - writer.writeCharacters(currentFileName); - writer.writeEndElement(); - } - continue; - - default: break; - } - } - - if (!remainingFiles.isEmpty()) { - QFile outputFile(output); - if (!outputFile.open(QIODevice::WriteOnly | QIODevice::Truncate)) { - fprintf(stderr, "Cannot open %s for writing.\n", qPrintable(output)); - return EXIT_FAILURE; - } - const QByteArray outputStringUtf8 = outputString.toUtf8(); - if (outputFile.write(outputStringUtf8) != outputStringUtf8.size()) - return EXIT_FAILURE; - - outputFile.close(); - if (outputFile.error() != QFileDevice::NoError) - return EXIT_FAILURE; - - // The build system expects this output if we wrote a qrc file and no output - // if no files remain. - fprintf(stdout, "New resource file written with %d files.\n", remainingFiles.count()); - } - - return EXIT_SUCCESS; -} diff --git a/tools/qmllint/findunqualified.cpp b/tools/qmllint/findunqualified.cpp index 27939608d7..4404ddf49a 100644 --- a/tools/qmllint/findunqualified.cpp +++ b/tools/qmllint/findunqualified.cpp @@ -170,7 +170,8 @@ FindUnqualifiedIDVisitor::localQmlFile2FakeMetaObject(QString filePath) { using namespace QQmlJS::AST; auto fake = new LanguageUtils::FakeMetaObject; - fake->setClassName(QFileInfo { filePath }.baseName()); + QString baseName = QFileInfo { filePath }.baseName(); + fake->setClassName(baseName.endsWith(".ui") ? baseName.chopped(3) : baseName); QFile file(filePath); if (!file.open(QFile::ReadOnly)) { return fake; @@ -273,6 +274,7 @@ FindUnqualifiedIDVisitor::localQmlFile2FakeMetaObject(QString filePath) auto sourceElement = static_cast<UiSourceElement *>(initMembers->member); if (FunctionExpression *fexpr = sourceElement->sourceElement->asFunctionDefinition()) { LanguageUtils::FakeMetaMethod method; + method.setMethodName(fexpr->name.toString()); method.setMethodType(LanguageUtils::FakeMetaMethod::Method); FormalParameterList *parameters = fexpr->formals; while (parameters) { @@ -304,6 +306,22 @@ FindUnqualifiedIDVisitor::localQmlFile2FakeMetaObject(QString filePath) return fake; } +void FindUnqualifiedIDVisitor::importDirectory(const QString &directory, const QString &prefix) +{ + QString dirname = directory; + QFileInfo info { dirname }; + if (info.isRelative()) + dirname = QDir(QFileInfo { m_filePath }.path()).filePath(dirname); + + QDirIterator it { dirname, QStringList() << QLatin1String("*.qml"), QDir::NoFilter }; + while (it.hasNext()) { + LanguageUtils::FakeMetaObject *fake = localQmlFile2FakeMetaObject(it.next()); + m_exportedName2MetaObject.insert( + prefix + fake->className(), + QSharedPointer<const LanguageUtils::FakeMetaObject>(fake)); + } +} + void FindUnqualifiedIDVisitor::importExportedNames(QStringRef prefix, QString name) { for (;;) { @@ -377,6 +395,8 @@ bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::UiProgram *) meta->addProperty(LanguageUtils::FakeMetaProperty {"ignoreUnknownSignals", "bool", false, false, false, 0}); meta->addProperty(LanguageUtils::FakeMetaProperty {"target", "QObject", false, false, false, 0}); m_exportedName2MetaObject["Connections"] = LanguageUtils::FakeMetaObject::ConstPtr { meta }; + + importDirectory(".", QString()); return true; } @@ -476,6 +496,23 @@ void FindUnqualifiedIDVisitor::endVisit(QQmlJS::AST::WithStatement *) leaveEnvironment(); } +static QString signalName(const QStringRef &handlerName) +{ + if (handlerName.startsWith("on") && handlerName.size() > 2) { + QString signal = handlerName.mid(2).toString(); + for (int i = 0; i < signal.length(); ++i) { + QCharRef ch = signal[i]; + if (ch.isLower()) + return QString(); + if (ch.isUpper()) { + ch = ch.toLower(); + return signal; + } + } + } + return QString(); +} + bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::UiScriptBinding *uisb) { using namespace QQmlJS::AST; @@ -489,8 +526,17 @@ bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::UiScriptBinding *uisb) if (m_currentScope->isVisualRootScope()) { m_rootId = identexp->name.toString(); } - } else if (name.startsWith("on") && name.size() > 2 && name.at(2).isUpper()) { - auto statement = uisb->statement; + } else { + const QString signal = signalName(name); + if (signal.isEmpty()) + return true; + + if (!m_currentScope->methods().contains(signal)) { + m_currentScope->addUnmatchedSignalHandler(name.toString(), uisb->firstSourceLocation()); + return true; + } + + const auto statement = uisb->statement; if (statement->kind == Node::Kind::Kind_ExpressionStatement) { if (static_cast<ExpressionStatement *>(statement)->expression->asFunctionDefinition()) { // functions are already handled @@ -499,17 +545,14 @@ bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::UiScriptBinding *uisb) return true; } } - QString signal = name.mid(2).toString(); - signal[0] = signal[0].toLower(); - if (!m_currentScope->methods().contains(signal)) { - qDebug() << "Info file does not contain signal" << signal; - } else { - auto method = m_currentScope->methods()[signal]; - for (auto const ¶m : method.parameterNames()) { - auto firstSourceLocation = uisb->statement->firstSourceLocation(); - bool hasMultilineStatementBody = uisb->statement->lastSourceLocation().startLine > firstSourceLocation.startLine; - m_currentScope->insertSignalIdentifier(param, method, firstSourceLocation, hasMultilineStatementBody); - } + + auto method = m_currentScope->methods()[signal]; + for (auto const ¶m : method.parameterNames()) { + const auto firstSourceLocation = statement->firstSourceLocation(); + bool hasMultilineStatementBody + = statement->lastSourceLocation().startLine > firstSourceLocation.startLine; + m_currentScope->insertSignalIdentifier(param, method, firstSourceLocation, + hasMultilineStatementBody); } return true; } @@ -649,18 +692,9 @@ bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::UiImport *import) prefix += import->importId + QLatin1Char('.'); } auto dirname = import->fileName.toString(); - if (!dirname.isEmpty()) { - QFileInfo info { dirname }; - if (info.isRelative()) { - dirname = QDir(QFileInfo { m_filePath }.path()).filePath(dirname); - } - QDirIterator it { dirname, QStringList() << QLatin1String("*.qml"), QDir::NoFilter }; - while (it.hasNext()) { - LanguageUtils::FakeMetaObject *fake = localQmlFile2FakeMetaObject(it.next()); - m_exportedName2MetaObject.insert( - fake->className(), QSharedPointer<const LanguageUtils::FakeMetaObject>(fake)); - } - } + if (!dirname.isEmpty()) + importDirectory(dirname, prefix); + QString path {}; if (!import->importId.isEmpty()) { m_qmlid2meta.insert(import->importId.toString(), {}); // TODO: do not put imported ids into the same space as qml IDs diff --git a/tools/qmllint/findunqualified.h b/tools/qmllint/findunqualified.h index 181f42f265..f7d1aab1f4 100644 --- a/tools/qmllint/findunqualified.h +++ b/tools/qmllint/findunqualified.h @@ -70,7 +70,7 @@ private: void importHelper(QString id, QString prefix, int major, int minor); LanguageUtils::FakeMetaObject* localQmlFile2FakeMetaObject(QString filePath); - + void importDirectory(const QString &directory, const QString &prefix); void importExportedNames(QStringRef prefix, QString name); void throwRecursionDepthError() override; diff --git a/tools/qmllint/scopetree.cpp b/tools/qmllint/scopetree.cpp index 2eff3fa319..1e873cca8f 100644 --- a/tools/qmllint/scopetree.cpp +++ b/tools/qmllint/scopetree.cpp @@ -81,6 +81,12 @@ void ScopeTree::insertPropertyIdentifier(QString id) this->addMethod(method); } +void ScopeTree::addUnmatchedSignalHandler(const QString &handler, + const QQmlJS::AST::SourceLocation &location) +{ + m_unmatchedSignalHandlers.append(qMakePair(handler, location)); +} + bool ScopeTree::isIdInCurrentScope(const QString &id) const { return isIdInCurrentQMlScopes(id) || isIdInCurrentJSScopes(id); @@ -132,6 +138,15 @@ bool ScopeTree::recheckIdentifiers(const QString& code, const QHash<QString, Lan workQueue.enqueue(this); while (!workQueue.empty()) { const ScopeTree* currentScope = workQueue.dequeue(); + for (const auto &handler : currentScope->m_unmatchedSignalHandlers) { + colorOut.write("Warning: ", Warning); + colorOut.write(QString::fromLatin1( + "no matching signal found for handler \"%1\" at %2:%3\n") + .arg(handler.first).arg(handler.second.startLine) + .arg(handler.second.startColumn), Normal); + printContext(colorOut, code, handler.second); + } + for (auto idLocationPair : currentScope->m_accessedIdentifiers) { if (qmlIDs.contains(idLocationPair.first)) continue; @@ -141,13 +156,11 @@ bool ScopeTree::recheckIdentifiers(const QString& code, const QHash<QString, Lan noUnqualifiedIdentifier = false; colorOut.write("Warning: ", Warning); auto location = idLocationPair.second; - colorOut.write(QString::asprintf("unqualified access at %d:%d\n", location.startLine, location.startColumn), Normal); - IssueLocationWithContext issueLocationWithContext {code, location}; - colorOut.write(issueLocationWithContext.beforeText.toString(), Normal); - colorOut.write(issueLocationWithContext.issueText.toString(), Error); - colorOut.write(issueLocationWithContext.afterText.toString() + QLatin1Char('\n'), Normal); - int tabCount = issueLocationWithContext.beforeText.count(QLatin1Char('\t')); - colorOut.write(QString(" ").repeated(issueLocationWithContext.beforeText.length() - tabCount) + QString("\t").repeated(tabCount) + QString("^").repeated(location.length) + QLatin1Char('\n'), Normal); + colorOut.write(QString::asprintf("unqualified access at %d:%d\n", location.startLine, + location.startColumn), Normal); + + printContext(colorOut, code, location); + // root(JS) --> program(qml) --> (first element) if (root->m_childScopes[0]->m_childScopes[0]->m_currentScopeQMLIdentifiers.contains(idLocationPair.first)) { ScopeTree *parentScope = currentScope->m_parentScope; @@ -161,6 +174,7 @@ bool ScopeTree::recheckIdentifiers(const QString& code, const QHash<QString, Lan colorOut.write("Note: ", Warning); colorOut.write(("You first have to give the root element an id\n")); } + IssueLocationWithContext issueLocationWithContext {code, location}; colorOut.write(issueLocationWithContext.beforeText.toString(), Normal); colorOut.write(rootId + QLatin1Char('.'), Hint); colorOut.write(issueLocationWithContext.issueText.toString(), Normal); @@ -212,7 +226,7 @@ QMap<QString, LanguageUtils::FakeMetaMethod>const &ScopeTree::methods() const bool ScopeTree::isIdInCurrentQMlScopes(QString id) const { auto qmlScope = getCurrentQMLScope(); - return qmlScope->m_currentScopeQMLIdentifiers.contains(id); + return qmlScope->m_currentScopeQMLIdentifiers.contains(id) || qmlScope->m_methods.contains(id); } bool ScopeTree::isIdInCurrentJSScopes(QString id) const @@ -250,6 +264,20 @@ ScopeTree *ScopeTree::getCurrentQMLScope() return qmlScope; } +void ScopeTree::printContext(ColorOutput &colorOut, const QString &code, + const QQmlJS::AST::SourceLocation &location) const +{ + IssueLocationWithContext issueLocationWithContext {code, location}; + colorOut.write(issueLocationWithContext.beforeText.toString(), Normal); + colorOut.write(issueLocationWithContext.issueText.toString(), Error); + colorOut.write(issueLocationWithContext.afterText.toString() + QLatin1Char('\n'), Normal); + int tabCount = issueLocationWithContext.beforeText.count(QLatin1Char('\t')); + colorOut.write(QString(" ").repeated(issueLocationWithContext.beforeText.length() - tabCount) + + QString("\t").repeated(tabCount) + + QString("^").repeated(location.length) + + QLatin1Char('\n'), Normal); +} + ScopeType ScopeTree::scopeType() {return m_scopeType;} void ScopeTree::addMethod(LanguageUtils::FakeMetaMethod method) diff --git a/tools/qmllint/scopetree.h b/tools/qmllint/scopetree.h index 872a509123..52cdc45e96 100644 --- a/tools/qmllint/scopetree.h +++ b/tools/qmllint/scopetree.h @@ -73,6 +73,8 @@ public: void insertQMLIdentifier(QString id); void insertSignalIdentifier(QString id, LanguageUtils::FakeMetaMethod method, QQmlJS::AST::SourceLocation loc, bool hasMultilineHandlerBody); void insertPropertyIdentifier(QString id); // inserts property as qml identifier as well as the corresponding + void addUnmatchedSignalHandler(const QString &handler, + const QQmlJS::AST::SourceLocation &location); bool isIdInCurrentScope(QString const &id) const; void addIdToAccssedIfNotInParentScopes(QPair<QString, QQmlJS::AST::SourceLocation> const& id_loc_pair, const QSet<QString>& unknownImports); @@ -96,11 +98,14 @@ private: ScopeTree *m_parentScope; QString m_name; ScopeType m_scopeType; + QVector<QPair<QString, QQmlJS::AST::SourceLocation>> m_unmatchedSignalHandlers; bool isIdInCurrentQMlScopes(QString id) const; bool isIdInCurrentJSScopes(QString id) const; bool isIdInjectedFromSignal(QString id) const; const ScopeTree* getCurrentQMLScope() const; ScopeTree* getCurrentQMLScope(); + void printContext(ColorOutput& colorOut, const QString &code, + const QQmlJS::AST::SourceLocation &location) const; }; #endif // SCOPETREE_H diff --git a/tools/qmlplugindump/qmlstreamwriter.cpp b/tools/qmlplugindump/qmlstreamwriter.cpp index 3632cee60d..b0fbc4e443 100644 --- a/tools/qmlplugindump/qmlstreamwriter.cpp +++ b/tools/qmlplugindump/qmlstreamwriter.cpp @@ -50,9 +50,9 @@ void QmlStreamWriter::writeEndDocument() void QmlStreamWriter::writeLibraryImport(const QString &uri, int majorVersion, int minorVersion, const QString &as) { - m_stream->write(QString("import %1 %2.%3").arg(uri, QString::number(majorVersion), QString::number(minorVersion)).toUtf8()); + m_stream->write(QString::fromLatin1("import %1 %2.%3").arg(uri, QString::number(majorVersion), QString::number(minorVersion)).toUtf8()); if (!as.isEmpty()) - m_stream->write(QString(" as %1").arg(as).toUtf8()); + m_stream->write(QString::fromLatin1(" as %1").arg(as).toUtf8()); m_stream->write("\n"); } @@ -60,7 +60,7 @@ void QmlStreamWriter::writeStartObject(const QString &component) { flushPotentialLinesWithNewlines(); writeIndent(); - m_stream->write(QString("%1 {").arg(component).toUtf8()); + m_stream->write(QString::fromLatin1("%1 {").arg(component).toUtf8()); ++m_indentDepth; m_maybeOneline = true; } @@ -89,7 +89,7 @@ void QmlStreamWriter::writeEndObject() void QmlStreamWriter::writeScriptBinding(const QString &name, const QString &rhs) { - writePotentialLine(QString("%1: %2").arg(name, rhs).toUtf8()); + writePotentialLine(QString::fromLatin1("%1: %2").arg(name, rhs).toUtf8()); } void QmlStreamWriter::writeBooleanBinding(const QString &name, bool value) @@ -104,7 +104,7 @@ void QmlStreamWriter::writeArrayBinding(const QString &name, const QStringList & // try to use a single line QString singleLine; - singleLine += QString("%1: [").arg(name); + singleLine += QString::fromLatin1("%1: [").arg(name); for (int i = 0; i < elements.size(); ++i) { singleLine += elements.at(i); if (i != elements.size() - 1) @@ -117,7 +117,7 @@ void QmlStreamWriter::writeArrayBinding(const QString &name, const QStringList & } // write multi-line - m_stream->write(QString("%1: [\n").arg(name).toUtf8()); + m_stream->write(QString::fromLatin1("%1: [\n").arg(name).toUtf8()); ++m_indentDepth; for (int i = 0; i < elements.size(); ++i) { writeIndent(); @@ -143,13 +143,13 @@ void QmlStreamWriter::writeScriptObjectLiteralBinding(const QString &name, const { flushPotentialLinesWithNewlines(); writeIndent(); - m_stream->write(QString("%1: {\n").arg(name).toUtf8()); + m_stream->write(QString::fromLatin1("%1: {\n").arg(name).toUtf8()); ++m_indentDepth; for (int i = 0; i < keyValue.size(); ++i) { const QString key = keyValue.at(i).first; const QString value = keyValue.at(i).second; writeIndent(); - m_stream->write(QString("%1: %2").arg(key, value).toUtf8()); + m_stream->write(QString::fromLatin1("%1: %2").arg(key, value).toUtf8()); if (i != keyValue.size() - 1) { m_stream->write(",\n"); } else { |