diff options
Diffstat (limited to 'tests/auto/qml/qmlcppcodegen')
151 files changed, 6959 insertions, 1981 deletions
diff --git a/tests/auto/qml/qmlcppcodegen/CMakeLists.txt b/tests/auto/qml/qmlcppcodegen/CMakeLists.txt index 8644e00cde..715ad6162a 100644 --- a/tests/auto/qml/qmlcppcodegen/CMakeLists.txt +++ b/tests/auto/qml/qmlcppcodegen/CMakeLists.txt @@ -1,6 +1,12 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: BSD-3-Clause +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qmlcppcodegen LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + add_subdirectory(data) qt_internal_add_test(tst_qmlcppcodegen @@ -8,9 +14,13 @@ qt_internal_add_test(tst_qmlcppcodegen tst_qmlcppcodegen.cpp LIBRARIES Qt::QmlPrivate - Qt::Gui + Qt::GuiPrivate codegen_test_module codegen_test_moduleplugin + codegen_test_hidden + codegen_test_hiddenplugin + codegen_test_stringbuilder + codegen_test_stringbuilderplugin ) qt_internal_add_test(tst_qmlcppcodegen_interpreted @@ -18,9 +28,13 @@ qt_internal_add_test(tst_qmlcppcodegen_interpreted tst_qmlcppcodegen.cpp LIBRARIES Qt::QmlPrivate - Qt::Gui + Qt::GuiPrivate codegen_test_module codegen_test_moduleplugin + codegen_test_hidden + codegen_test_hiddenplugin + codegen_test_stringbuilder + codegen_test_stringbuilderplugin DEFINES QT_TEST_FORCE_INTERPRETER ) diff --git a/tests/auto/qml/qmlcppcodegen/data/Action.qml b/tests/auto/qml/qmlcppcodegen/data/Action.qml new file mode 100644 index 0000000000..99b86fb31c --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/Action.qml @@ -0,0 +1,7 @@ +import QtQuick +import QtQuick.Controls as QQC2 +import QtQuick.Templates as T + +QQC2.Action { + property bool visible: true +} diff --git a/tests/auto/qml/qmlcppcodegen/data/B.qml b/tests/auto/qml/qmlcppcodegen/data/B.qml new file mode 100644 index 0000000000..97e895ecad --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/B.qml @@ -0,0 +1,5 @@ +import QtQml + +QtObject { + property rect r +} diff --git a/tests/auto/qml/qmlcppcodegen/data/BaseConstraint.qml b/tests/auto/qml/qmlcppcodegen/data/BaseConstraint.qml new file mode 100644 index 0000000000..2615778f6a --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/BaseConstraint.qml @@ -0,0 +1,9 @@ +pragma Strict +import QtQml + +QtObject { + property int satisfaction: Satisfaction.NONE + property QtObject output + + function inputsKnown(mark: int) : bool { return true } +} diff --git a/tests/auto/qml/qmlcppcodegen/data/CMakeLists.txt b/tests/auto/qml/qmlcppcodegen/data/CMakeLists.txt index 0737d77005..7f2e6ad967 100644 --- a/tests/auto/qml/qmlcppcodegen/data/CMakeLists.txt +++ b/tests/auto/qml/qmlcppcodegen/data/CMakeLists.txt @@ -4,41 +4,61 @@ set(cpp_sources ambiguous.h birthdayparty.cpp birthdayparty.h + convertQJSPrimitiveValueToIntegral.h cppbaseclass.h druggeljug.h + dummyobjekt.h dynamicmeta.h enumproblems.h enumProperty.h + getOptionalLookup.h gadgetwithenum.h invisible.h + listprovider.h multiforeign.h objectwithmethod.h person.cpp person.h + resettable.h + sequenceToIterable.h sequencetypeexample.cpp sequencetypeexample.h state.h theme.cpp theme.h timelinetheme.cpp timelinetheme.h + variantMapLookup.h + variantreturn.h + weathermoduleurl.h wrapwithvariant.h withlength.h ) set(qml_files AccessModelMethodsFromOutside.qml + Action.qml ArraySequenceLengthInterop.qml + B.qml BadType.qml + BaseConstraint.qml BaseMember.qml BindingExpression.qml + CxxTypeFromDir.qml + CxxTypeFromImplicit.qml Cycle1.qml Cycle2.qml Cycle3.qml - CxxTypeFromDir.qml - CxxTypeFromImplicit.qml + CppMethodListReturnType.qml Dummy.qml + Dummy2.qml + EditConstraint.qml Enums.qml Foozle.qml + GetOptionalLookupOnQJSValueNonStrict.qml + GetOptionalLookupShadowed.qml Loopy.qml + NotificationItem.qml + NotificationsUtils.js OkType.qml Panel.qml + Planner.qml ProgressBar/Keyframe.qml ProgressBar/KeyframeGroup.qml ProgressBar/ProgressBar.ui.qml @@ -46,98 +66,145 @@ set(qml_files ProgressBar/Timeline.qml ProgressBar/TimelineAnimation.qml RootWithoutId.qml + Satisfaction.qml SelectionRectangle.qml + ShadowedObjectName.qml + ShadowedObjectNameDerived.qml + StoreMetaEnum.qml Test.qml TestCase.qml + Variable.qml WindowDerived.qml aliasLookup.qml ambiguous1/Ambiguous.qml ambiguous2/Ambiguous.qml + ambiguousAs.qml ambiguousSignals.qml anchorsFill.qml argumentConversion.qml array.qml + arrayCtor.qml asCast.qml attachedBaseEnum.qml badSequence.qml + basicBlocksWithBackJump.qml + basicBlocksWithBackJump_infinite.qml + basicDTZ.qml bindToValueType.qml blockComments.qml + boolCoercions.qml + boolPointerMerge.qml boundComponents.qml callContextPropertyLookupResult.qml callWithSpread.qml childobject.qml colorAsVariant.qml colorString.qml - consoleObject.qml + compareOriginals.qml + comparisonTypes.qml componentReturnType.qml compositeTypeMethod.qml compositesingleton.qml + consoleObject.qml + consoleTrace.qml construct.qml contextParam.qml conversionDecrement.qml + conversionInDeadCode.qml conversions.qml conversions2.qml + convertPrimitiveToVar.qml + convertQJSPrimitiveValueToIntegral.qml + convertToOriginalReadAcumulatorForUnaryOperators.qml curlygrouped.qml cycleHead.qml + dateConstruction.qml dateConversions.qml deadShoeSize.qml deadStoreLoop.qml dialog.qml + dialogButtonBox.qml dynamicscene.qml + enforceSignature.qml enumConversion.qml + enumFromBadSingleton.qml enumInvalid.qml enumLookup.qml + enumMarkedAsFlag.qml enumProblems.qml enumScope.qml enumsInOtherObject.qml enumsUser.qml equalityQObjects.qml + equalityQUrl.qml + equalityTestsWithNullOrUndefined.qml equalityVarAndNonStorable.qml equalsUndefined.qml + exceptionFromInner.qml excessiveParameters.qml extendedTypes.qml + extra/extra.qml failures.qml fallbacklookups.qml + fallbackresettable.qml fileDialog.qml + flagEnum.qml fromBoolValue.qml - functionLookup.qml funcWithParams.qml + functionLookup.qml functionReturningVoid.qml functionTakingVar.qml + getOptionalLookup.qml globals.qml - hidden/Main.qml - hidden/Style.qml idAccess.qml + ignoredFunctionReturn.qml immediateQuit.qml imports/QmlBench/Globals.qml importsFromImportPath.qml + indirectlyShadowable.qml infinities.qml infinitiesToInt.qml - invisibleBase.qml - invisibleTypes.qml - invisibleListElementType.qml intEnumCompare.qml intOverflow.qml + intToEnum.qml interactive.qml interceptor.qml + internalConversion.qml + invisibleBase.qml + invisibleListElementType.qml + invisibleTypes.qml isnan.qml + iteration.qml javaScriptArgument.qml + jsArrayMethods.qml + jsArrayMethodsUntyped.qml + jsArrayMethodsWithParams.qml + jsArrayMethodsWithParamsUntyped.qml jsMathObject.qml jsimport.qml jsmoduleimport.qml layouts.qml - library.js letAndConst.qml + library.js listAsArgument.qml + listConversion.qml listIndices.qml + listOfInvisible.qml listPropertyAsModel.qml + listToString.qml listlength.qml math.qml + mathMinMax.qml mathOperations.qml + mathStaticProperties.qml + mergedObjectRead.qml + mergedObjectWrite.qml + methodOnListLookup.qml methods.qml modulePrefix.qml moveRegVoid.qml multiforeign.qml + multipleCtors.qml namespaceWithEnum.qml noBindingLoop.qml noQQmlData.qml @@ -146,9 +213,14 @@ set(qml_files notEqualsInt.qml notNotString.qml nullAccess.qml + nullAccessInsideSignalHandler.qml nullComparison.qml + nullishCoalescing.qml numbersInJsPrimitive.qml objectInVar.qml + objectLookupOnListElement.qml + objectWithStringListMethod.qml + optionalComparison.qml outOfBounds.qml overriddenMember.qml ownProperty.qml @@ -157,48 +229,76 @@ set(qml_files popContextAfterRet.qml prefixedMetaType.qml pressAndHoldButton.qml - registerelimination.qml + qtbug113150.qml + reduceWithNullThis.qml + readEnumFromInstance.qml + readonlyListProperty.qml registerPropagation.qml + registerelimination.qml + renameAdjust.qml + resettable.qml + returnAfterReject.qml revisions.qml + scopeIdLookup.qml scopeVsObject.qml + scopedEnum.qml script.js script.mjs + sequenceToIterable.qml + setLookupConversion.qml + setLookupOriginalScope.qml + shadowedAsCasts.qml + shadowedMethod.qml + shadowedPrimitiveCmpEqNull.qml shared/Slider.qml shifts.qml signal.qml signalHandler.qml signalIndexMismatch.qml + signalsWithLists.qml signatureIgnored.qml specificParent.qml storeElementSideEffects.qml stringArg.qml stringLength.qml stringToByteArray.qml + structuredValueType.qml testlogger.js text.qml themerbad.qml themergood.qml + thisObject.qml throwObjectName.qml toString.qml + topLevelComponent.qml translation.qml + trigraphs.qml trivialSignalHandler.qml typePropagationLoop.qml typePropertyClash.qml typedArray.qml undefinedResets.qml + undefinedToDouble.qml unknownAttached.qml unknownParameter.qml unstoredUndefined.qml unusedAttached.qml urlString.qml usingCxxTypesFromFileImports.qml + valueTypeCast.qml valueTypeCopy.qml + valueTypeDefault.qml valueTypeLists.qml valueTypeProperty.qml valueTypeReference.qml + variantMap.qml + variantMapLookup.qml + variantReturn.qml variantlist.qml versionmismatch.qml + voidConversion.qml voidfunction.qml + writeback.qml dummy_imports.qml ) @@ -214,21 +314,88 @@ set_source_files_properties("shared/Slider.qml" set_source_files_properties("hidden/Style.qml" PROPERTIES QT_QML_SINGLETON_TYPE TRUE) +qt_policy(SET QTP0001 NEW) + +# Add the module for the hidden files before setting QTP0004, so that we don't add a qmldir in +# "hidden". That would defeat the purpose. + +qt_add_library(codegen_test_hidden STATIC) +qt_autogen_tools_initial_setup(codegen_test_hidden) + +set_target_properties(codegen_test_hidden PROPERTIES + # We really want qmlcachegen here, even if qmlsc is available + QT_QMLCACHEGEN_EXECUTABLE qmlcachegen + QT_QMLCACHEGEN_ARGUMENTS --validate-basic-blocks +) + +target_compile_definitions(codegen_test_hidden PUBLIC + -DGENERATED_CPP_FOLDER="${CMAKE_CURRENT_BINARY_DIR}/.rcc/qmlcache" +) + +qt_policy(SET QTP0004 OLD) +qt6_add_qml_module(codegen_test_hidden + URI HiddenTestTypes + QML_FILES + hidden/Main.qml + hidden/Style.qml + OUTPUT_DIRECTORY HiddenTestTypes + __QT_INTERNAL_DISAMBIGUATE_QMLDIR_RESOURCE +) + +add_dependencies(codegen_test_hidden Qt::Quick) + +qt_autogen_tools_initial_setup(codegen_test_hiddenplugin) + +qt_policy(SET QTP0004 NEW) + +qt_add_library(codegen_test_stringbuilder STATIC) +qt_autogen_tools_initial_setup(codegen_test_stringbuilder) + +set_target_properties(codegen_test_stringbuilder PROPERTIES + # We really want qmlcachegen here, even if qmlsc is available + QT_QMLCACHEGEN_EXECUTABLE qmlcachegen + QT_QMLCACHEGEN_ARGUMENTS --validate-basic-blocks +) + +target_compile_definitions(codegen_test_stringbuilder PRIVATE + -DGENERATED_CPP_FOLDER="${CMAKE_CURRENT_BINARY_DIR}/.rcc/qmlcache" + QT_USE_QSTRINGBUILDER +) + +qt6_add_qml_module(codegen_test_stringbuilder + URI StringBuilderTestTypes + SOURCES + writableVariantMap.h + QML_FILES + writeVariantMap.qml + OUTPUT_DIRECTORY stringbuilderTestTypes + __QT_INTERNAL_DISAMBIGUATE_QMLDIR_RESOURCE +) + +qt_autogen_tools_initial_setup(codegen_test_stringbuilderplugin) + qt_add_library(codegen_test_module STATIC) qt_autogen_tools_initial_setup(codegen_test_module) set_target_properties(codegen_test_module PROPERTIES # We really want qmlcachegen here, even if qmlsc is available QT_QMLCACHEGEN_EXECUTABLE qmlcachegen + QT_QMLCACHEGEN_ARGUMENTS --validate-basic-blocks +) + + + +target_compile_definitions(codegen_test_module PUBLIC + -DGENERATED_CPP_FOLDER="${CMAKE_CURRENT_BINARY_DIR}/.rcc/qmlcache" ) qt6_add_qml_module(codegen_test_module VERSION 1.5 URI TestTypes IMPORT_PATH "${CMAKE_CURRENT_SOURCE_DIR}/imports/" - AUTO_RESOURCE_PREFIX DEPENDENCIES QtQuick + QtQuick.Controls QtQuick.Templates QtQuick.Shapes SOURCES @@ -238,8 +405,96 @@ qt6_add_qml_module(codegen_test_module RESOURCES ${resource_files} OUTPUT_DIRECTORY TestTypes # Make sure tst_qmlcachegen doesn't see our output + __QT_INTERNAL_DISAMBIGUATE_QMLDIR_RESOURCE ) +if(${CMAKE_VERSION} GREATER_EQUAL "3.19.0") + qt_target_qml_sources(codegen_test_module + QML_FILES extra2/extra.qml + ) +else() + target_compile_definitions(codegen_test_module PUBLIC + -DVERY_OLD_CMAKE=1 + ) +endif() + add_dependencies(codegen_test_module Qt::Quick Qt::QuickTemplates2 Qt::QuickShapesPrivate) qt_autogen_tools_initial_setup(codegen_test_moduleplugin) + + +qt_add_library(codegen_test_module_verify STATIC) +qt_autogen_tools_initial_setup(codegen_test_module_verify) + +set_target_properties(codegen_test_module_verify PROPERTIES + # We really want qmlcachegen here, even if qmlsc is available + QT_QMLCACHEGEN_EXECUTABLE qmlcachegen + QT_QMLCACHEGEN_ARGUMENTS --validate-basic-blocks +) + + +qt6_add_qml_module(codegen_test_module_verify + VERSION 1.5 + URI TestTypes + IMPORT_PATH "${CMAKE_CURRENT_SOURCE_DIR}/imports/" + DEPENDENCIES + QtQuick + QtQuick.Controls + QtQuick.Templates + QtQuick.Shapes + SOURCES + ${cpp_sources} + QML_FILES + ${qml_files} + RESOURCES + ${resource_files} + OUTPUT_DIRECTORY verify/TestTypes # Make sure tst_qmlcachegen doesn't see our output + TARGET_PATH verify/TestTypes # Different target path to avoid resource file name clashes + __QT_INTERNAL_DISAMBIGUATE_QMLDIR_RESOURCE +) + +add_dependencies(codegen_test_module_verify Qt::Quick Qt::QuickTemplates2 Qt::QuickShapesPrivate) + +qt_autogen_tools_initial_setup(codegen_test_module_verifyplugin) + + +qt_internal_add_test(tst_qmlcppcodegen_verify + SOURCES + tst_qmlcppcodegen_verify.cpp +) + +add_dependencies(tst_qmlcppcodegen_verify codegen_test_module codegen_test_module_verify) + +set(a_files "") +set(b_files "") + +foreach(qml_file IN LISTS qml_files) + string(REGEX REPLACE "\\.(js|mjs|qml)$" "_\\1" compiled_file ${qml_file}) + string(REGEX REPLACE "[$#?]+" "_" compiled_file ${compiled_file}) + + list(APPEND + a_files + "${CMAKE_CURRENT_BINARY_DIR}/.rcc/qmlcache/codegen_test_module_${compiled_file}.cpp") + + list(APPEND + b_files + "${CMAKE_CURRENT_BINARY_DIR}/.rcc/qmlcache/codegen_test_module_verify_${compiled_file}.cpp") +endforeach() + +qt_add_resources(tst_qmlcppcodegen_verify "a" + PREFIX + "/a" + FILES + ${a_files} + BASE + "${CMAKE_CURRENT_BINARY_DIR}/.rcc/qmlcache/" +) + +qt_add_resources(tst_qmlcppcodegen_verify "b" + PREFIX + "/b" + FILES + ${b_files} + BASE + "${CMAKE_CURRENT_BINARY_DIR}/.rcc/qmlcache/" +) diff --git a/tests/auto/qml/qmlcppcodegen/data/CppMethodListReturnType.qml b/tests/auto/qml/qmlcppcodegen/data/CppMethodListReturnType.qml new file mode 100644 index 0000000000..9c3ce4e877 --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/CppMethodListReturnType.qml @@ -0,0 +1,12 @@ +pragma Strict + +import QtQuick +import TestTypes + +Item { + ListProvider { + id: listProvider + } + + property var list: listProvider.intList() +} diff --git a/tests/auto/qml/qmlcppcodegen/data/Dummy2.qml b/tests/auto/qml/qmlcppcodegen/data/Dummy2.qml new file mode 100644 index 0000000000..a3bbef1888 --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/Dummy2.qml @@ -0,0 +1,18 @@ +// Copyright (C) 2020 The Qt Company Ltd. + +import QtQml + +QtObject { + property int value + property Dummy2 child + property int dummyEnum + + signal triggered() + signal signalWithArg(int one, bool two) + property real onValue + property real offValue + + function someFunction(a: int, b: bool, c: Dummy2, d: real, e: int) : int { return 42 } + property string strProp + function concat(a: string, b: string) : string { return a + b } +} diff --git a/tests/auto/qml/qmlcppcodegen/data/EditConstraint.qml b/tests/auto/qml/qmlcppcodegen/data/EditConstraint.qml new file mode 100644 index 0000000000..ce278fbdf9 --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/EditConstraint.qml @@ -0,0 +1,6 @@ +pragma Strict +import QtQml + +QtObject { + property Variable myOutput +} diff --git a/tests/auto/qml/qmlcppcodegen/data/GetOptionalLookupOnQJSValueNonStrict.qml b/tests/auto/qml/qmlcppcodegen/data/GetOptionalLookupOnQJSValueNonStrict.qml new file mode 100644 index 0000000000..5a89e996b4 --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/GetOptionalLookupOnQJSValueNonStrict.qml @@ -0,0 +1,7 @@ +import QtQml +import TestTypes + +QtObject { + property Action action: Action { } + property bool b: action?.visible +} diff --git a/tests/auto/qml/qmlcppcodegen/data/GetOptionalLookupShadowed.qml b/tests/auto/qml/qmlcppcodegen/data/GetOptionalLookupShadowed.qml new file mode 100644 index 0000000000..eacefc3017 --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/GetOptionalLookupShadowed.qml @@ -0,0 +1,19 @@ +pragma Strict + +import QtQml +import QtQuick + +QtObject { + id: root + + component Base : QtObject { + property int i: 1 + } + + component Derived : Base { + property string i: "a" + } + + property Base base: Derived { } + property var res: root.base?.i +} diff --git a/tests/auto/qml/qmlcppcodegen/data/NotificationItem.qml b/tests/auto/qml/qmlcppcodegen/data/NotificationItem.qml new file mode 100644 index 0000000000..fba4df6453 --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/NotificationItem.qml @@ -0,0 +1,7 @@ +import QtQml +import TestTypes as MobileShell + +QtObject { + id: notificationItem + objectName: MobileShell.NotificationsUtils.determineNotificationHeadingText(notificationItem) +} diff --git a/tests/auto/qml/qmlcppcodegen/data/NotificationsUtils.js b/tests/auto/qml/qmlcppcodegen/data/NotificationsUtils.js new file mode 100644 index 0000000000..079270e1b9 --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/NotificationsUtils.js @@ -0,0 +1,3 @@ +function determineNotificationHeadingText(notificationItem) { + return "heading"; +} diff --git a/tests/auto/qml/qmlcppcodegen/data/Panel.qml b/tests/auto/qml/qmlcppcodegen/data/Panel.qml index 84e926b8d2..7f589d23e8 100644 --- a/tests/auto/qml/qmlcppcodegen/data/Panel.qml +++ b/tests/auto/qml/qmlcppcodegen/data/Panel.qml @@ -1,5 +1,5 @@ // Copyright (C) 2017 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only import QtQuick 2.0 diff --git a/tests/auto/qml/qmlcppcodegen/data/Planner.qml b/tests/auto/qml/qmlcppcodegen/data/Planner.qml new file mode 100644 index 0000000000..ddb2fff053 --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/Planner.qml @@ -0,0 +1,50 @@ +pragma Strict +import QtQml + +QtObject { + id: planner + property Variable last: Variable { value: 10 } + + function newMark() : int { + return 5; + } + + function addPropagate(i: int) : bool { + return false; + } + + function typeErasedRemoveOne(v: QtObject) { removeOne(v as Variable) } + + // Work with various shadowable members and return values. + function removeOne(v: Variable) { + let vDeterminedBy = v.determinedBy; + for (let i = 0, length = v.length(); i < length; ++i) { + let next = v.constraint(i) as BaseConstraint; + if (next.satisfaction === Satisfaction.NONE) + objectName += "n" + else if (next !== vDeterminedBy) + objectName += "d" + else + objectName += "x" + } + } + + function typeErasedRun(c: QtObject) { run(c as BaseConstraint) } + + function run(initial: BaseConstraint) { + let mark = planner.newMark(); + let c = initial; + + let output = c.output as Variable; + if (output.mark !== mark && c.inputsKnown(mark)) { + output.mark = mark; + } + } + + function verify(i: int) { + if (last.value !== i) + console.error("failed", last.value, i); + else + console.log("success") + } +} diff --git a/tests/auto/qml/qmlcppcodegen/data/ProgressBar/ProgressBar.ui.qml b/tests/auto/qml/qmlcppcodegen/data/ProgressBar/ProgressBar.ui.qml index 21a3832366..a44af04a50 100644 --- a/tests/auto/qml/qmlcppcodegen/data/ProgressBar/ProgressBar.ui.qml +++ b/tests/auto/qml/qmlcppcodegen/data/ProgressBar/ProgressBar.ui.qml @@ -1,5 +1,5 @@ // Copyright (C) 2019 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only import QtQuick 2.9 import QtQuick.Window 2.3 diff --git a/tests/auto/qml/qmlcppcodegen/data/ProgressBar/Root.qml b/tests/auto/qml/qmlcppcodegen/data/ProgressBar/Root.qml index 707f0d9be9..43ff1b62b1 100644 --- a/tests/auto/qml/qmlcppcodegen/data/ProgressBar/Root.qml +++ b/tests/auto/qml/qmlcppcodegen/data/ProgressBar/Root.qml @@ -1,5 +1,5 @@ // Copyright (C) 2019 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only import QtQuick 2.9 diff --git a/tests/auto/qml/qmlcppcodegen/data/ProgressBar/TimelineAnimation.qml b/tests/auto/qml/qmlcppcodegen/data/ProgressBar/TimelineAnimation.qml index b97e8956bf..75ad2245f2 100644 --- a/tests/auto/qml/qmlcppcodegen/data/ProgressBar/TimelineAnimation.qml +++ b/tests/auto/qml/qmlcppcodegen/data/ProgressBar/TimelineAnimation.qml @@ -2,5 +2,5 @@ import QtQuick NumberAnimation { property bool pingPong - signal finished() + signal finishedEvil() } diff --git a/tests/auto/qml/qmlcppcodegen/data/Satisfaction.qml b/tests/auto/qml/qmlcppcodegen/data/Satisfaction.qml new file mode 100644 index 0000000000..74968c65ea --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/Satisfaction.qml @@ -0,0 +1,10 @@ +pragma Strict +import QtQml + +QtObject { + enum Value { + NONE = 0, + FORWARD = 1, + BACKWARD = 2 + } +} diff --git a/tests/auto/qml/qmlcppcodegen/data/ShadowedObjectName.qml b/tests/auto/qml/qmlcppcodegen/data/ShadowedObjectName.qml new file mode 100644 index 0000000000..f079f4a94e --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/ShadowedObjectName.qml @@ -0,0 +1,6 @@ +pragma Strict +import QtQml + +QtObject { + property int objectName: 12 +} diff --git a/tests/auto/qml/qmlcppcodegen/data/ShadowedObjectNameDerived.qml b/tests/auto/qml/qmlcppcodegen/data/ShadowedObjectNameDerived.qml new file mode 100644 index 0000000000..f988cd6bc9 --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/ShadowedObjectNameDerived.qml @@ -0,0 +1,6 @@ +pragma Strict +import QtQml + +ShadowedObjectName { + property double objectName: 17.4 +} diff --git a/tests/auto/qml/qmlcppcodegen/data/StoreMetaEnum.qml b/tests/auto/qml/qmlcppcodegen/data/StoreMetaEnum.qml new file mode 100644 index 0000000000..eb3da15a3a --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/StoreMetaEnum.qml @@ -0,0 +1,12 @@ +import QtQml + +QtObject { + enum Foo { + Bar, + Baz + } + + property var eF: StoreMetaEnum.Foo + property int bar: eF.Bar + property int baz: eF.Baz +} diff --git a/tests/auto/qml/qmlcppcodegen/data/Variable.qml b/tests/auto/qml/qmlcppcodegen/data/Variable.qml new file mode 100644 index 0000000000..f5af97757f --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/Variable.qml @@ -0,0 +1,22 @@ +pragma Strict +import QtQml + +QtObject { + id: variable + property int value: 0 + property int mark: 0 + property BaseConstraint determinedBy: null + property list<BaseConstraint> constraints: [ + BaseConstraint { + satisfaction: variable.value == 0 ? Satisfaction.NONE : Satisfaction.FORWARD + } + ] + + function length(): int { + return constraints.length + } + + function constraint(i: int) : BaseConstraint { + return constraints[i]; + } +} diff --git a/tests/auto/qml/qmlcppcodegen/data/ambiguous.h b/tests/auto/qml/qmlcppcodegen/data/ambiguous.h index 5ea20dbcd1..8f964d593c 100644 --- a/tests/auto/qml/qmlcppcodegen/data/ambiguous.h +++ b/tests/auto/qml/qmlcppcodegen/data/ambiguous.h @@ -1,5 +1,5 @@ // Copyright (C) 2022 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #ifndef AMBIGUOUS_H #define AMBIGUOUS_H diff --git a/tests/auto/qml/qmlcppcodegen/data/ambiguousAs.qml b/tests/auto/qml/qmlcppcodegen/data/ambiguousAs.qml new file mode 100644 index 0000000000..2b7cbd593d --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/ambiguousAs.qml @@ -0,0 +1,15 @@ +pragma Strict +import QtQml + +QtObject { + id: self + property bool useSelf: true + property QtObject other: { + var a; + if (useSelf) + a = self + else + a = 15 + return a as QtObject + } +} diff --git a/tests/auto/qml/qmlcppcodegen/data/arrayCtor.qml b/tests/auto/qml/qmlcppcodegen/data/arrayCtor.qml new file mode 100644 index 0000000000..9886a14cb1 --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/arrayCtor.qml @@ -0,0 +1,11 @@ +pragma Strict +import QML + +QtObject { + property list<int> defaultCtor: new Array() + property list<int> oneArgCtor: new Array(5) + property list<int> multiArgCtor: new Array(2, 3, 3, 4) + property list<bool> arrayTrue: new Array(true) + property list<bool> arrayFalse: new Array(false) + property list<real> arrayNegative: new Array(-14) +} diff --git a/tests/auto/qml/qmlcppcodegen/data/asCast.qml b/tests/auto/qml/qmlcppcodegen/data/asCast.qml index 1befc08d0a..cb8155ca6c 100644 --- a/tests/auto/qml/qmlcppcodegen/data/asCast.qml +++ b/tests/auto/qml/qmlcppcodegen/data/asCast.qml @@ -28,4 +28,14 @@ Item { property QtObject dummyAsItem: dummy as Item property QtObject dummyAsRectangle: dummy as Rectangle property QtObject dummyAsDummy: dummy as Dummy + + property QtObject nullAsObject: null as QtObject + property QtObject nullAsItem: null as Item + property QtObject nullAsRectangle: null as Rectangle + property QtObject nullAsDummy: null as Dummy + + property QtObject undefinedAsObject: undefined as QtObject + property QtObject undefinedAsItem: undefined as Item + property QtObject undefinedAsRectangle: undefined as Rectangle + property QtObject undefinedAsDummy: undefined as Dummy } diff --git a/tests/auto/qml/qmlcppcodegen/data/basicBlocksWithBackJump.qml b/tests/auto/qml/qmlcppcodegen/data/basicBlocksWithBackJump.qml new file mode 100644 index 0000000000..5b254fe494 --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/basicBlocksWithBackJump.qml @@ -0,0 +1,33 @@ +pragma Strict +import QtQml + +QtObject { + function t1() { + let i = 0 + let foo = false + if (true) { + for (i = 0; i < 42 ; ++i) {} + } else { + console.log(foo) + } + } + + function t2() { + let foo = false + if (false) { + while(Math.random() < 0.5) {} + } else { + console.log(foo) + } + } + + function t3() { + let foo = false + if (Math.random() < 0.5) { + console.log(foo) + } else { + while(Math.random() < 0.5) {} + console.log(foo) + } + } +} diff --git a/tests/auto/qml/qmlcppcodegen/data/basicBlocksWithBackJump_infinite.qml b/tests/auto/qml/qmlcppcodegen/data/basicBlocksWithBackJump_infinite.qml new file mode 100644 index 0000000000..997ff68736 --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/basicBlocksWithBackJump_infinite.qml @@ -0,0 +1,13 @@ +pragma Strict +import QtQml + +QtObject { + function infinite() { + let foo = false + if (true) { + while (true) {} + } else { + console.log(foo) + } + } +} diff --git a/tests/auto/qml/qmlcppcodegen/data/basicDTZ.qml b/tests/auto/qml/qmlcppcodegen/data/basicDTZ.qml new file mode 100644 index 0000000000..bc9506a533 --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/basicDTZ.qml @@ -0,0 +1,40 @@ +pragma Strict +import QtQml + +QtObject { + id: win + property real width: 640 + property real height: 480 + property string title: "none" + + function t1(): void { + const w = win.width + const h = win.height + + if (w > 0 && h > 0) { + const wByH = h / 3.0 * 4.0 + } + } + + function t2(): void { + let i = 42 + win.title = "Foo " + i + + for (let j = 0; j < 10; j++) { + win.title = "Bar " + j + } + + for (let k = 0; k < i; k++) { + win.title = "Baz " + k + } + } + + function t3(): void { + let v1 = 1 + let v2 = 2 + + if (true) { + v1 = v2 + } + } +} diff --git a/tests/auto/qml/qmlcppcodegen/data/birthdayparty.cpp b/tests/auto/qml/qmlcppcodegen/data/birthdayparty.cpp index 048459f03a..10a2c90b38 100644 --- a/tests/auto/qml/qmlcppcodegen/data/birthdayparty.cpp +++ b/tests/auto/qml/qmlcppcodegen/data/birthdayparty.cpp @@ -1,5 +1,5 @@ // Copyright (C) 2022 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include "birthdayparty.h" @@ -36,6 +36,23 @@ Person *BirthdayParty::guest(int index) const return m_guests.at(index); } +QStringList BirthdayParty::guestNames() const +{ + QStringList names; + for (Person *guest: m_guests) + names.append(guest->name()); + return names; +} + +QVariantList BirthdayParty::stuffs() const +{ + return QVariantList({ + QVariant::fromValue(objectName()), + QVariant::fromValue(m_guests.size()), + QVariant::fromValue(m_host) + }); +} + void BirthdayParty::invite(const QString &name) { auto *person = new Person(this); diff --git a/tests/auto/qml/qmlcppcodegen/data/birthdayparty.h b/tests/auto/qml/qmlcppcodegen/data/birthdayparty.h index a6871c39b1..8dd640c67f 100644 --- a/tests/auto/qml/qmlcppcodegen/data/birthdayparty.h +++ b/tests/auto/qml/qmlcppcodegen/data/birthdayparty.h @@ -1,5 +1,5 @@ // Copyright (C) 2022 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #ifndef BIRTHDAYPARTY_H #define BIRTHDAYPARTY_H @@ -52,7 +52,6 @@ private: }; struct Foozle { - Q_GADGET int foo = 1; }; @@ -61,6 +60,8 @@ class BirthdayParty : public QObject Q_OBJECT Q_PROPERTY(Person *host READ host WRITE setHost NOTIFY hostChanged FINAL) Q_PROPERTY(QQmlListProperty<Person> guests READ guests) + Q_PROPERTY(QStringList guestNames READ guestNames FINAL) + Q_PROPERTY(QVariantList stuffs READ stuffs FINAL) QML_ELEMENT QML_ATTACHED(BirthdayPartyAttached) QML_EXTENDED(BirthDayPartyExtended) @@ -74,6 +75,9 @@ public: int guestCount() const; Person *guest(int) const; + QStringList guestNames() const; + QVariantList stuffs() const; + Q_INVOKABLE void invite(const QString &name); static BirthdayPartyAttached *qmlAttachedProperties(QObject *object); diff --git a/tests/auto/qml/qmlcppcodegen/data/boolCoercions.qml b/tests/auto/qml/qmlcppcodegen/data/boolCoercions.qml new file mode 100644 index 0000000000..6b8ebb3f38 --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/boolCoercions.qml @@ -0,0 +1,45 @@ +pragma Strict +import QtQml + +QtObject { + property rect a + property bool t1: a + + property int c: 1 + property bool t2: c + + property url e: "qrc:/ab/c.txt" + property bool t3: e + + property string f: "a" + property bool t4: f + + property var j: 1 + property bool t5: j + + id: k + property bool t6: k + + property date l + property bool t7: l + + property url m + property bool t8: m + + + + property int b: 0 + property bool f1: b + + property QtObject d: null + property bool f2: d + + property string g + property bool f3: g + + property var h: undefined + property bool f4: h + + property var i: null + property bool f5: i +} diff --git a/tests/auto/qml/qmlcppcodegen/data/boolPointerMerge.qml b/tests/auto/qml/qmlcppcodegen/data/boolPointerMerge.qml new file mode 100644 index 0000000000..b2d4d7c5d0 --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/boolPointerMerge.qml @@ -0,0 +1,15 @@ +pragma Strict +import TestTypes +import QtQuick + +Loader { + id: self + source: "BaseMember.qml" + property int ppp: -99 + + onItemChanged: { + var base = item as BaseMember; + if (base) + base.ppp = ppp + } +} diff --git a/tests/auto/qml/qmlcppcodegen/data/childobject.qml b/tests/auto/qml/qmlcppcodegen/data/childobject.qml index 3775ee16bc..76ad8fbbb2 100644 --- a/tests/auto/qml/qmlcppcodegen/data/childobject.qml +++ b/tests/auto/qml/qmlcppcodegen/data/childobject.qml @@ -4,6 +4,19 @@ import TestTypes QtObject { property ObjectWithMethod child: ObjectWithMethod { objectName: "kraut" + + function doString() { overloaded("string"); } + function doNumber() { overloaded(5.2); } + function doArray() { overloaded({a: 2, b: 3, c: 3}); } + + function doString2() { overloaded2("string"); } + function doNumber2() { overloaded2(5.2); } + + // Artificially pass an extra argument to avoid choosing the "string" overload. + // Unfortunately this is still order-dependent on the metaobject level. + function doArray2() { overloaded2({a: 2, b: 3, c: 3}, 1); } + + function doFoo() { foo(this); } } objectName: child.objectName property int doneThing: child.doThing() diff --git a/tests/auto/qml/qmlcppcodegen/data/compareOriginals.qml b/tests/auto/qml/qmlcppcodegen/data/compareOriginals.qml new file mode 100644 index 0000000000..3d40ffee62 --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/compareOriginals.qml @@ -0,0 +1,39 @@ +pragma Strict +import QtQml + +QtObject { + component Variable: QtObject { + property int value: 4 + } + + property Variable first: Variable {} + property Variable last: Variable { + id: last + } + + property int compareOriginals: { + var matches = 0; + for (var i = 0; i < 6; i++) { + first.value = i; // do a shadowed assignment + if (last.value != i) + ++matches + } + return matches; + } + + property bool optionalThis: { + var a + if (2 == 2) + a = this + else + a = undefined + + var b + if (2 == 2) + b = this + else + b = undefined + + return a === b + } +} diff --git a/tests/auto/qml/qmlcppcodegen/data/comparisonTypes.qml b/tests/auto/qml/qmlcppcodegen/data/comparisonTypes.qml new file mode 100644 index 0000000000..423cd55ed4 --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/comparisonTypes.qml @@ -0,0 +1,54 @@ +import QtQml + +QtObject { + component Variable: QtObject { + property int value: 4 + } + + component VariableShadow: Variable { + property string value: "1" + } + + property Variable last: VariableShadow {} + + function find(n: int) : int { + var found = 0 + for (var i = 0; i < n; i++) { + if (last.value == i) + ++found + } + return found; + } + + function findStrict(n: int) : int { + var found = 0 + for (var i = 0; i < n; i++) { + if (last.value === i) + ++found + } + return found; + } + + function findNot(n: int) : int { + var found = 0 + for (var i = 0; i < n; i++) { + if (last.value != i) + ++found + } + return found; + } + + function findNotStrict(n: int) : int { + var found = 0 + for (var i = 0; i < n; i++) { + if (last.value !== i) + ++found + } + return found; + } + + property int found: find(3) + property int foundStrict: findStrict(10) + property int foundNot: findNot(3) + property int foundNotStrict: findNotStrict(10) +} diff --git a/tests/auto/qml/qmlcppcodegen/data/consoleTrace.qml b/tests/auto/qml/qmlcppcodegen/data/consoleTrace.qml new file mode 100644 index 0000000000..a80af89ddd --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/consoleTrace.qml @@ -0,0 +1,8 @@ +import QtQml + +QtObject { + function a() { b() } + function b() { c() } + function c() { console.trace() } + Component.onCompleted: a() +} diff --git a/tests/auto/qml/qmlcppcodegen/data/conversionInDeadCode.qml b/tests/auto/qml/qmlcppcodegen/data/conversionInDeadCode.qml new file mode 100644 index 0000000000..b2e7b40c00 --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/conversionInDeadCode.qml @@ -0,0 +1,32 @@ +pragma Strict +import QtQml + +QtObject { + // This does not look like dead code, but each access to 'result' generates a + // DeadTemoralZoneCheck instruction that we ignore when compiling to C++ + // after checking statically that 'result' is alive throughout the function. + // Therefore, this function is a torture test for the dead code elimination. + function calc(a: int, b: int) : int { + let result = a; + if (b < 0) { + if (b < -1) + result -= b; + if (b < -2) + result /= b; + } else { + if (b > 1) + result *= b; + if (b > 2) + result += b; + } + return result; + } + + property int a: calc(10, -3); + property int b: calc(10, -2); + property int c: calc(10, -1); + property int d: calc(10, 0); + property int e: calc(10, 1); + property int f: calc(10, 2); + property int g: calc(10, 3); +} diff --git a/tests/auto/qml/qmlcppcodegen/data/conversions2.qml b/tests/auto/qml/qmlcppcodegen/data/conversions2.qml index c3a9414ae2..d0d1fc9b8f 100644 --- a/tests/auto/qml/qmlcppcodegen/data/conversions2.qml +++ b/tests/auto/qml/qmlcppcodegen/data/conversions2.qml @@ -94,6 +94,8 @@ Item { } function qtest_signalHandlerName(sn) { + // Warning: to not test for signal handlers like this in actual code. + // Use the helper methods in QQmlSignalNames instead. if (sn.substr(0, 2) === "on" && sn[2] === sn[2].toUpperCase()) return sn return "on" + sn.substr(0, 1).toUpperCase() + sn.substr(1) diff --git a/tests/auto/qml/qmlcppcodegen/data/convertPrimitiveToVar.qml b/tests/auto/qml/qmlcppcodegen/data/convertPrimitiveToVar.qml new file mode 100644 index 0000000000..f7c2cc4058 --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/convertPrimitiveToVar.qml @@ -0,0 +1,18 @@ +pragma Strict +import QtQml + +QtObject { + id: foo + + property int offsetValue + + function send(data : variant) { + } + + Component.onCompleted: () => { + let deltaOffset = 42 + deltaOffset -= 1 + foo.offsetValue = deltaOffset + foo.send({offset: deltaOffset}) + } +} diff --git a/tests/auto/qml/qmlcppcodegen/data/convertQJSPrimitiveValueToIntegral.h b/tests/auto/qml/qmlcppcodegen/data/convertQJSPrimitiveValueToIntegral.h new file mode 100644 index 0000000000..459dd62374 --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/convertQJSPrimitiveValueToIntegral.h @@ -0,0 +1,34 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#ifndef CONVERTQJSPRIMITIVEVALUETOINTEGRAL_H +#define CONVERTQJSPRIMITIVEVALUETOINTEGRAL_H + +#include <QtCore/qobject.h> +#include <QtQml/qqml.h> + +class Moo485 : public QObject +{ + Q_OBJECT + QML_ELEMENT + + Q_PROPERTY(int uid READ uid CONSTANT FINAL) + +public: + explicit Moo485(QObject *parent = nullptr) : QObject(parent) { } + int uid() const { return 4711; } +}; + +class Foo485 : public QObject +{ + Q_OBJECT + QML_ELEMENT + + Q_PROPERTY(quint16 uid MEMBER m_uid FINAL) + +public: + explicit Foo485(QObject *parent = nullptr) : QObject(parent) { } + quint16 m_uid = 0; +}; + +#endif // CONVERTQJSPRIMITIVEVALUETOINTEGRAL_H diff --git a/tests/auto/qml/qmlcppcodegen/data/convertQJSPrimitiveValueToIntegral.qml b/tests/auto/qml/qmlcppcodegen/data/convertQJSPrimitiveValueToIntegral.qml new file mode 100644 index 0000000000..6a15d6f775 --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/convertQJSPrimitiveValueToIntegral.qml @@ -0,0 +1,13 @@ +import QtQuick +import TestTypes + + +Item { + id: root + + property Moo485 moo + + readonly property Foo485 foo: Foo485 { + uid: root.moo.uid ?? 0xFFFF + } +} diff --git a/tests/auto/qml/qmlcppcodegen/data/convertToOriginalReadAcumulatorForUnaryOperators.qml b/tests/auto/qml/qmlcppcodegen/data/convertToOriginalReadAcumulatorForUnaryOperators.qml new file mode 100644 index 0000000000..6eb14bba57 --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/convertToOriginalReadAcumulatorForUnaryOperators.qml @@ -0,0 +1,13 @@ +pragma Strict +import QtQml + +QtObject { + id: self + property int i: 0 + property Planner planner: null + + function satisfy(mark: int) { + planner.addPropagate(mark); + i = +mark; + } +} diff --git a/tests/auto/qml/qmlcppcodegen/data/cppbaseclass.h b/tests/auto/qml/qmlcppcodegen/data/cppbaseclass.h index eecc3d968e..812415ae24 100644 --- a/tests/auto/qml/qmlcppcodegen/data/cppbaseclass.h +++ b/tests/auto/qml/qmlcppcodegen/data/cppbaseclass.h @@ -1,5 +1,5 @@ // Copyright (C) 2022 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #ifndef CPPBASECLASS_H #define CPPBASECLASS_H diff --git a/tests/auto/qml/qmlcppcodegen/data/dateConstruction.qml b/tests/auto/qml/qmlcppcodegen/data/dateConstruction.qml new file mode 100644 index 0000000000..fc8a34da71 --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/dateConstruction.qml @@ -0,0 +1,20 @@ +pragma Strict +import QtQml + +QtObject { + property date now: new Date() + property date now2: new Date(now) + property date fromString: new Date("1995-12-17T03:24:00") + property date fromNumber: new Date(777) + property date fromPrimitive: new Date(objectName.length === 0 ? 57 : "1997-02-13T13:04:12") + property date from2: new Date(1996, 1) + property date from3: new Date(1996, 2, 3) + property date from4: new Date(1996, 3, 4, 5) + property date from5: new Date(1996, 4, 5, 6, 7) + property date from6: new Date(1996, 5, 6, 7, 8, 9) + property date from7: new Date(1996, 6, 7, 8, 9, 10, 11) + property date from8: new Date(1996, 7, 8, 9, 10, 11, 12, 13) + + property date withUnderflow: new Date(-4, -5, -6, -7, -8, -9, -10) + property date invalid: new Date("foo", "bar") +} diff --git a/tests/auto/qml/qmlcppcodegen/data/dateConversions.qml b/tests/auto/qml/qmlcppcodegen/data/dateConversions.qml index 38a34f7487..5c0a426466 100644 --- a/tests/auto/qml/qmlcppcodegen/data/dateConversions.qml +++ b/tests/auto/qml/qmlcppcodegen/data/dateConversions.qml @@ -9,12 +9,17 @@ QtObject { property string dateString: date property string timeString: time + property real dateNumber: date + property real timeNumber: time + function shuffle() { Druggeljug.myDate = date; Druggeljug.myTime = time; dateString = Druggeljug.myDate; timeString = Druggeljug.myTime; + dateNumber = Druggeljug.myDate; + timeNumber = Druggeljug.myTime; } function fool() { @@ -22,4 +27,9 @@ QtObject { Druggeljug.myTime = Druggeljug.myDate; Druggeljug.myDate = tmp; } + + function invalidate() { + date = new Date("foo", "bar"); + time = new Date("bar", "foo"); + } } diff --git a/tests/auto/qml/qmlcppcodegen/data/dialogButtonBox.qml b/tests/auto/qml/qmlcppcodegen/data/dialogButtonBox.qml new file mode 100644 index 0000000000..b30e0124c8 --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/dialogButtonBox.qml @@ -0,0 +1,8 @@ +pragma Strict +import QtQuick.Controls.Basic + +ApplicationWindow { + footer: DialogButtonBox { + standardButtons: DialogButtonBox.Ok | DialogButtonBox.Cancel + } +} diff --git a/tests/auto/qml/qmlcppcodegen/data/dummyobjekt.h b/tests/auto/qml/qmlcppcodegen/data/dummyobjekt.h new file mode 100644 index 0000000000..ace319f91f --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/dummyobjekt.h @@ -0,0 +1,29 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#ifndef DUMMYOBJEKT_H +#define DUMMYOBJEKT_H + +#include <QtCore/qobject.h> +#include <QtQml/qqml.h> + +#if QT_DEPRECATED_SINCE(6, 4) +class DummyObjekt : public QObject +{ + Q_OBJECT + QML_ELEMENT + QML_SINGLETON + +public: + enum Test { + TestA = 1, + TestB + }; + Q_ENUM(Test) + + // Deliberately not default constructible + explicit DummyObjekt(QObject *parent) : QObject(parent) {} +}; +#endif + +#endif // DUMMYOBJEKT_H diff --git a/tests/auto/qml/qmlcppcodegen/data/dynamicmeta.h b/tests/auto/qml/qmlcppcodegen/data/dynamicmeta.h index 64c2850bda..d8358b6a9c 100644 --- a/tests/auto/qml/qmlcppcodegen/data/dynamicmeta.h +++ b/tests/auto/qml/qmlcppcodegen/data/dynamicmeta.h @@ -1,38 +1,48 @@ // Copyright (C) 2022 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #ifndef DYNAMICMETA_H #define DYNAMICMETA_H #include <private/qobject_p.h> +#include <private/qmetaobjectbuilder_p.h> #include <QtQmlIntegration/qqmlintegration.h> -struct FreeDeleter { - void operator()(QMetaObject *meta) { free(meta); } -}; - template<typename T> class MetaObjectData : public QDynamicMetaObjectData { Q_DISABLE_COPY_MOVE(MetaObjectData) public: - MetaObjectData() = default; - ~MetaObjectData() = default; + MetaObjectData() + { + QMetaObjectBuilder builder; + builder.setSuperClass(&T::staticMetaObject); + builder.setFlags(builder.flags() | DynamicMetaObject); + metaObject = builder.toMetaObject(); + }; + + ~MetaObjectData() { + free(metaObject); + }; QMetaObject *toDynamicMetaObject(QObject *) override { - return const_cast<QMetaObject *>(&T::staticMetaObject); + return metaObject; } int metaCall(QObject *o, QMetaObject::Call call, int idx, void **argv) override { return o->qt_metacall(call, idx, argv); } + + QMetaObject *metaObject = nullptr; }; class DynamicMeta : public QObject { Q_OBJECT Q_PROPERTY(int foo READ foo WRITE setFoo NOTIFY fooChanged FINAL) + Q_PROPERTY(qreal value READ value WRITE setValue RESET resetValue NOTIFY valueChanged FINAL) + Q_PROPERTY(qreal shadowable READ shadowable CONSTANT) QML_ELEMENT public: @@ -54,11 +64,26 @@ public: Q_INVOKABLE int bar(int baz) { return baz + 12; } + qreal value() const { return m_value; } + qreal shadowable() const { return 25; } + +public slots: + void resetValue() { setValue(0); } + void setValue(qreal value) + { + if (m_value == value) + return; + m_value = value; + emit valueChanged(); + } + Q_SIGNALS: void fooChanged(); + void valueChanged(); private: int m_foo = 0; + qreal m_value = 0; }; class DynamicMetaSingleton : public DynamicMeta diff --git a/tests/auto/qml/qmlcppcodegen/data/enforceSignature.qml b/tests/auto/qml/qmlcppcodegen/data/enforceSignature.qml new file mode 100644 index 0000000000..571a000199 --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/enforceSignature.qml @@ -0,0 +1,11 @@ +import QtQml + +QtObject { + id: mainItem + + function arg(item: Binding) : QtObject { return item } + function ret(item: QtObject) : Binding { return item } + + property QtObject a: arg(mainItem); + property QtObject b: ret(mainItem); +} diff --git a/tests/auto/qml/qmlcppcodegen/data/enumConversion.qml b/tests/auto/qml/qmlcppcodegen/data/enumConversion.qml index fee2c50e15..61ddd2162d 100644 --- a/tests/auto/qml/qmlcppcodegen/data/enumConversion.qml +++ b/tests/auto/qml/qmlcppcodegen/data/enumConversion.qml @@ -1,7 +1,12 @@ +pragma Strict import TestTypes MyType { id: root + + property alias status: root.a + property int test: myEnumType.type property bool test_1: myEnumType.type + objectName: root.status + "m" } diff --git a/tests/auto/qml/qmlcppcodegen/data/enumFromBadSingleton.qml b/tests/auto/qml/qmlcppcodegen/data/enumFromBadSingleton.qml new file mode 100644 index 0000000000..3176fde315 --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/enumFromBadSingleton.qml @@ -0,0 +1,6 @@ +import QtQml +import TestTypes + +QtObject { + objectName: "Dummy: " + DummyObjekt.TestA +} diff --git a/tests/auto/qml/qmlcppcodegen/data/enumMarkedAsFlag.qml b/tests/auto/qml/qmlcppcodegen/data/enumMarkedAsFlag.qml new file mode 100644 index 0000000000..2ef37cbdf0 --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/enumMarkedAsFlag.qml @@ -0,0 +1,6 @@ +import QML +import TestTypes + +QtObject { + property int flagValue: ControlFlags.Both +} diff --git a/tests/auto/qml/qmlcppcodegen/data/enumProblems.qml b/tests/auto/qml/qmlcppcodegen/data/enumProblems.qml index f9a4eb144b..6a57b0e64a 100644 --- a/tests/auto/qml/qmlcppcodegen/data/enumProblems.qml +++ b/tests/auto/qml/qmlcppcodegen/data/enumProblems.qml @@ -11,4 +11,9 @@ QtObject { readonly property FooThing fighter: root.f.get(Foo.Fighter) readonly property FooThing bar: root.f.get(Foo.Component) } + + property int a: FooFactory.B + property int b: f.t8 + property int c: FooFactory.D + property int d: f.t16 } diff --git a/tests/auto/qml/qmlcppcodegen/data/enumProperty.h b/tests/auto/qml/qmlcppcodegen/data/enumProperty.h index 8c13e860a3..8d6405a059 100644 --- a/tests/auto/qml/qmlcppcodegen/data/enumProperty.h +++ b/tests/auto/qml/qmlcppcodegen/data/enumProperty.h @@ -15,20 +15,87 @@ public: Tri = 0x04, }; Q_ENUM(MyEnum) - Q_PROPERTY(MyEnum type READ type) + Q_PROPERTY(MyEnum type READ type CONSTANT) MyEnum type() const { return MyEnum::Tri; } }; class MyType : public QObject { Q_OBJECT - Q_PROPERTY(MyEnumType myEnumType READ myEnumType) + Q_PROPERTY(MyEnumType myEnumType READ myEnumType CONSTANT) + Q_PROPERTY(A a READ a WRITE setA NOTIFY aChanged FINAL) QML_ELEMENT public: + enum A { B, C, D }; + Q_ENUM(A) + MyEnumType myEnumType() const { return m_type; } + A a() const { return m_a; } + void setA(A newA) + { + if (m_a == newA) + return; + m_a = newA; + emit aChanged(); + } + + Q_INVOKABLE int method(quint16, const QString &) { return 24; } + Q_INVOKABLE int method(quint16, MyType::A a) { return int(a); } + +Q_SIGNALS: + void aChanged(); + private: MyEnumType m_type; + A m_a = B; +}; + +class CommunicationPermission +{ + Q_GADGET +public: + enum CommunicationMode : quint8 { + Access = 0x01, + Advertise = 0x02, + Default = Access | Advertise, + }; + Q_DECLARE_FLAGS(CommunicationModes, CommunicationMode) + Q_FLAG(CommunicationModes) + + void setCommunicationModes(CommunicationModes modes) { m_modes = modes; } + CommunicationModes communicationModes() const { return m_modes; } + +private: + CommunicationModes m_modes; +}; + +struct QQmlCommunicationPermission : public QObject +{ + Q_OBJECT + QML_NAMED_ELEMENT(CommunicationPermission) + QML_EXTENDED_NAMESPACE(CommunicationPermission) + Q_PROPERTY(CommunicationPermission::CommunicationModes communicationModes READ communicationModes WRITE setCommunicationmodes NOTIFY communicationModesChanged) + +public: + CommunicationPermission::CommunicationModes communicationModes() const + { + return m_permission.communicationModes(); + } + + void setCommunicationmodes(const CommunicationPermission::CommunicationModes &newCommunicationModes) + { + if (communicationModes() == newCommunicationModes) + return; + m_permission.setCommunicationModes(newCommunicationModes); + emit communicationModesChanged(); + } + +signals: + void communicationModesChanged(); + +private: + CommunicationPermission m_permission; }; #endif // ENUMPROPERTY_H diff --git a/tests/auto/qml/qmlcppcodegen/data/enumproblems.h b/tests/auto/qml/qmlcppcodegen/data/enumproblems.h index 08a00acf7e..36f97bec5a 100644 --- a/tests/auto/qml/qmlcppcodegen/data/enumproblems.h +++ b/tests/auto/qml/qmlcppcodegen/data/enumproblems.h @@ -1,10 +1,11 @@ // Copyright (C) 2022 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #ifndef ENUMPROBLEMS_H #define ENUMPROBLEMS_H #include <QObject> +#include <QtCore/qflags.h> #include <QtQml/qqml.h> #include <QtQml/qqmlregistration.h> @@ -45,9 +46,74 @@ class FooThingWrapper { class FooFactory : public QObject { Q_OBJECT QML_ELEMENT + Q_PROPERTY(T8 t8 READ t8 CONSTANT FINAL) + Q_PROPERTY(T16 t16 READ t16 CONSTANT FINAL) public: + enum T8: qint8 { + A, B, C + }; + Q_ENUM(T8) + + enum T16: qint16 { + D = 500, E, F + }; + Q_ENUM(T16) + + T8 t8() const { return C; } + T16 t16() const { return E; } + Q_INVOKABLE Foo* get(Foo::Type type) const { return new Foo(type); } }; +class ControlFlags : public QObject { + Q_OBJECT + QML_ELEMENT + QML_UNCREATABLE("Flag Container Class") +public: + + enum Option { + ControlA = 0x1, + ControlB = 0x2, + Both = ControlA | ControlB + }; + + Q_DECLARE_FLAGS(Options, Option) + Q_FLAG(Option) +}; + +class ScopedEnum : public QObject { + Q_OBJECT + QML_NAMED_ELEMENT(Data) + Q_CLASSINFO("RegisterEnumClassesUnscoped", "false") + +public: + enum class DType { + A = 27, B + }; + Q_ENUM(DType) + + enum EType { + C = 7, D + }; + Q_ENUM(EType) +}; + +class UnscopedEnum : public QObject { + Q_OBJECT + QML_NAMED_ELEMENT(Data2) + Q_CLASSINFO("RegisterEnumClassesUnscoped", "true") + +public: + enum class DType { + A = 26, B + }; + Q_ENUM(DType) + + enum EType { + C = 6, D + }; + Q_ENUM(EType) +}; + #endif // ENUMPROBLEMS_H diff --git a/tests/auto/qml/qmlcppcodegen/data/equalityQUrl.qml b/tests/auto/qml/qmlcppcodegen/data/equalityQUrl.qml new file mode 100644 index 0000000000..55ac68592c --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/equalityQUrl.qml @@ -0,0 +1,16 @@ +pragma Strict +import QtQuick + +Item { + property url emptyUrl: "" + property url sourceUrl: "some/path/file.png" + + property bool emptyUrlStrict: emptyUrl === Qt.resolvedUrl("") + property bool emptyUrlWeak: emptyUrl == Qt.resolvedUrl("") + + property bool sourceUrlStrict: sourceUrl === Qt.url("some/path/file.png"); + property bool sourceUrlWeak: sourceUrl == Qt.url("some/path/file.png"); + + property bool sourceIsNotEmptyStrict: sourceUrl !== emptyUrl + property bool sourceIsNotEmptyWeak: sourceUrl != emptyUrl +} diff --git a/tests/auto/qml/qmlcppcodegen/data/equalityTestsWithNullOrUndefined.qml b/tests/auto/qml/qmlcppcodegen/data/equalityTestsWithNullOrUndefined.qml new file mode 100644 index 0000000000..cd0c433ea9 --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/equalityTestsWithNullOrUndefined.qml @@ -0,0 +1,14 @@ +pragma Strict + +import QtQml +import QtQuick + +Window { + property var foo + Component.onCompleted: { + console.log(foo !== null) + console.log(foo === null) + console.log(foo !== undefined) + console.log(foo === undefined) + } +} diff --git a/tests/auto/qml/qmlcppcodegen/data/exceptionFromInner.qml b/tests/auto/qml/qmlcppcodegen/data/exceptionFromInner.qml new file mode 100644 index 0000000000..13855356f2 --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/exceptionFromInner.qml @@ -0,0 +1,10 @@ +pragma Strict +import QtQml + +QtObject { + property QtObject theNull: null + + function doFail() : string { return theNull.objectName } + function delegateFail() : string { doFail() } + function disbelieveFail() : string { delegateFail() } +} diff --git a/tests/auto/qml/qmlcppcodegen/data/extra/extra.qml b/tests/auto/qml/qmlcppcodegen/data/extra/extra.qml new file mode 100644 index 0000000000..e8f51984b8 --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/extra/extra.qml @@ -0,0 +1,6 @@ +pragma Strict +import QtQml + +B { + r: ({x: 4, y: 6, width: 8, height: 10}) +} diff --git a/tests/auto/qml/qmlcppcodegen/data/extra2/extra.qml b/tests/auto/qml/qmlcppcodegen/data/extra2/extra.qml new file mode 100644 index 0000000000..e8f51984b8 --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/extra2/extra.qml @@ -0,0 +1,6 @@ +pragma Strict +import QtQml + +B { + r: ({x: 4, y: 6, width: 8, height: 10}) +} diff --git a/tests/auto/qml/qmlcppcodegen/data/failures.qml b/tests/auto/qml/qmlcppcodegen/data/failures.qml index f90fb44fe1..3b0e4908ab 100644 --- a/tests/auto/qml/qmlcppcodegen/data/failures.qml +++ b/tests/auto/qml/qmlcppcodegen/data/failures.qml @@ -9,6 +9,7 @@ QtObject { property string attachedForNasty: Nasty.objectName property Nasty nasty: Nasty { + id: theNasty objectName: Component.objectName } @@ -30,18 +31,10 @@ QtObject { Component.onCompleted: doesNotExist() - property string aString: self + "a" - property BirthdayParty party: BirthdayParty { onPartyStarted: (foozle) => { objectName = foozle } } - signal foo() - signal bar() - - // Cannot assign potential undefined - onFoo: objectName = self.bar() - property int enumFromGadget1: GadgetWithEnum.CONNECTED + 1 property int enumFromGadget2: TT2.GadgetWithEnum.CONNECTED + 1 @@ -62,4 +55,55 @@ QtObject { let a; return a; } + + function getText(myArr: list<string>): string { + myArr.shiftss() + } + + function readTracks(metadataList : list<badType>): int { + return metadataList.length + } + + function dtzFail() : int { + for (var a = 10; a < 20; ++a) { + switch (a) { + case 11: + let b = 5; + break; + case 10: + console.log(b); + break; + } + } + return a; + } + + // TODO: Drop these once we can manipulate QVariant-wrapped lists. + property list<withLength> withLengths + property int l: withLengths.length + property withLength w: withLengths[10] + + property unconstructibleWithLength uwl: 12 + 1 + + // Cannot generate code for getters + property rect r3: ({ get x() { return 42; }, y: 4 }) + + property int nonIterable: { + var result = 1; + for (var a in Component) + ++result; + return result; + } + + property alias selfself: self + property alias nastyBad: theNasty.bad + function writeToUnknown() : int { + self.selfself.nastyBad = undefined; + return 5; + } + + readonly property int someNumber: 10 + function writeToReadonly() { someNumber = 20 } + + property var silly: [,0] } diff --git a/tests/auto/qml/qmlcppcodegen/data/fallbackresettable.qml b/tests/auto/qml/qmlcppcodegen/data/fallbackresettable.qml new file mode 100644 index 0000000000..44b55e245a --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/fallbackresettable.qml @@ -0,0 +1,23 @@ +pragma Strict +import QtQml +import TestTypes + +DynamicMeta { + id: self + value: 999 + + property double notResettable: 10 + property double notResettable2: { return undefined } + + property DynamicMeta shadowing: DynamicMeta { + property var shadowable: undefined + } + + function doReset() { self.value = undefined } + function doReset2() { self.value = shadowing.shadowable } + function doNotReset() { self.notResettable = undefined } + + signal aaa() + signal bbb() + onAaa: objectName = self.bbb() +} diff --git a/tests/auto/qml/qmlcppcodegen/data/fileDialog.qml b/tests/auto/qml/qmlcppcodegen/data/fileDialog.qml index b8bd466717..6634982de2 100644 --- a/tests/auto/qml/qmlcppcodegen/data/fileDialog.qml +++ b/tests/auto/qml/qmlcppcodegen/data/fileDialog.qml @@ -1,5 +1,5 @@ // Copyright (C) 2021 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only import QtQuick import QtQuick.Controls diff --git a/tests/auto/qml/qmlcppcodegen/data/flagEnum.qml b/tests/auto/qml/qmlcppcodegen/data/flagEnum.qml new file mode 100644 index 0000000000..3ea5cf98db --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/flagEnum.qml @@ -0,0 +1,6 @@ +pragma Strict +import TestTypes + +CommunicationPermission { + communicationModes: CommunicationPermission.Access +} diff --git a/tests/auto/qml/qmlcppcodegen/data/gadgetwithenum.h b/tests/auto/qml/qmlcppcodegen/data/gadgetwithenum.h index 839e026b77..3c81cd2e7f 100644 --- a/tests/auto/qml/qmlcppcodegen/data/gadgetwithenum.h +++ b/tests/auto/qml/qmlcppcodegen/data/gadgetwithenum.h @@ -1,5 +1,5 @@ // Copyright (C) 2022 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #ifndef GADGETWITHENUM_H #define GADGETWITHENUM_H @@ -26,4 +26,40 @@ namespace GadgetWithEnumWrapper { QML_NAMED_ELEMENT(NamespaceWithEnum) }; +struct Gadget +{ + Q_GADGET + QML_VALUE_TYPE(gadget) + +public: + enum class Prop1 { High, low, VeryHigh, VeryLow }; + Q_ENUM(Prop1) + + enum class Prop2 { VeryHigh, High, low, VeryLow }; + Q_ENUM(Prop2) +}; + +class Backend : public QObject +{ + Q_OBJECT + QML_ELEMENT + QML_SINGLETON + Q_PROPERTY(prop priority READ priority FINAL CONSTANT) + Q_PROPERTY(Gadget gadget READ gadget FINAL CONSTANT) + Q_CLASSINFO("RegisterEnumsFromRelatedTypes", "false") + +public: + enum prop { High, low, VeryHigh, VeryLow }; + Q_ENUM(prop) + + explicit Backend(QObject *parent = nullptr) : QObject(parent) {} + + prop priority() const { return m_priority; } + Gadget gadget() const { return m_gadget; } + +private: + prop m_priority = low; + Gadget m_gadget; +}; + #endif // GADGETWITHENUM_H diff --git a/tests/auto/qml/qmlcppcodegen/data/getOptionalLookup.h b/tests/auto/qml/qmlcppcodegen/data/getOptionalLookup.h new file mode 100644 index 0000000000..e8a24cd707 --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/getOptionalLookup.h @@ -0,0 +1,42 @@ +#ifndef GETOPTIONALLOOKUP_H +#define GETOPTIONALLOOKUP_H + +#include <QObject> +#include <QQmlEngine> + +class GOL_Object : public QObject +{ + Q_OBJECT + QML_ELEMENT + + Q_PROPERTY(int i READ i CONSTANT FINAL) + Q_PROPERTY(QString s READ s CONSTANT FINAL) + Q_PROPERTY(GOL_Object *childA READ childA WRITE setChildA NOTIFY childAChanged FINAL) + Q_PROPERTY(Enum e READ e CONSTANT FINAL) + +public: + GOL_Object(QObject *parent = nullptr) : QObject(parent) { } + + int i() const { return m_i; } + void setI(int i) { m_i = i; } + + QString s() const { return m_s; } + void setS(QString s) { m_s = s; } + + GOL_Object *childA() const { return m_childA; } + void setChildA(GOL_Object *a) { m_childA = a; } + + enum Enum { V1, V2 }; + Q_ENUM(Enum) + Enum e() const { return Enum::V2; } + +signals: + void childAChanged(); + +private: + int m_i = 5; + QString m_s = "6"; + GOL_Object *m_childA = nullptr; +}; + +#endif // GETOPTIONALLOOKUP_H diff --git a/tests/auto/qml/qmlcppcodegen/data/getOptionalLookup.qml b/tests/auto/qml/qmlcppcodegen/data/getOptionalLookup.qml new file mode 100644 index 0000000000..ee360d7142 --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/getOptionalLookup.qml @@ -0,0 +1,34 @@ +pragma Strict +pragma ValueTypeBehavior: Addressable + +import QtQuick + +GOL_Object { + id: root + + property rect r: Qt.rect(0, 0, 20, 50) + property point p: Qt.point(0, -10) + property var v: Qt.point(5, 5) + property var u: undefined + + property int to1: root?.i + property string to2: root?.s + property GOL_Object to3: root?.childA + property var to4: root.childA?.i + property var to5: (undefined as GOL_Object)?.childA + property int to6: (root as GOL_Object)?.s.length + + property int tv1: root.r?.bottom + property int tv2: root.p?.y + + property int te1: root?.e + property int te2: GOL_Object?.V2 + property bool te3: root?.e === GOL_Object?.V1 + property bool te4: root?.e === GOL_Object?.V2 + + property int tc1: root?.p.y + property int tc2: root.r?.x + + property var tc4: root?.childA?.s + property var tc5: root.childA?.s +} diff --git a/tests/auto/qml/qmlcppcodegen/data/idAccess.qml b/tests/auto/qml/qmlcppcodegen/data/idAccess.qml index 2090926872..6ca1f7f66b 100644 --- a/tests/auto/qml/qmlcppcodegen/data/idAccess.qml +++ b/tests/auto/qml/qmlcppcodegen/data/idAccess.qml @@ -1,3 +1,4 @@ +pragma Strict import QtQuick Item { @@ -11,5 +12,9 @@ Item { Text { id: ttt + onTextChanged: { + root.objectName = "dead" + ttt.objectName = "context" + } } } diff --git a/tests/auto/qml/qmlcppcodegen/data/ignoredFunctionReturn.qml b/tests/auto/qml/qmlcppcodegen/data/ignoredFunctionReturn.qml new file mode 100644 index 0000000000..640e2bc22a --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/ignoredFunctionReturn.qml @@ -0,0 +1,14 @@ +import QtQuick + +Item { + id: root + + Component { + id: comp + Rectangle { + color: "blue" + } + } + + Component.onCompleted: comp.createObject(root, {"width": 200, "height": 200}) +} diff --git a/tests/auto/qml/qmlcppcodegen/data/indirectlyShadowable.qml b/tests/auto/qml/qmlcppcodegen/data/indirectlyShadowable.qml new file mode 100644 index 0000000000..de31527e5b --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/indirectlyShadowable.qml @@ -0,0 +1,39 @@ +import QtQml + +QtObject { + id: self + objectName: "self" + + component Inner : QtObject { + property QtObject shadowable: QtObject { + objectName: "shadowable" + } + } + + component Outer : QtObject { + property Inner inner: Inner {} + } + + component Evil : Outer { + property string inner: "evil" + } + + property Outer outer: Outer {} + property Outer evil: Evil {} + + property QtObject notShadowable: QtObject { + objectName: "notShadowable" + } + + function getInnerShadowable() { + notShadowable = outer.inner.shadowable; + } + + function setInnerShadowable() { + outer.inner.shadowable = self; + } + + function turnEvil() { + outer = evil; + } +} diff --git a/tests/auto/qml/qmlcppcodegen/data/intToEnum.qml b/tests/auto/qml/qmlcppcodegen/data/intToEnum.qml new file mode 100644 index 0000000000..e255f4e8f4 --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/intToEnum.qml @@ -0,0 +1,7 @@ +pragma Strict +import TestTypes + +MyType { + a: myEnumType.type === 4 ? 2 : 1 + property int b: method("12", "hh") +} diff --git a/tests/auto/qml/qmlcppcodegen/data/interactive.qml b/tests/auto/qml/qmlcppcodegen/data/interactive.qml index e857df96e7..be5e5f0d40 100644 --- a/tests/auto/qml/qmlcppcodegen/data/interactive.qml +++ b/tests/auto/qml/qmlcppcodegen/data/interactive.qml @@ -1,5 +1,5 @@ // Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only pragma Strict import QtQuick 2.9 diff --git a/tests/auto/qml/qmlcppcodegen/data/internalConversion.qml b/tests/auto/qml/qmlcppcodegen/data/internalConversion.qml new file mode 100644 index 0000000000..7304b7a6b9 --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/internalConversion.qml @@ -0,0 +1,16 @@ +pragma Strict +import QtQml + +QtObject { + property QtObject offset: QtObject { + id: a + property string mark + } + + function markInputs(mark: string) { + offset.objectName = mark; + a.mark = mark; + } + + Component.onCompleted: markInputs("hello") +} diff --git a/tests/auto/qml/qmlcppcodegen/data/invisible.h b/tests/auto/qml/qmlcppcodegen/data/invisible.h index a385ee975f..4f4ebb87ad 100644 --- a/tests/auto/qml/qmlcppcodegen/data/invisible.h +++ b/tests/auto/qml/qmlcppcodegen/data/invisible.h @@ -1,11 +1,12 @@ // Copyright (C) 2022 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #ifndef INVISIBLE_H #define INVISIBLE_H #include <QtCore/qobject.h> #include <QtQmlIntegration/qqmlintegration.h> +#include <QtQml/qqmllist.h> class Invisible : public QObject { @@ -45,6 +46,39 @@ class DerivedFromInvisible : public Invisible { Q_OBJECT QML_ELEMENT + Q_PROPERTY(double implicitWidth MEMBER m_implicitWidth NOTIFY implicitWidthChanged FINAL) +public: + DerivedFromInvisible(QObject *parent = nullptr) : Invisible(parent) {} + +signals: + void implicitWidthChanged(); + +private: + double m_implicitWidth = 27; +}; + +class WithListPropertyOfDerivedFromInvisible : public QObject +{ + Q_OBJECT + QML_ELEMENT + Q_PROPERTY(QQmlListProperty<DerivedFromInvisible> children READ children NOTIFY childrenChanged FINAL) + +public: + WithListPropertyOfDerivedFromInvisible(QObject *parent = nullptr) : QObject(parent) + { + m_children.append(new DerivedFromInvisible(this)); + } + + QQmlListProperty<DerivedFromInvisible> children() + { + return QQmlListProperty<DerivedFromInvisible>(this, &m_children); + } + +signals: + void childrenChanged(); + +private: + QList<DerivedFromInvisible *> m_children; }; #endif // INVISIBLE_H diff --git a/tests/auto/qml/qmlcppcodegen/data/iteration.qml b/tests/auto/qml/qmlcppcodegen/data/iteration.qml new file mode 100644 index 0000000000..8632eefa1b --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/iteration.qml @@ -0,0 +1,20 @@ +pragma Strict + +import QtQml + +QtObject { + property list<int> ints: [3, 4, 5] + property list<QtObject> objects: [ + QtObject { objectName: "a" }, + QtObject { objectName: "b" }, + QtObject { objectName: "c" } + ] + + Component.onCompleted: { + for (var a in objects) { + objectName += objects[a].objectName; + for (var b in ints) + objectName += ints[b]; + } + } +} diff --git a/tests/auto/qml/qmlcppcodegen/data/jsArrayMethods.qml b/tests/auto/qml/qmlcppcodegen/data/jsArrayMethods.qml new file mode 100644 index 0000000000..ff372bca45 --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/jsArrayMethods.qml @@ -0,0 +1,28 @@ +pragma Strict +import QML + +QtObject { + id: self + + property QtObject l1: QtObject { objectName: "klaus" } + property QtObject l2: QtObject { function toString(): string { return "teil" } } + property QtObject l3: QtObject { } + + function jsArray() : list<var> { return [l1, l2, l3, l1, l2, l3] } + property list<QtObject> listProperty: [l1, l2, l3, l1, l2, l3] + + property string jsArrayToString: jsArray().toString() + property string listPropertyToString: listProperty.toString() + + property bool listPropertyIncludes: listProperty.includes(l3) + property bool jsArrayIncludes: jsArray().includes(l3) + + property string listPropertyJoin: listProperty.join() + property string jsArrayJoin: jsArray().join() + + property int listPropertyIndexOf: listProperty.indexOf(l2) + property int jsArrayIndexOf: jsArray().indexOf(l2) + + property int listPropertyLastIndexOf: listProperty.lastIndexOf(l3) + property int jsArrayLastIndexOf: jsArray().lastIndexOf(l3) +} diff --git a/tests/auto/qml/qmlcppcodegen/data/jsArrayMethodsUntyped.qml b/tests/auto/qml/qmlcppcodegen/data/jsArrayMethodsUntyped.qml new file mode 100644 index 0000000000..7426c692fe --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/jsArrayMethodsUntyped.qml @@ -0,0 +1,17 @@ +import QML + +QtObject { + id: self + + property QtObject l1 + property QtObject l2 + property QtObject l3 + + function jsArray() { return [l1, l2, l3, l1, l2, l3] } + + property string jsArrayToString: jsArray().toString() + property bool jsArrayIncludes: jsArray().includes(l3) + property string jsArrayJoin: jsArray().join() + property int jsArrayIndexOf: jsArray().indexOf(l2) + property int jsArrayLastIndexOf: jsArray().lastIndexOf(l3) +} diff --git a/tests/auto/qml/qmlcppcodegen/data/jsArrayMethodsWithParams.qml b/tests/auto/qml/qmlcppcodegen/data/jsArrayMethodsWithParams.qml new file mode 100644 index 0000000000..293e7cbda5 --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/jsArrayMethodsWithParams.qml @@ -0,0 +1,26 @@ +pragma Strict +import QML + +QtObject { + id: self + + required property int i + required property int j + required property int k + + property QtObject l1: QtObject { objectName: "klaus" } + property QtObject l2: QtObject { function toString(): string { return "teil" } } + property QtObject l3: QtObject { } + + function jsArray() : list<var> { return [l1, l2, l3, l1, l2, l3] } + property list<QtObject> listProperty: [l1, l2, l3, l1, l2, l3] + + property list<QtObject> listPropertySlice: listProperty.slice(i, j) + property list<var> jsArraySlice: jsArray().slice(i, j) + + property int listPropertyIndexOf: listProperty.indexOf(l2, i) + property int jsArrayIndexOf: jsArray().indexOf(l2, i) + + property int listPropertyLastIndexOf: listProperty.lastIndexOf(l3, i) + property int jsArrayLastIndexOf: jsArray().lastIndexOf(l3, i) +} diff --git a/tests/auto/qml/qmlcppcodegen/data/jsArrayMethodsWithParamsUntyped.qml b/tests/auto/qml/qmlcppcodegen/data/jsArrayMethodsWithParamsUntyped.qml new file mode 100644 index 0000000000..9e928bd6f6 --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/jsArrayMethodsWithParamsUntyped.qml @@ -0,0 +1,18 @@ +import QML + +QtObject { + id: self + + required property int i + required property int j + required property int k + + property QtObject l1 + property QtObject l2 + property QtObject l3 + + function jsArray() { return [l1, l2, l3, l1, l2, l3] } + property var jsArraySlice: jsArray().slice(i, j) + property int jsArrayIndexOf: jsArray().indexOf(l2, i) + property int jsArrayLastIndexOf: jsArray().lastIndexOf(l3, i) +} diff --git a/tests/auto/qml/qmlcppcodegen/data/listConversion.qml b/tests/auto/qml/qmlcppcodegen/data/listConversion.qml new file mode 100644 index 0000000000..ca86d9a1d6 --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/listConversion.qml @@ -0,0 +1,17 @@ +pragma Strict +import QtQml +import TestTypes + +BirthdayParty { + id: self + + guests: [ + Person { name: "Horst 1" }, + Person { name: "Horst 2" }, + Person { name: "Horst 3" } + ] + + property list<QtObject> o: self.guests + property list<string> s: self.guestNames + property list<var> v: self.stuffs +} diff --git a/tests/auto/qml/qmlcppcodegen/data/listOfInvisible.qml b/tests/auto/qml/qmlcppcodegen/data/listOfInvisible.qml new file mode 100644 index 0000000000..f3698d78ab --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/listOfInvisible.qml @@ -0,0 +1,6 @@ +pragma Strict +import TestTypes + +WithListPropertyOfDerivedFromInvisible { + property real width: children[0].implicitWidth +} diff --git a/tests/auto/qml/qmlcppcodegen/data/listToString.qml b/tests/auto/qml/qmlcppcodegen/data/listToString.qml new file mode 100644 index 0000000000..e9e4b85956 --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/listToString.qml @@ -0,0 +1,25 @@ +pragma Strict +import QtQml + +QtObject { + property list<string> stringList: ["one", "two"] + property list<int> intList: [1, 2] + property list<QtObject> objectList: [this, this] + + Component.onCompleted: { + console.log(stringList) + console.log(stringList + "") + + console.log(intList) + console.log(intList + "") + + console.log(objectList) + console.log(objectList + "") + + console.log(["a", "b"]); + + // TODO: Cannot do this, yet, because we cannot coerce a list to string on the fly. + // We need to store it as list first. + // console.log(["a", "b"] + ""); + } +} diff --git a/tests/auto/qml/qmlcppcodegen/data/listprovider.h b/tests/auto/qml/qmlcppcodegen/data/listprovider.h new file mode 100644 index 0000000000..076944b586 --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/listprovider.h @@ -0,0 +1,24 @@ +#ifndef QLISTPROVIDER_H +#define QLISTPROVIDER_H + +#include <QObject> +#include <QQmlEngine> + +class QListProvider : public QObject +{ + Q_OBJECT + QML_NAMED_ELEMENT(ListProvider) + +public: + explicit QListProvider(QObject *parent = nullptr) : QObject(parent) { } + + Q_INVOKABLE QList<int> intList() const + { + QList<int> list; + for (int i = 0; i < 3; ++i) + list.append(i); + return list; + } +}; + +#endif // QLISTPROVIDER_H diff --git a/tests/auto/qml/qmlcppcodegen/data/math.qml b/tests/auto/qml/qmlcppcodegen/data/math.qml index cc6cd3741a..ad6303e682 100644 --- a/tests/auto/qml/qmlcppcodegen/data/math.qml +++ b/tests/auto/qml/qmlcppcodegen/data/math.qml @@ -3,4 +3,5 @@ import QML QtObject { property int a: Math.max(5, 7, 9, -111) property var b: 50 / 22 + property real c: Math.PI * 2 } diff --git a/tests/auto/qml/qmlcppcodegen/data/mathMinMax.qml b/tests/auto/qml/qmlcppcodegen/data/mathMinMax.qml new file mode 100644 index 0000000000..654b699918 --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/mathMinMax.qml @@ -0,0 +1,59 @@ +pragma Strict +import QtQml +import QtQuick + +Rectangle { + Component.onCompleted: { + // Math.max() + console.log(Math.max(1, 1)); + console.log(Math.max(1, 2)); + console.log(Math.max(2, 1)); + console.log(Math.max(0, 0)); + console.log(Math.max(-1, 0)); + console.log(Math.max(0, -1)); + console.log(Math.max(-1, -1)); + + console.log(Math.max(0, 0, 0)); + console.log(Math.max(0, 0, 1, 0, 0, 0)); + console.log(Math.max(-2, -1, 0, 1, 2)); + console.log(Math.max(2, 1, 0, -1, -2)); + console.log(Math.max(9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0)); + + console.log(Math.max(0.0, 0.0, 0.0)) + console.log(Math.max(-0.001, 0.001, 0.002)) + console.log(Math.max(5.4, 1, 0.002)) + console.log(Math.max(null, 0, -1, 8E-2, NaN, undefined, true, false, Infinity)) + console.log(Math.max(0, -1, 8E-2, true, false, Infinity)) + console.log(Math.max(0, -1, 8E-2, true, false)) + console.log(Math.max(0, -1, 8E-2, false)) + console.log(Math.max(0, -1, 8E-2, true, false, Infinity)) + console.log(Math.max(-1, -8, null)) + console.log(Math.max(undefined, 20, 70)) + + // Math.min() + console.log(Math.min(1, 1)); + console.log(Math.min(1, +2)); + console.log(Math.min(2, 1)); + console.log(Math.min(0, 0)); + console.log(Math.min(-1, 0)); + console.log(Math.min(0, -1)); + console.log(Math.min(-1, -1)); + + console.log(Math.min(0, 0, 0)); + console.log(Math.min(0, 0, 1, 0, 0, 0)); + console.log(Math.min(-2, -1, 0, 1, 2)); + console.log(Math.min(2, 1, 0, -1, -2)); + console.log(Math.min(9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0)); + + console.log(Math.min(0.0, 0.0, 0.0)) + console.log(Math.min(-0.001, 0.001, 0.002)) + console.log(Math.min(5.4, 1, 0.002)) + console.log(Math.min(null, 0, -1, 8E-2, NaN, undefined, true, false, Infinity)) + console.log(Math.min(0, -1, 8E-2, true, false, Infinity)) + console.log(Math.min(0, -1, 8E-2, true, false)) + console.log(Math.min(0, -1, 8E-2, false)) + console.log(Math.min(0, -1, 8E-2, true, false, Infinity)) + console.log(Math.min(-1, -8, null)) + console.log(Math.min(undefined, 20, 70)) + } +} diff --git a/tests/auto/qml/qmlcppcodegen/data/mathStaticProperties.qml b/tests/auto/qml/qmlcppcodegen/data/mathStaticProperties.qml new file mode 100644 index 0000000000..fad74a28bd --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/mathStaticProperties.qml @@ -0,0 +1,17 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +pragma Strict + +import QML + +QtObject { + property double e: Math.E + property double ln10: Math.LN10 + property double ln2: Math.LN2 + property double log10e: Math.LOG10E + property double log2e: Math.LOG2E + property double pi: Math.PI + property double sqrt1_2: Math.SQRT1_2 + property double sqrt2: Math.SQRT2 +} diff --git a/tests/auto/qml/qmlcppcodegen/data/mergedObjectRead.qml b/tests/auto/qml/qmlcppcodegen/data/mergedObjectRead.qml new file mode 100644 index 0000000000..161e21e643 --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/mergedObjectRead.qml @@ -0,0 +1,14 @@ +pragma Strict +import QtQuick + +Item { + objectName: "a" + + function f(arg: Item) : string { + // Read arg as QtObject and Item, merged into QtObject. + console.log(arg) + return arg.x + } + + Component.onCompleted: objectName = f(null) +} diff --git a/tests/auto/qml/qmlcppcodegen/data/mergedObjectWrite.qml b/tests/auto/qml/qmlcppcodegen/data/mergedObjectWrite.qml new file mode 100644 index 0000000000..5f4bb4ff87 --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/mergedObjectWrite.qml @@ -0,0 +1,15 @@ +pragma Strict +import QtQuick + +Item { + objectName: "a" + + function f(arg: Item) : string { + // Write arg as Item, read it as QtObject. + arg.x = 5 + console.log(arg) + return arg.objectName + } + + Component.onCompleted: objectName = f(null) +} diff --git a/tests/auto/qml/qmlcppcodegen/data/methodOnListLookup.qml b/tests/auto/qml/qmlcppcodegen/data/methodOnListLookup.qml new file mode 100644 index 0000000000..b6b7179438 --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/methodOnListLookup.qml @@ -0,0 +1,16 @@ +pragma Strict +import QtQml +import TestTypes + +QtObject { + objectName: people[0].getName() + property list<Person> people: [ + Person { + name: "no one" + } + ] + + function boom() : string { + return people[1].getName() + } +} diff --git a/tests/auto/qml/qmlcppcodegen/data/methods.qml b/tests/auto/qml/qmlcppcodegen/data/methods.qml index 3abd14c9c1..c045c2249b 100644 --- a/tests/auto/qml/qmlcppcodegen/data/methods.qml +++ b/tests/auto/qml/qmlcppcodegen/data/methods.qml @@ -35,6 +35,8 @@ BirthdayParty { } function stuff(sn) { + // Warning: to not test for signal handlers like this in actual code. + // Use the helper methods in QQmlSignalNames instead. if (sn.substr(0, 2) === "on" && sn[2] === sn[2].toUpperCase()) return sn return "on" + sn.substr(0, 1).toUpperCase() + sn.substr(1) diff --git a/tests/auto/qml/qmlcppcodegen/data/multiforeign.h b/tests/auto/qml/qmlcppcodegen/data/multiforeign.h index 290b6370f5..6c46d5ad86 100644 --- a/tests/auto/qml/qmlcppcodegen/data/multiforeign.h +++ b/tests/auto/qml/qmlcppcodegen/data/multiforeign.h @@ -1,5 +1,5 @@ // Copyright (C) 2022 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #ifndef MULTIFOREIGN_H #define MULTIFOREIGN_H diff --git a/tests/auto/qml/qmlcppcodegen/data/multipleCtors.qml b/tests/auto/qml/qmlcppcodegen/data/multipleCtors.qml new file mode 100644 index 0000000000..61dfdb7ca5 --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/multipleCtors.qml @@ -0,0 +1,13 @@ +pragma Strict + +import TestTypes +import QtQml + +QtObject { + property rect r: Qt.rect(1, 2, 3, 4) + property point p: Qt.point(5, 6); + + property withLength wr: r + property withLength wp: p + property withLength wi: 17 +} diff --git a/tests/auto/qml/qmlcppcodegen/data/nullAccessInsideSignalHandler.qml b/tests/auto/qml/qmlcppcodegen/data/nullAccessInsideSignalHandler.qml new file mode 100644 index 0000000000..8fe47b7296 --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/nullAccessInsideSignalHandler.qml @@ -0,0 +1,33 @@ +import QtQuick + +Item { + id: root + visible: true + + property var speaker + signal say_hello() + + Component{ + id: speakerComp + Text { + text: "HELLO" + function say_hello() { + console.log(text) + } + } + } + + Timer { + interval: 1; running: true; repeat: false + onTriggered: root.say_hello(); + } + + Component.onCompleted: + { + root.speaker = speakerComp.createObject(root); + + root.say_hello.connect(root.speaker.say_hello); + + root.speaker.destroy(); + } +} diff --git a/tests/auto/qml/qmlcppcodegen/data/nullComparison.qml b/tests/auto/qml/qmlcppcodegen/data/nullComparison.qml index 1f9af7169b..53b3697c9b 100644 --- a/tests/auto/qml/qmlcppcodegen/data/nullComparison.qml +++ b/tests/auto/qml/qmlcppcodegen/data/nullComparison.qml @@ -6,6 +6,7 @@ QtObject { property int w: 1 property int x: 1 property int y: 1 + property int z: 1 Component.onCompleted: { var g = null; if (g !== null) { @@ -22,5 +23,15 @@ QtObject { if (h === undefined) { y = 5; } + + var o = this; + if (o != null) + z += 7; + if (o == null) + z += 6; + if (g == null) + z += 10; + if (g != null) + z += 20; } } diff --git a/tests/auto/qml/qmlcppcodegen/data/nullishCoalescing.qml b/tests/auto/qml/qmlcppcodegen/data/nullishCoalescing.qml new file mode 100644 index 0000000000..f84f93c5d2 --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/nullishCoalescing.qml @@ -0,0 +1,37 @@ +pragma Strict +pragma ValueTypeBehavior: Addressable + +import QtQuick + +GOL_Object { + id: root + + property int p1: 5 ?? -1 + property string p2: "6" ?? "-1" + + property var p3: undefined ?? undefined + property var p4: undefined ?? null + property var p5: undefined ?? -1 + property var p6: undefined ?? "-1" + + property var p7: null ?? undefined + property var p8: null ?? null + property var p9: null ?? -1 + property var p10: null ?? "-1" + + property int p11: GOL_Object.V2 ?? "-1" + + property int p12: 1 ?? 2 ?? 3 + property int p13: "1" ?? "2" ?? "3" + property var p14: undefined ?? "2" ?? undefined + property var p15: undefined ?? undefined ?? 1 + + property var p16 + property var p17 + + Component.onCompleted: { + p16 = (root.childA as GOL_Object)?.i ?? -1 + root.childA = root + p17 = (root.childA as GOL_Object)?.i ?? -1 + } +} diff --git a/tests/auto/qml/qmlcppcodegen/data/numbersInJsPrimitive.qml b/tests/auto/qml/qmlcppcodegen/data/numbersInJsPrimitive.qml index f12d3f5ea2..ec848429e8 100644 --- a/tests/auto/qml/qmlcppcodegen/data/numbersInJsPrimitive.qml +++ b/tests/auto/qml/qmlcppcodegen/data/numbersInJsPrimitive.qml @@ -4,23 +4,57 @@ import TestTypes QtObject { function writeValues() { + Druggeljug.myInt8 = 35 + Druggeljug.myUint8 = 36 + Druggeljug.myInt16 = 37 + Druggeljug.myUint16 = 38 Druggeljug.myInt = 39 Druggeljug.myUint = 40 Druggeljug.myInt32 = 41 Druggeljug.myUint32 = 42 } + function negateValues() { + Druggeljug.myInt8 = -Druggeljug.myInt8; + Druggeljug.myUint8 = -Druggeljug.myUint8; + Druggeljug.myInt16 = -Druggeljug.myInt16; + Druggeljug.myUint16 = -Druggeljug.myUint16; + Druggeljug.myInt = -Druggeljug.myInt; + Druggeljug.myUint = -Druggeljug.myUint; + Druggeljug.myInt32 = -Druggeljug.myInt32; + Druggeljug.myUint32 = -Druggeljug.myUint32; + } + + function shuffleValues() { + Druggeljug.myInt8 = Druggeljug.myUint8; + Druggeljug.myUint8 = Druggeljug.myInt16; + Druggeljug.myInt16 = Druggeljug.myUint16; + Druggeljug.myUint16 = Druggeljug.myInt; + Druggeljug.myInt = Druggeljug.myUint; + Druggeljug.myUint = Druggeljug.myInt32; + Druggeljug.myInt32 = Druggeljug.myUint32; + Druggeljug.myUint32 = Druggeljug.myInt8; + } + function readValueAsString(i: int) : string { switch (i) { - case 0: return Druggeljug.myInt; - case 1: return Druggeljug.myUint; - case 2: return Druggeljug.myInt32; - case 3: return Druggeljug.myUint32; + case 0: return Druggeljug.myInt8; + case 1: return Druggeljug.myUint8; + case 2: return Druggeljug.myInt16; + case 3: return Druggeljug.myUint16; + case 4: return Druggeljug.myInt; + case 5: return Druggeljug.myUint; + case 6: return Druggeljug.myInt32; + case 7: return Druggeljug.myUint32; default: return ""; } } function storeValues() { + Druggeljug.storeMyInt8(1330) + Druggeljug.storeMyUint8(1331) + Druggeljug.storeMyInt16(1332) + Druggeljug.storeMyUint16(1333) Druggeljug.storeMyInt(1334) Druggeljug.storeMyUint(1335) Druggeljug.storeMyInt32(1336) diff --git a/tests/auto/qml/qmlcppcodegen/data/objectLookupOnListElement.qml b/tests/auto/qml/qmlcppcodegen/data/objectLookupOnListElement.qml new file mode 100644 index 0000000000..4804921b02 --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/objectLookupOnListElement.qml @@ -0,0 +1,34 @@ +pragma Strict +import QtQuick + +Item { + id: stack + + property int current: 0 + + onCurrentChanged: setZOrders() + Component.onCompleted: setZOrders() + + function setZOrders() { + for (var i = 0; i < Math.max(stack.children.length, 3); ++i) { + stack.children[i].z = (i == current ? 1 : 0) + stack.children[i].enabled = (i == current) + } + } + + function zOrders() : list<int> { + return [ + stack.children[0].z, + stack.children[1].z, + stack.children[2].z + ] + } + + function clearChildren() { + children.length = 0; + } + + Item {} + Item {} + Item {} +} diff --git a/tests/auto/qml/qmlcppcodegen/data/objectWithStringListMethod.qml b/tests/auto/qml/qmlcppcodegen/data/objectWithStringListMethod.qml new file mode 100644 index 0000000000..14f84c57d0 --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/objectWithStringListMethod.qml @@ -0,0 +1,7 @@ +import QtQml +import TestTypes + +QtObject { + readonly property ObjectWithStringListMethod foo: ObjectFactory.getFoo() + Component.onCompleted: console.log(foo ? foo.names().length : "-") +} diff --git a/tests/auto/qml/qmlcppcodegen/data/objectwithmethod.h b/tests/auto/qml/qmlcppcodegen/data/objectwithmethod.h index 348862985f..f43a0d5531 100644 --- a/tests/auto/qml/qmlcppcodegen/data/objectwithmethod.h +++ b/tests/auto/qml/qmlcppcodegen/data/objectwithmethod.h @@ -1,5 +1,5 @@ // Copyright (C) 2022 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #ifndef OBJECTWITHMETOD_H #define OBJECTWITHMETOD_H @@ -7,6 +7,7 @@ #include <QtCore/qobject.h> #include <QtCore/qproperty.h> #include <QtQml/qqml.h> +#include <QtQml/private/qv4engine_p.h> // Make objectName available. It doesn't exist on the builtin QtObject type struct QObjectForeignForObjectName { @@ -27,6 +28,20 @@ public: Q_INVOKABLE int doThing() const { return theThing; } QProperty<int> theThing; QBindable<int> theThingBindable() { return QBindable<int>(&theThing); } + + // The meta methods are populated back to front. + // The V4Function flag should not bleed into the others in either case. + + Q_INVOKABLE void overloaded(QQmlV4FunctionPtr) { setObjectName(QStringLiteral("javaScript")); } + Q_INVOKABLE void overloaded(double) { setObjectName(QStringLiteral("double")); } + Q_INVOKABLE void overloaded(const QString &) { setObjectName(QStringLiteral("string")); } + + Q_INVOKABLE void foo(const QString &bla) { setObjectName(bla); } + Q_INVOKABLE void foo(ObjectWithMethod *) { setObjectName(QStringLiteral("ObjectWithMethod")); } + + Q_INVOKABLE void overloaded2(double) { setObjectName(QStringLiteral("double")); } + Q_INVOKABLE void overloaded2(const QString &) { setObjectName(QStringLiteral("string")); } + Q_INVOKABLE void overloaded2(QQmlV4FunctionPtr) { setObjectName(QStringLiteral("javaScript")); } }; class OverriddenObjectName : public ObjectWithMethod @@ -56,4 +71,40 @@ private: QProperty<QString> m_objectName; }; +class ObjectWithStringListMethod : public QObject +{ + Q_OBJECT + QML_ELEMENT + +public: + explicit ObjectWithStringListMethod(QObject *parent = nullptr) : QObject(parent) + { + m_names.append("One"); + m_names.append("Two"); + } + + Q_INVOKABLE QStringList names() const { return m_names; } + +private: + QStringList m_names; +}; + +class ObjectFactory : public QObject { + Q_OBJECT + QML_ELEMENT + QML_SINGLETON + +public: + explicit ObjectFactory(QObject *parent = nullptr) : QObject(parent) {} + Q_INVOKABLE ObjectWithStringListMethod *getFoo() + { + if (!m_foo) + m_foo = new ObjectWithStringListMethod(this); + return m_foo; + } + +private: + ObjectWithStringListMethod *m_foo = nullptr; +}; + #endif // OBJECTWITHMETHOD_H diff --git a/tests/auto/qml/qmlcppcodegen/data/optionalComparison.qml b/tests/auto/qml/qmlcppcodegen/data/optionalComparison.qml new file mode 100644 index 0000000000..4dbc541721 --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/optionalComparison.qml @@ -0,0 +1,77 @@ +pragma Strict +import QtQml + +QtObject { + property list<QtObject> elms: [this, null, null] + + property int found: find(this) + property int foundNot: findNot(this) + property int foundStrict: findStrict(this) + property int foundStrictNot: findStrictNot(this) + + function find(elm : QtObject) : int { + let found = 0; + for (var i = 0; i < elms.length; i++) { + var value = elms[i]; + if (value == elm) + ++found; + } + return found; + } + + function findNot(elm : QtObject) : int { + let found = 0; + for (var i = 0; i < elms.length; i++) { + var value = elms[i]; + if (value != elm) + ++found; + } + return found; + } + + function findStrict(elm : QtObject) : int { + let found = 0; + for (var i = 0; i < elms.length; i++) { + var value = elms[i]; + if (value === elm) + ++found; + } + return found; + } + + function findStrictNot(elm : QtObject) : int { + let found = 0; + for (var i = 0; i < elms.length; i++) { + var value = elms[i]; + if (value !== elm) + ++found; + } + return found; + } + + property bool optionalNull: { + let a // Produces a QJsPrimitiveValue we can compare to null below + if (objectName.length === 0) + a = null + else + a = undefined + + return a === null + } + + property int undefinedEqualsUndefined: { + var matches = 0; + + // Overrun the array so that we get some undefined !== undefined. + for (var i = 0; i < 4; i++) { + var val1 = elms[i] + for (var j = 0; j < 4; j++) { + var val2 = elms[j] + if (!(val1 !== val2)) + ++matches + } + } + + return matches; + } +} diff --git a/tests/auto/qml/qmlcppcodegen/data/person.cpp b/tests/auto/qml/qmlcppcodegen/data/person.cpp index 4dcd6fd56f..946dfbcdaa 100644 --- a/tests/auto/qml/qmlcppcodegen/data/person.cpp +++ b/tests/auto/qml/qmlcppcodegen/data/person.cpp @@ -1,5 +1,5 @@ // Copyright (C) 2022 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include "person.h" @@ -98,3 +98,21 @@ void Person::setCousins(const QList<Person *> &newCousins) m_cousins = newCousins; emit cousinsChanged(); } + +QRectF Person::area() const +{ + return m_area; +} + +void Person::setArea(const QRectF &newArea) +{ + if (m_area.valueBypassingBindings() == newArea) + return; + m_area = newArea; + emit areaChanged(); +} + +QBindable<QRectF> Person::areaBindable() +{ + return QBindable<QRectF>(&m_area); +} diff --git a/tests/auto/qml/qmlcppcodegen/data/person.h b/tests/auto/qml/qmlcppcodegen/data/person.h index fba4a9e9a5..c46aa757a7 100644 --- a/tests/auto/qml/qmlcppcodegen/data/person.h +++ b/tests/auto/qml/qmlcppcodegen/data/person.h @@ -1,5 +1,5 @@ // Copyright (C) 2022 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #ifndef PERSON_H #define PERSON_H @@ -8,6 +8,35 @@ #include <QtQml/qqml.h> #include <QtQml/qqmlengine.h> #include <QtCore/qproperty.h> +#include <QtCore/qrect.h> + +struct Inner +{ + Q_GADGET + QML_VALUE_TYPE(inner) + QML_STRUCTURED_VALUE + Q_PROPERTY(int i MEMBER i) + +private: + friend bool operator==(const Inner &lhs, const Inner &rhs) { return lhs.i == rhs.i; } + friend bool operator!=(const Inner &lhs, const Inner &rhs) { return !(lhs == rhs); } + + int i = 11; +}; + +struct Outer +{ + Q_GADGET + QML_VALUE_TYPE(outer) + QML_STRUCTURED_VALUE + Q_PROPERTY(Inner inner MEMBER inner) + +private: + friend bool operator==(const Outer &lhs, const Outer &rhs) { return lhs.inner == rhs.inner; } + friend bool operator!=(const Outer &lhs, const Outer &rhs) { return !(lhs == rhs); } + + Inner inner; +}; // Intentionally opaque type class Barzle : public QObject {}; @@ -22,6 +51,8 @@ class Person : public QObject Q_PROPERTY(QList<Barzle *> barzles READ barzles WRITE setBarzles NOTIFY barzlesChanged FINAL) Q_PROPERTY(QList<Person *> cousins READ cousins WRITE setCousins NOTIFY cousinsChanged FINAL) Q_PROPERTY(QByteArray data READ data WRITE setData NOTIFY dataChanged FINAL) + Q_PROPERTY(QRectF area READ area WRITE setArea NOTIFY areaChanged) // not FINAL + Q_PROPERTY(QRectF area2 READ area WRITE setArea NOTIFY areaChanged BINDABLE areaBindable FINAL) QML_ELEMENT public: Person(QObject *parent = nullptr); @@ -52,6 +83,12 @@ public: QList<Person *> cousins() const; void setCousins(const QList<Person *> &newCousins); + QRectF area() const; + void setArea(const QRectF &newArea); + QBindable<QRectF> areaBindable(); + + Q_INVOKABLE QString getName() const { return m_name; } + signals: void nameChanged(); void shoeSizeChanged(); @@ -62,6 +99,10 @@ signals: void ambiguous(int a = 9); void cousinsChanged(); + void objectListHappened(const QList<QObject *> &); + void variantListHappened(const QList<QVariant> &); + + void areaChanged(); private: QString m_name; @@ -70,6 +111,7 @@ private: QList<Barzle *> m_barzles; QList<Person *> m_cousins; QProperty<QByteArray> m_data; + QProperty<QRectF> m_area; }; class BarzleListRegistration diff --git a/tests/auto/qml/qmlcppcodegen/data/qtbug113150.qml b/tests/auto/qml/qmlcppcodegen/data/qtbug113150.qml new file mode 100644 index 0000000000..c7103eaf05 --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/qtbug113150.qml @@ -0,0 +1,13 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +pragma Strict + +import QtQuick + +Window { + // If static properties of the Math global object are not directly + // supported, a warning should be issued in turn failing the build + // due to `pragma Strict`. + width: 200 * Math.PI +} diff --git a/tests/auto/qml/qmlcppcodegen/data/readEnumFromInstance.qml b/tests/auto/qml/qmlcppcodegen/data/readEnumFromInstance.qml new file mode 100644 index 0000000000..d0176e6b15 --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/readEnumFromInstance.qml @@ -0,0 +1,16 @@ +import QtQml +import TestTypes + +QtObject { + id: root + + property int priority: Backend.gadget.VeryHigh + property int prop2: Backend.priority + + property bool priorityIsVeryHigh: root.priority == Backend.VeryHigh + + function cyclePriority() : int { + root.priority = Backend.gadget.High; + return root.priority; + } +} diff --git a/tests/auto/qml/qmlcppcodegen/data/readonlyListProperty.qml b/tests/auto/qml/qmlcppcodegen/data/readonlyListProperty.qml new file mode 100644 index 0000000000..149638283b --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/readonlyListProperty.qml @@ -0,0 +1,17 @@ +import QtQml + +QtObject { + id: testObj + + // "readonly" means the identity of the list cannot be changed. + // Its contents can be changed. + readonly default property list<QtObject> theList + + Component.onCompleted: { + for (var i = 0; i < 4; i++) + testObj.theList.push(testObj) + } + + property int l: theList.length +} + diff --git a/tests/auto/qml/qmlcppcodegen/data/reduceWithNullThis.qml b/tests/auto/qml/qmlcppcodegen/data/reduceWithNullThis.qml new file mode 100644 index 0000000000..c6fda8c739 --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/reduceWithNullThis.qml @@ -0,0 +1,18 @@ +import QtQml + +QtObject { + id: mainItem + property int topPadding: 12 + property int bottomPadding: 12 + + property int preferredHeight: mainItem.children.reduce(maximumImplicitHeightReducer, 0) + topPadding + bottomPadding + function maximumImplicitHeightReducer(accumulator: real, item: Binding): real { + return Math.max(accumulator, (item.objectName + "b").length); + } + + property int preferredHeight2: mainItem.children.reduce((accumulator, item) => { + return Math.max(accumulator, (item.objectName + "b").length); + }, 0) + topPadding + bottomPadding + + property list<Binding> children: [ Binding { objectName: "aaa" } ] +} diff --git a/tests/auto/qml/qmlcppcodegen/data/renameAdjust.qml b/tests/auto/qml/qmlcppcodegen/data/renameAdjust.qml new file mode 100644 index 0000000000..9352163ba7 --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/renameAdjust.qml @@ -0,0 +1,19 @@ +pragma Strict +import QtQml + +QtObject { + id: last + property int value: 10 + + function verify(i: int) { + if (last.value !== i) + console.error("failed", last.value, i); + else + console.log("success") + } + + Component.onCompleted: { + verify(10) + verify(11) + } +} diff --git a/tests/auto/qml/qmlcppcodegen/data/resettable.h b/tests/auto/qml/qmlcppcodegen/data/resettable.h new file mode 100644 index 0000000000..755c8de237 --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/resettable.h @@ -0,0 +1,39 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#ifndef RESETTABLE_H +#define RESETTABLE_H + +#include <QtCore/qobject.h> +#include <QtQml/qqml.h> + +class ResettableProperty : public QObject +{ + Q_OBJECT + QML_NAMED_ELEMENT(Resettable) + Q_PROPERTY(qreal value READ value WRITE setValue RESET resetValue NOTIFY valueChanged FINAL) + Q_PROPERTY(qreal shadowable READ shadowable CONSTANT) + +public: + explicit ResettableProperty(QObject *parent = nullptr) : QObject(parent) {} + qreal value() const { return m_value; } + qreal shadowable() const { return 25; } + +public slots: + void resetValue() { setValue(0); } + void setValue(qreal value) + { + if (m_value == value) + return; + m_value = value; + emit valueChanged(); + } + +signals: + void valueChanged(); + +private: + qreal m_value = 0; +}; + +#endif // RESETTABLE_H diff --git a/tests/auto/qml/qmlcppcodegen/data/resettable.qml b/tests/auto/qml/qmlcppcodegen/data/resettable.qml new file mode 100644 index 0000000000..561655032d --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/resettable.qml @@ -0,0 +1,23 @@ +pragma Strict +import QtQml +import TestTypes + +Resettable { + id: self + value: 999 + + property double notResettable: 10 + property double notResettable2: { return undefined } + + property Resettable shadowing: Resettable { + property var shadowable: undefined + } + + function doReset() { self.value = undefined } + function doReset2() { self.value = shadowing.shadowable } + function doNotReset() { self.notResettable = undefined } + + signal aaa() + signal bbb() + onAaa: objectName = self.bbb() +} diff --git a/tests/auto/qml/qmlcppcodegen/data/returnAfterReject.qml b/tests/auto/qml/qmlcppcodegen/data/returnAfterReject.qml new file mode 100644 index 0000000000..e0b85eb270 --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/returnAfterReject.qml @@ -0,0 +1,15 @@ +import QtQml + +QtObject { + id: remaining + + property int bar: 0 + + Component.onCompleted: { + let remainingTime = 123 + if (remainingTime < 0) { + remainingTime += 24 * 60 * 60 + } + remaining.bar = isNaN(remainingTime) ? 0 : remainingTime + } +} diff --git a/tests/auto/qml/qmlcppcodegen/data/scopeIdLookup.qml b/tests/auto/qml/qmlcppcodegen/data/scopeIdLookup.qml new file mode 100644 index 0000000000..e23f180598 --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/scopeIdLookup.qml @@ -0,0 +1,20 @@ +pragma ComponentBehavior: Bound + +import QtQml + +QtObject { + id: root + + property QtObject b: QtObject { + id: bar + objectName: "outer" + } + + property Instantiator i: Instantiator { + model: 1 + delegate: QtObject { + property QtObject bar: QtObject { objectName: "inner" } + Component.onCompleted: root.objectName = bar.objectName + } + } +} diff --git a/tests/auto/qml/qmlcppcodegen/data/scopedEnum.qml b/tests/auto/qml/qmlcppcodegen/data/scopedEnum.qml new file mode 100644 index 0000000000..8b9f161b06 --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/scopedEnum.qml @@ -0,0 +1,19 @@ +import QtQml +import TestTypes + +QtObject { + property int good: Data.DType.A + property int bad: Data.A + + property int wrong: Data.EType.C + property int right: Data.C + + property int notgood: Data2.DType.A + property int notbad: Data2.A + + property int notwrong: Data2.EType.C + property int notright: Data2.C + + property int passable: Enums.AppState.Blue + property int wild: Enums.Green +} diff --git a/tests/auto/qml/qmlcppcodegen/data/sequenceToIterable.h b/tests/auto/qml/qmlcppcodegen/data/sequenceToIterable.h new file mode 100644 index 0000000000..76c72fff36 --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/sequenceToIterable.h @@ -0,0 +1,56 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#ifndef SEQUENCETOITERABLE_H +#define SEQUENCETOITERABLE_H + +#include <QtCore/qobject.h> +#include <QtQml/qqml.h> + +class Entry : public QObject { + Q_OBJECT + +public: + explicit Entry(const QString &name, QObject *parent = nullptr) + : QObject(parent), m_name(name) + { + setObjectName(name); + } + +private: + QString m_name; +}; + +class EntryWrapper { + Q_GADGET + QML_FOREIGN(Entry) + QML_NAMED_ELEMENT(Entry) + QML_UNCREATABLE("These are my Entry objects") +}; + +class EntryListRegistration +{ + Q_GADGET + QML_FOREIGN(QList<Entry*>) + QML_ANONYMOUS + QML_SEQUENTIAL_CONTAINER(Entry*) +}; + +class EntrySource : public QObject { + Q_OBJECT + QML_ELEMENT + QML_SINGLETON + +public: + explicit EntrySource(QObject* parent = nullptr) : QObject(parent) { + for (int i = 0; i < 10; i++) { + m_entries.push_back(new Entry(QString("Item %1").arg(i), this)); + } + } + Q_INVOKABLE QList<Entry*> getEntries() const { return m_entries; } + +private: + QList<Entry*> m_entries; +}; + +#endif // SEQUENCETOITERABLE_H diff --git a/tests/auto/qml/qmlcppcodegen/data/sequenceToIterable.qml b/tests/auto/qml/qmlcppcodegen/data/sequenceToIterable.qml new file mode 100644 index 0000000000..23e2645128 --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/sequenceToIterable.qml @@ -0,0 +1,20 @@ +pragma Strict +import QtQuick +import TestTypes + +Item { + Component.onCompleted: () => { + repeater.model = EntrySource.getEntries() + } + + Repeater { + id: repeater + Item { + required property int index + required property QtObject modelData + objectName: modelData + ": " + index + } + } + + property int c: children.length +} diff --git a/tests/auto/qml/qmlcppcodegen/data/setLookupConversion.qml b/tests/auto/qml/qmlcppcodegen/data/setLookupConversion.qml new file mode 100644 index 0000000000..404ee8653b --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/setLookupConversion.qml @@ -0,0 +1,17 @@ +pragma Strict +import QtQml + +QtObject { + id: first + property int value + + objectName: a.objectName + property QtObject a: QtObject {} + function t() { a.objectName = "a" } + + Component.onCompleted: { + for (let i = 0; i < 10; ++i) { + first.value = i; + } + } +} diff --git a/tests/auto/qml/qmlcppcodegen/data/setLookupOriginalScope.qml b/tests/auto/qml/qmlcppcodegen/data/setLookupOriginalScope.qml new file mode 100644 index 0000000000..9975bfdfa4 --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/setLookupOriginalScope.qml @@ -0,0 +1,17 @@ +pragma Strict +import QtQml + +QtObject { + property Component newEditConstraint: EditConstraint {} + property Variable variable: Variable {} + property EditConstraint edit + + function trigger() { + change(variable, 55); + } + + function change(v: Variable, newValue: int) { + edit = newEditConstraint.createObject(null, {myOutput: v}) as EditConstraint; + v.value = newValue; + } +} diff --git a/tests/auto/qml/qmlcppcodegen/data/shadowedAsCasts.qml b/tests/auto/qml/qmlcppcodegen/data/shadowedAsCasts.qml new file mode 100644 index 0000000000..ccb50f4934 --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/shadowedAsCasts.qml @@ -0,0 +1,31 @@ +pragma Strict +import QtQml + +QtObject { + property ShadowedObjectName shadowed1: ShadowedObjectName {} + property ShadowedObjectName shadowed2: ShadowedObjectName {} + property QtObject shadowed3: ShadowedObjectNameDerived {} + + function returnShadowed2() : QtObject { return shadowed2 } + + function a(mark: int) { + // as-cast can be optimized out if we're clever. + (shadowed1 as QtObject).objectName = mark; + } + + function b(mark: int) { + // method return values can contain shadowed properties! + returnShadowed2().objectName = mark; + } + + function c(mark: int) { + // Has to do an actual as-cast, but results in ShadowedObjectNameDerived! + (shadowed3 as ShadowedObjectName).objectName = mark; + } + + Component.onCompleted: { + a(43); + b(42); + c(41); + } +} diff --git a/tests/auto/qml/qmlcppcodegen/data/shadowedMethod.qml b/tests/auto/qml/qmlcppcodegen/data/shadowedMethod.qml new file mode 100644 index 0000000000..590fb40b17 --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/shadowedMethod.qml @@ -0,0 +1,35 @@ +pragma Strict +import QtQuick + +Item { + component B: Item { + function contains(point: point) : string { + return "b" + } + } + + + component C: Item { + function contains(point: point) : string { + return "c" + } + } + + property Item a: Item {} + property B b: B {} + property C c: C {} + + function doThing() : var { return a.contains(Qt.point(0, 0)) } + + property var athing; + property var bthing; + property var cthing; + + Component.onCompleted: { + athing = doThing(); + a = b; + bthing = doThing(); + a = c; + cthing = doThing(); + } +} diff --git a/tests/auto/qml/qmlcppcodegen/data/shadowedPrimitiveCmpEqNull.qml b/tests/auto/qml/qmlcppcodegen/data/shadowedPrimitiveCmpEqNull.qml new file mode 100644 index 0000000000..0e130b9afc --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/shadowedPrimitiveCmpEqNull.qml @@ -0,0 +1,16 @@ +import QtQuick + +QtObject { + id: win + + component Foo: QtObject { + property int progress: 0 + } + + property int progress: 0 + readonly property Foo configuring: Foo {} + + Component.onCompleted: { + win.configuring.progress = win?.progress ?? 0 + } +} diff --git a/tests/auto/qml/qmlcppcodegen/data/shared/Slider.qml b/tests/auto/qml/qmlcppcodegen/data/shared/Slider.qml index 58a567f4db..3a0349966d 100644 --- a/tests/auto/qml/qmlcppcodegen/data/shared/Slider.qml +++ b/tests/auto/qml/qmlcppcodegen/data/shared/Slider.qml @@ -1,5 +1,5 @@ // Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only import QtQuick 2.12 diff --git a/tests/auto/qml/qmlcppcodegen/data/signalsWithLists.qml b/tests/auto/qml/qmlcppcodegen/data/signalsWithLists.qml new file mode 100644 index 0000000000..9a07b206d4 --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/signalsWithLists.qml @@ -0,0 +1,18 @@ +pragma Strict +import QtQml +import TestTypes + +Person { + property list<var> varlist: [1, "foo", this, undefined, true] + property list<QtObject> objlist: [this, null, this] + + function sendSignals() { + variantListHappened(varlist); + objectListHappened(objlist); + } + + property int happening: 0 + + onObjectListHappened: (objects) => { happening += objects.length } + onVariantListHappened: (variants) => { happening += variants.length } +} diff --git a/tests/auto/qml/qmlcppcodegen/data/state.h b/tests/auto/qml/qmlcppcodegen/data/state.h index 1afcebc4ea..708e681781 100644 --- a/tests/auto/qml/qmlcppcodegen/data/state.h +++ b/tests/auto/qml/qmlcppcodegen/data/state.h @@ -1,5 +1,5 @@ // Copyright (C) 2022 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #ifndef STATE_H #define STATE_H diff --git a/tests/auto/qml/qmlcppcodegen/data/structuredValueType.qml b/tests/auto/qml/qmlcppcodegen/data/structuredValueType.qml new file mode 100644 index 0000000000..158dff2d0b --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/structuredValueType.qml @@ -0,0 +1,8 @@ +pragma Strict +import QtQml + +QtObject { + property rect r: ({x: 1, y: 2, width: 3, height: 4}) + property rect r2: { var x = 42; return {x}; } + property weatherModelUrl w: ({ strings: ["one", "two", "three"] }) +} diff --git a/tests/auto/qml/qmlcppcodegen/data/theme.cpp b/tests/auto/qml/qmlcppcodegen/data/theme.cpp index c8a2612753..10302645c0 100644 --- a/tests/auto/qml/qmlcppcodegen/data/theme.cpp +++ b/tests/auto/qml/qmlcppcodegen/data/theme.cpp @@ -1,5 +1,5 @@ // Copyright (C) 2022 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include "theme.h" diff --git a/tests/auto/qml/qmlcppcodegen/data/theme.h b/tests/auto/qml/qmlcppcodegen/data/theme.h index f70a3b440e..3e56ec1f9f 100644 --- a/tests/auto/qml/qmlcppcodegen/data/theme.h +++ b/tests/auto/qml/qmlcppcodegen/data/theme.h @@ -1,5 +1,5 @@ // Copyright (C) 2022 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #ifndef THEME_H #define THEME_H diff --git a/tests/auto/qml/qmlcppcodegen/data/thisObject.qml b/tests/auto/qml/qmlcppcodegen/data/thisObject.qml new file mode 100644 index 0000000000..50664eced2 --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/thisObject.qml @@ -0,0 +1,11 @@ +pragma Strict +import QtQml + +QtObject { + property QtObject warned + + function f(arg: QtObject) { warned = arg } + function warn() { f(this) } + + Component.onCompleted: warn() +} diff --git a/tests/auto/qml/qmlcppcodegen/data/timelinetheme.cpp b/tests/auto/qml/qmlcppcodegen/data/timelinetheme.cpp index 1853d2dc12..04e1797c7c 100644 --- a/tests/auto/qml/qmlcppcodegen/data/timelinetheme.cpp +++ b/tests/auto/qml/qmlcppcodegen/data/timelinetheme.cpp @@ -1,5 +1,5 @@ // Copyright (C) 2022 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include "timelinetheme.h" diff --git a/tests/auto/qml/qmlcppcodegen/data/timelinetheme.h b/tests/auto/qml/qmlcppcodegen/data/timelinetheme.h index 335e43b570..3e7da77cc9 100644 --- a/tests/auto/qml/qmlcppcodegen/data/timelinetheme.h +++ b/tests/auto/qml/qmlcppcodegen/data/timelinetheme.h @@ -1,5 +1,5 @@ // Copyright (C) 2022 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #ifndef TIMELINETHEME_H #define TIMELINETHEME_H diff --git a/tests/auto/qml/qmlcppcodegen/data/topLevelComponent.qml b/tests/auto/qml/qmlcppcodegen/data/topLevelComponent.qml new file mode 100644 index 0000000000..a73e28f642 --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/topLevelComponent.qml @@ -0,0 +1,14 @@ +pragma Strict +import QtQml + +Component { + QtObject { + id: root + + function myOpen() { + root.objectName = "foo" + } + + Component.onCompleted: myOpen() + } +} diff --git a/tests/auto/qml/qmlcppcodegen/data/trigraphs.qml b/tests/auto/qml/qmlcppcodegen/data/trigraphs.qml new file mode 100644 index 0000000000..e7bf5ccec9 --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/trigraphs.qml @@ -0,0 +1,5 @@ +import QtQml + +QtObject { + objectName: "??= ??/ ??' ??( ??) ??! ??< ??> ??-" +} diff --git a/tests/auto/qml/qmlcppcodegen/data/tst_qmlcppcodegen_verify.cpp b/tests/auto/qml/qmlcppcodegen/data/tst_qmlcppcodegen_verify.cpp new file mode 100644 index 0000000000..02629ad7f6 --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/tst_qmlcppcodegen_verify.cpp @@ -0,0 +1,47 @@ +// Copyright (C) 2023 The Qt Company Ltd. + +#include <QtTest> +#include <QtCore/qobject.h> +#include <QtCore/qdir.h> +#include <QtCore/qfile.h> +#include <QtCore/qstring.h> +#include <QtCore/qbytearray.h> + +class tst_QmlCppCodegenVerify : public QObject +{ + Q_OBJECT +private slots: + void verifyGeneratedSources_data(); + void verifyGeneratedSources(); +}; + +void tst_QmlCppCodegenVerify::verifyGeneratedSources_data() +{ + QTest::addColumn<QString>("file"); + + QDir a(":/a"); + const QStringList entries = a.entryList(QDir::Files); + for (const QString &entry : entries) + QTest::addRow("%s", entry.toUtf8().constData()) << entry; +} + +void tst_QmlCppCodegenVerify::verifyGeneratedSources() +{ + QFETCH(QString, file); + QFile a(":/a/" + file); + QFile b(":/b/" + file.replace("codegen_test_module", "codegen_test_module_verify")); + + QVERIFY(a.open(QIODevice::ReadOnly)); + QVERIFY(b.open(QIODevice::ReadOnly)); + + const QByteArray aData = a.readAll(); + const QByteArray bData = b.readAll() + .replace("verify/TestTypes", "TestTypes") + .replace("verify_TestTypes", "TestTypes"); + + QCOMPARE(aData, bData); +} + +QTEST_MAIN(tst_QmlCppCodegenVerify) + +#include "tst_qmlcppcodegen_verify.moc" diff --git a/tests/auto/qml/qmlcppcodegen/data/undefinedToDouble.qml b/tests/auto/qml/qmlcppcodegen/data/undefinedToDouble.qml new file mode 100644 index 0000000000..e76443a2e0 --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/undefinedToDouble.qml @@ -0,0 +1,6 @@ +pragma Strict +import QtQml + +QtObject { + property double d: Math.max(undefined, 40) +} diff --git a/tests/auto/qml/qmlcppcodegen/data/urlString.qml b/tests/auto/qml/qmlcppcodegen/data/urlString.qml index 511c54532c..a83855ebdb 100644 --- a/tests/auto/qml/qmlcppcodegen/data/urlString.qml +++ b/tests/auto/qml/qmlcppcodegen/data/urlString.qml @@ -9,5 +9,12 @@ QtObject { Component.onCompleted: { c = "http://dddddd.com"; self.d = "http://aaaaaa.com"; + myUrlChanged(c) + } + + signal myUrlChanged(urlParam: url) + + onMyUrlChanged: (urlParam) => { + objectName = urlParam; } } diff --git a/tests/auto/qml/qmlcppcodegen/data/valueTypeCast.qml b/tests/auto/qml/qmlcppcodegen/data/valueTypeCast.qml new file mode 100644 index 0000000000..a775773dda --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/valueTypeCast.qml @@ -0,0 +1,43 @@ +pragma ValueTypeBehavior: Addressable +import QtQml + +QtObject { + id: root + property rect r: Qt.rect(10, 20, 3, 4) + property var v: r + property real x: (v as rect).x + + function f(input: bool) : var { + if (input) + return 0 + return Qt.point(2, 2) + } + + property var vv: Qt.point(5, 5) + property var uu: undefined + + property int tv3: (root.vv as point)?.x + property var tv4: (root.uu as rect)?.x + property int tc3: (root?.vv as point)?.y + property var tc6: (root?.uu as rect)?.height + property var tc7: (f(true) as point)?.x + property var tc8: (f(false) as point)?.x + + property string greeting1 + property string greeting2 + + readonly property string defaultGreeting: "Default Greeting" + property QtObject o: QtObject { + id: o + property var customGreeting + function greet() : string { + return (o.customGreeting as string) ?? root.defaultGreeting + } + } + + Component.onCompleted: { + root.greeting1 = o.greet() + o.customGreeting = "Custom Greeting" + root.greeting2 = o.greet() + } +} diff --git a/tests/auto/qml/qmlcppcodegen/data/valueTypeCopy.qml b/tests/auto/qml/qmlcppcodegen/data/valueTypeCopy.qml index cca634753d..d33133bb6b 100644 --- a/tests/auto/qml/qmlcppcodegen/data/valueTypeCopy.qml +++ b/tests/auto/qml/qmlcppcodegen/data/valueTypeCopy.qml @@ -1,4 +1,4 @@ -pragma ValueTypeBehavior: Copy +pragma ValueTypeBehavior: Copy, Addressable import QtQml QtObject { diff --git a/tests/auto/qml/qmlcppcodegen/data/valueTypeDefault.qml b/tests/auto/qml/qmlcppcodegen/data/valueTypeDefault.qml new file mode 100644 index 0000000000..42a55e832d --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/valueTypeDefault.qml @@ -0,0 +1,34 @@ +import QtQml + + +QtObject { + id: root + + property list<double> numbers: { + var result = []; + for (var i = 0; i < 10; ++i) + result[i] = i; + return result; + } + + property rect r: ({x: 1, y: 2, width: 3, height: 4}) + + function evil() : double { + var numbers = root.numbers; + root.numbers = []; + var a = 0; + for (var j = 0; j < 10; ++j) { + a += numbers[j]; + } + return a; + } + + function fvil() : double { + var r = root.r; + root.r = {x: 5, y: 6, width: 7, height: 8}; + return r.x; + } + + property double e: evil() + property double f: fvil() +} diff --git a/tests/auto/qml/qmlcppcodegen/data/valueTypeReference.qml b/tests/auto/qml/qmlcppcodegen/data/valueTypeReference.qml index 568f39820c..a87f88ecf9 100644 --- a/tests/auto/qml/qmlcppcodegen/data/valueTypeReference.qml +++ b/tests/auto/qml/qmlcppcodegen/data/valueTypeReference.qml @@ -1,4 +1,4 @@ -pragma ValueTypeBehavior: Reference +pragma ValueTypeBehavior: Reference, Inaddressable import QtQml QtObject { diff --git a/tests/auto/qml/qmlcppcodegen/data/variantMap.qml b/tests/auto/qml/qmlcppcodegen/data/variantMap.qml new file mode 100644 index 0000000000..d7147ec5fc --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/variantMap.qml @@ -0,0 +1,27 @@ +pragma Strict +import QtQml + +QtObject { + property Component shadowable: QtObject {} + property B b: B { id: theB } + property rect r: theB.r + property var v: { "1": null, "25": undefined, "19": "19" } + + property Component c: Component { + id: unshadowable + QtObject {} + } + + // We need this extra function in order to coerce the result of the shadowable + // method call back to QtObject + function createShadowable() : QtObject { + return shadowable.createObject(this, {objectName: "a"}) + } + + objectName: { + return createShadowable().objectName + + " " + unshadowable.createObject(this, {objectName: "b"}).objectName + } + + Component.onCompleted: b.r = { x: 12, y: 13, width: 14, height: 15 } +} diff --git a/tests/auto/qml/qmlcppcodegen/data/variantMapLookup.h b/tests/auto/qml/qmlcppcodegen/data/variantMapLookup.h new file mode 100644 index 0000000000..61d38228b0 --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/variantMapLookup.h @@ -0,0 +1,17 @@ +#pragma once +#include <QObject> +#include <QVariantMap> +#include <QtQml/qqmlregistration.h> + +class VariantMapLookupFoo : public QObject +{ + Q_OBJECT + QML_ELEMENT + Q_PROPERTY(QVariantMap data READ data CONSTANT) + +public: + VariantMapLookupFoo(QObject *parent = nullptr) : QObject(parent) { } + +private: + QVariantMap data() const { return { { "value", 42 } }; } +}; diff --git a/tests/auto/qml/qmlcppcodegen/data/variantMapLookup.qml b/tests/auto/qml/qmlcppcodegen/data/variantMapLookup.qml new file mode 100644 index 0000000000..4e56cb9448 --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/variantMapLookup.qml @@ -0,0 +1,11 @@ +pragma Strict +import TestTypes +import QtQuick + +Item { + property int i: moo.data.value + + VariantMapLookupFoo { + id: moo + } +} diff --git a/tests/auto/qml/qmlcppcodegen/data/variantReturn.qml b/tests/auto/qml/qmlcppcodegen/data/variantReturn.qml new file mode 100644 index 0000000000..cca26265c9 --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/variantReturn.qml @@ -0,0 +1,15 @@ +pragma Strict +import QtQml +import TestTypes + +QtObject { + property DirectBindable a: DirectBindable { + id: aId + x: WeatherModelUrlUtils.url(1) + } + + property IndirectBindable b: IndirectBindable { + id: bId + y: aId.x + } +} diff --git a/tests/auto/qml/qmlcppcodegen/data/variantreturn.h b/tests/auto/qml/qmlcppcodegen/data/variantreturn.h new file mode 100644 index 0000000000..87718aaef3 --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/variantreturn.h @@ -0,0 +1,63 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#ifndef VARIANTERETURN_H +#define VARIANTERETURN_H + +#include <QtCore/qobject.h> +#include <QtCore/qproperty.h> +#include <QtQml/qqmlregistration.h> + +#include "weathermoduleurl.h" + +class DirectBindable : public QObject +{ + Q_OBJECT + QML_ELEMENT + Q_PROPERTY(WeatherModelUrl x READ x WRITE setX NOTIFY xChanged BINDABLE bindableX) + +public: + explicit DirectBindable(QObject *parent = nullptr) : QObject(parent) {} + + WeatherModelUrl x() const { return m_x.value(); } + void setX(const WeatherModelUrl& newX) { m_x.setValue(newX);} + QBindable<WeatherModelUrl> bindableX() { return QBindable<WeatherModelUrl>(&m_x); } + +Q_SIGNALS: + void xChanged(); + +private: + Q_OBJECT_BINDABLE_PROPERTY(DirectBindable, WeatherModelUrl, m_x, &DirectBindable::xChanged) +}; + +class IndirectBindable : public QObject +{ + Q_OBJECT + QML_ELEMENT + Q_PROPERTY(WeatherModelUrl y READ y WRITE setY NOTIFY yChanged BINDABLE bindableY) + Q_PROPERTY(int z READ z NOTIFY zChanged) + +public: + explicit IndirectBindable(QObject *parent = nullptr) : QObject(parent) { + m_z.setBinding([this]()->int { + return m_y.value().timeIndex() * 2; + }); + } + + WeatherModelUrl y() const { return m_y.value(); } + void setY(const WeatherModelUrl& newY) { m_y.setValue(newY); } + QBindable<WeatherModelUrl> bindableY() { return QBindable<WeatherModelUrl>(&m_y); } + + int z() const { return m_z.value(); } + QBindable<int> bindableZ() const { return QBindable<int>(&m_z); } + +Q_SIGNALS: + void yChanged(); + void zChanged(); + +private: + Q_OBJECT_BINDABLE_PROPERTY(IndirectBindable, WeatherModelUrl, m_y, &IndirectBindable::yChanged) + Q_OBJECT_BINDABLE_PROPERTY(IndirectBindable, int, m_z, &IndirectBindable::zChanged) +}; + +#endif // VARIANTRETURN_H diff --git a/tests/auto/qml/qmlcppcodegen/data/voidConversion.qml b/tests/auto/qml/qmlcppcodegen/data/voidConversion.qml new file mode 100644 index 0000000000..f5826d9e48 --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/voidConversion.qml @@ -0,0 +1,10 @@ +import QtQml + +QtObject { + id: item + property point p: Qt.point(20, 10) + + Component.onCompleted: { + item.p = undefined + } +} diff --git a/tests/auto/qml/qmlcppcodegen/data/weathermoduleurl.h b/tests/auto/qml/qmlcppcodegen/data/weathermoduleurl.h new file mode 100644 index 0000000000..998118dc5b --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/weathermoduleurl.h @@ -0,0 +1,61 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#ifndef WEATHERMODELURL_H +#define WEATHERMODELURL_H + +#include <QtQml/qqmlregistration.h> +#include <QtCore/qobject.h> + +class WeatherModelUrl +{ + Q_GADGET + QML_STRUCTURED_VALUE + QML_VALUE_TYPE(weatherModelUrl) + Q_PROPERTY(qsizetype timeIndex READ timeIndex CONSTANT) + Q_PROPERTY(QStringList strings READ strings WRITE setStrings) + +public: + WeatherModelUrl() : m_timeIndex(-1) {} + WeatherModelUrl(qsizetype timeIdx) : m_timeIndex(timeIdx) {} + + qsizetype timeIndex() const { return m_timeIndex; } + + QStringList strings() const { return m_strings; } + void setStrings(const QStringList &newStrings) + { + if (m_strings != newStrings) + m_strings = newStrings; + } + +private: + friend bool operator==(const WeatherModelUrl &a, const WeatherModelUrl &b) + { + return a.m_timeIndex == b.m_timeIndex; + } + + friend bool operator!=(const WeatherModelUrl &a, const WeatherModelUrl &b) + { + return !(a == b); + } + + qsizetype m_timeIndex; + QStringList m_strings; +}; + +class WeatherModelUrlUtils : public QObject +{ + Q_OBJECT + QML_ELEMENT + QML_SINGLETON + +public: + WeatherModelUrlUtils(QObject *parent = nullptr) : QObject(parent) {} + + Q_INVOKABLE static WeatherModelUrl url(int timeIdx) + { + return WeatherModelUrl(timeIdx); + } +}; + +#endif // WEATHERMODELURL_H diff --git a/tests/auto/qml/qmlcppcodegen/data/withlength.h b/tests/auto/qml/qmlcppcodegen/data/withlength.h index ba95522c53..26c6307f2b 100644 --- a/tests/auto/qml/qmlcppcodegen/data/withlength.h +++ b/tests/auto/qml/qmlcppcodegen/data/withlength.h @@ -1,11 +1,13 @@ // Copyright (C) 2022 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #ifndef WITHLENGTH_H #define WITHLENGTH_H #include <QtCore/qobject.h> -#include <QtQmlIntegration/qqmlintegration.h> +#include <QtCore/qpoint.h> +#include <QtCore/qrect.h> +#include <QtQml/qqml.h> struct ValueTypeWithLength { @@ -18,6 +20,8 @@ struct ValueTypeWithLength public: ValueTypeWithLength() = default; Q_INVOKABLE ValueTypeWithLength(int length) : m_length(length) {} + Q_INVOKABLE ValueTypeWithLength(QPointF point) : m_length(point.manhattanLength()) {} + Q_INVOKABLE ValueTypeWithLength(QRectF rect) : m_length(rect.width()) {} Q_INVOKABLE QString toString() const { return QStringLiteral("no"); } int length() const { return m_length; } @@ -26,4 +30,24 @@ private: int m_length = 19; }; +struct InnerWithLength { + int m_length; +}; + +struct UnconstructibleWithLength +{ + Q_GADGET + QML_VALUE_TYPE(unconstructibleWithLength) + + QML_FOREIGN(InnerWithLength) + QML_EXTENDED(UnconstructibleWithLength) + +public: + UnconstructibleWithLength() = default; + Q_INVOKABLE UnconstructibleWithLength(int length) : v{length} {} + +private: + InnerWithLength v; +}; + #endif // WITHLENGTH_H diff --git a/tests/auto/qml/qmlcppcodegen/data/wrapwithvariant.h b/tests/auto/qml/qmlcppcodegen/data/wrapwithvariant.h index dce78fa9c9..7de49f095a 100644 --- a/tests/auto/qml/qmlcppcodegen/data/wrapwithvariant.h +++ b/tests/auto/qml/qmlcppcodegen/data/wrapwithvariant.h @@ -1,5 +1,5 @@ // Copyright (C) 2022 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #ifndef WRAPWITHVARIANT_H #define WRAPWITHVARIANT_H diff --git a/tests/auto/qml/qmlcppcodegen/data/writableVariantMap.h b/tests/auto/qml/qmlcppcodegen/data/writableVariantMap.h new file mode 100644 index 0000000000..3c0fedd28b --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/writableVariantMap.h @@ -0,0 +1,31 @@ +#pragma once +#include <QObject> +#include <QVariantMap> +#include <QtQml/qqmlregistration.h> + +class WritableVariantMap : public QObject +{ + Q_OBJECT + QML_ELEMENT + Q_PROPERTY(QVariantMap data READ data WRITE setData NOTIFY dataChanged) + +public: + WritableVariantMap(QObject *parent = nullptr) : QObject(parent) { } + + QVariantMap data() const { return m_data; } + void setData(const QVariantMap &data) + { + if (m_data != data) { + m_data = data; + emit dataChanged(); + } + } + +signals: + void dataChanged(); + +private: + QVariantMap m_data; +}; + + diff --git a/tests/auto/qml/qmlcppcodegen/data/writeVariantMap.qml b/tests/auto/qml/qmlcppcodegen/data/writeVariantMap.qml new file mode 100644 index 0000000000..536e53b408 --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/writeVariantMap.qml @@ -0,0 +1,10 @@ +pragma Strict +import StringBuilderTestTypes + +WritableVariantMap { + id: dragSource + property string modelData: "Drag Me" + data: ({ + "text/plain": "%" + dragSource.modelData + "%" + }) +} diff --git a/tests/auto/qml/qmlcppcodegen/data/writeback.qml b/tests/auto/qml/qmlcppcodegen/data/writeback.qml new file mode 100644 index 0000000000..359f00efb7 --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/writeback.qml @@ -0,0 +1,43 @@ +pragma Strict + +import TestTypes +import QtQml + +Person { + id: self + + area { + width: 19 + height: 199 + } + + property list<int> ints: [4, 3, 2, 1] + + property outer recursive + property Person shadowable: Person { + id: notShadowable + area.width: self.area.width + area2.height: self.area2.height + } + + Component.onCompleted: { + area.width = 16 + area2.height = 17 + + self.area.x = 4 + self.area2.y = 5 + + // You cannot do this on the shadowable Person because + // shadowable.area may not actually be a QRectF anymore. + notShadowable.area.x = 40 + notShadowable.area2.y = 50 + + self.recursive.inner.i = 99; + + self.ints[0] = 12; + ints[1] = 22; + ints[6] = 33; + } + + property int inner: recursive.inner.i +} diff --git a/tests/auto/qml/qmlcppcodegen/tst_qmlcppcodegen.cpp b/tests/auto/qml/qmlcppcodegen/tst_qmlcppcodegen.cpp index 400075c08d..53cc068e8c 100644 --- a/tests/auto/qml/qmlcppcodegen/tst_qmlcppcodegen.cpp +++ b/tests/auto/qml/qmlcppcodegen/tst_qmlcppcodegen.cpp @@ -1,10 +1,16 @@ -// Copyright (C) 2021 The Qt Company Ltd. +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only -#include "data/druggeljug.h" #include <data/birthdayparty.h> #include <data/cppbaseclass.h> +#include <data/druggeljug.h> +#include <data/enumProperty.h> #include <data/enumproblems.h> +#include <data/getOptionalLookup.h> #include <data/objectwithmethod.h> +#include <data/resettable.h> +#include <data/weathermoduleurl.h> +#include <data/withlength.h> #include <QtQml/private/qqmlengine_p.h> #include <QtQml/private/qqmlpropertycachecreator_p.h> @@ -12,6 +18,7 @@ #include <QtTest> #include <QtQml> #include <QtGui/qcolor.h> +#include <QtGui/qpa/qplatformdialoghelper.h> #if QT_CONFIG(process) #include <QtCore/qprocess.h> @@ -26,146 +33,460 @@ class tst_QmlCppCodegen : public QObject Q_OBJECT private slots: void initTestCase(); + void cleanupTestCase(); - void simpleBinding(); - void cppValueTypeList(); + void accessModelMethodFromOutSide(); + void aliasLookup(); + void ambiguousAs(); + void ambiguousSignals(); void anchorsFill(); - void signalHandler(); - void idAccess(); - void globals(); - void multiLookup(); - void enums(); - void funcWithParams(); - void intOverflow(); - void stringLength(); - void scopeVsObject(); - void compositeTypeMethod(); - void excessiveParameters(); - void jsImport(); - void jsmoduleImport(); - void methods(); - void math(); - void unknownParameter(); + void argumentConversion(); void array(); - void equalsUndefined(); - void conversions(); - void interestingFiles_data(); - void interestingFiles(); - void extendedTypes(); - void construct(); - void contextParam(); - void attachedType(); - void componentReturnType(); - void onAssignment(); - void failures(); - void enumScope(); - void unusedAttached(); + void arrayCtor(); + void asCast(); void attachedBaseEnum(); - void nullAccess(); - void interceptor(); - void nonNotifyable(); - void importsFromImportPath(); - void aliasLookup(); - void outOfBoundsArray(); - void compositeSingleton(); - void lotsOfRegisters(); - void inPlaceDecrement(); - void shifts(); - void valueTypeProperty(); - void propertyOfParent(); - void accessModelMethodFromOutSide(); - void functionArguments(); + void attachedSelf(); + void attachedType(); + void badSequence(); + void basicBlocksWithBackJump(); + void basicBlocksWithBackJump_infinite(); + void basicDTZ(); + void bindToValueType(); void bindingExpression(); - void voidFunction(); - void overriddenProperty(); - void listLength(); - void parentProperty(); - void registerElimination(); - void asCast(); - void noQQmlData(); - void scopeObjectDestruction(); + void blockComments(); + void boolCoercions(); + void boolPointerMerge(); + void boundComponents(); + void callContextPropertyLookupResult(); + void callWithSpread(); void colorAsVariant(); - void bindToValueType(); - void undefinedResets(); - void innerObjectNonShadowable(); - void ownPropertiesNonShadowable(); - void modulePrefix(); void colorString(); - void urlString(); - void callContextPropertyLookupResult(); + void compareOriginals(); + void comparisonTypes(); + void componentReturnType(); + void compositeSingleton(); + void compositeTypeMethod(); + void consoleObject(); + void consoleTrace(); + void construct(); + void contextParam(); + void conversionDecrement(); + void conversionInDeadCode(); + void conversions(); + void convertPrimitiveToVar(); + void convertQJSPrimitiveValueToIntegral(); + void convertToOriginalReadAcumulatorForUnaryOperators(); + void cppMethodListReturnType(); + void cppValueTypeList(); + void dateConstruction(); + void dateConversions(); void deadShoeSize(); - void listIndices(); - void jsMathObject(); - void intEnumCompare(); - void attachedSelf(); - void functionReturningVoid(); - void functionCallOnNamespaced(); + void dialogButtonBox(); + void enumConversion(); + void enumFromBadSingleton(); + void enumLookup(); + void enumMarkedAsFlag(); + void enumProblems(); + void enumScope(); + void enums(); + void enforceSignature(); + void enumsInOtherObject(); + void equalityQObjects(); + void equalityQUrl(); + void equalityTestsWithNullOrUndefined(); + void equalityVarAndNonStorable(); + void equalityVarAndStorable(); + void equalsUndefined(); + void evadingAmbiguity(); + void exceptionFromInner(); + void excessiveParameters(); + void extendedTypes(); + void failures(); + void fallbackLookups(); + void fileImportsContainCxxTypes(); + void flagEnum(); void flushBeforeCapture(); - void unknownAttached(); - void variantlist(); - void popContextAfterRet(); - void revisions(); - void invisibleBase(); - void notEqualsInt(); - void infinities(); - void blockComments(); + void fromBoolValue(); + void funcWithParams(); + void functionArguments(); + void functionCallOnNamespaced(); void functionLookup(); - void objectInVar(); + void functionReturningVoid(); void functionTakingVar(); - void testIsnan(); - void fallbackLookups(); - void typedArray(); - void prefixedType(); - void evadingAmbiguity(); - void fromBoolValue(); - void invisibleTypes(); + void getLookupOfScript(); + void getOptionalLookup(); + void getOptionalLookup_data(); + void getOptionalLookupOnQJSValueNonStrict(); + void getOptionalLookupShadowed(); + void globals(); + void idAccess(); + void ignoredFunctionReturn(); + void importsFromImportPath(); + void inPlaceDecrement(); + void inaccessibleProperty(); + void indirectlyShadowable(); + void infinities(); + void infinitiesToInt(); + void innerObjectNonShadowable(); + void intEnumCompare(); + void intOverflow(); + void intToEnum(); + void interceptor(); + void interestingFiles(); + void interestingFiles_data(); + void internalConversion(); void invalidPropertyType(); - void valueTypeLists(); - void boundComponents(); + void invisibleBase(); void invisibleListElementType(); - void typePropertyClash(); - void objectToString(); - void throwObjectName(); + void invisibleSingleton(); + void invisibleTypes(); + void iteration(); void javaScriptArgument(); - void translation(); - void stringArg(); - void conversionDecrement(); - void unstoredUndefined(); - void registerPropagation(); - void argumentConversion(); - void badSequence(); - void enumLookup(); - void trivialSignalHandler(); - void stringToByteArray(); + void jsArrayMethods(); + void jsArrayMethodsWithParams(); + void jsArrayMethodsWithParams_data(); + void jsImport(); + void jsMathObject(); + void jsmoduleImport(); + void lengthAccessArraySequenceCompat(); + void letAndConst(); + void listAsArgument(); + void listConversion(); + void listIndices(); + void listLength(); + void listOfInvisible(); void listPropertyAsModel(); - void notNotString(); + void listToString(); + void lotsOfRegisters(); + void math(); + void mathMinMax(); void mathOperations(); - void inaccessibleProperty(); - void typePropagationLoop(); - void signatureIgnored(); - void listAsArgument(); - void letAndConst(); - void signalIndexMismatch(); - void callWithSpread(); - void nullComparison(); - void consoleObject(); + void mathStaticProperties(); + void mergedObjectReadWrite(); + void methodOnListLookup(); + void methods(); + void modulePrefix(); + void multiDirectory_data(); + void multiDirectory(); void multiForeign(); + void multiLookup(); + void multipleCtors(); void namespaceWithEnum(); - void enumProblems(); - void enumConversion(); - void ambiguousSignals(); - void fileImportsContainCxxTypes(); - void lengthAccessArraySequenceCompat(); - void storeElementSideEffects(); + void noQQmlData(); + void nonNotifyable(); + void notEqualsInt(); + void notNotString(); + void nullAccess(); + void nullAccessInsideSignalHandler(); + void nullComparison(); + void nullishCoalescing(); + void nullishCoalescing_data(); void numbersInJsPrimitive(); - void infinitiesToInt(); - void equalityVarAndNonStorable(); - void equalityQObjects(); - void dateConversions(); + void objectInVar(); + void objectLookupOnListElement(); + void objectToString(); + void objectWithStringListMethod(); + void onAssignment(); + void optionalComparison(); + void outOfBoundsArray(); + void overriddenProperty(); + void ownPropertiesNonShadowable(); + void parentProperty(); + void popContextAfterRet(); + void prefixedType(); + void propertyOfParent(); + void reduceWithNullThis(); + void readEnumFromInstance(); + void readonlyListProperty(); + void registerElimination(); + void registerPropagation(); + void renameAdjust(); + + void resettableProperty(); + void resettableProperty_data(); + + void returnAfterReject(); + void revisions(); + void scopeIdLookup(); + void scopeObjectDestruction(); + void scopeVsObject(); + void scopedEnum(); + void sequenceToIterable(); + void setLookupConversion(); + void setLookupOriginalScope(); + void shadowedAsCasts(); + void shadowedMethod(); + void shadowedPrimitiveCmpEqNull(); + void shifts(); + void signalHandler(); + void signalIndexMismatch(); + void signalsWithLists(); + void signatureIgnored(); + void simpleBinding(); + void storeElementSideEffects(); + void storeMetaEnum(); + void stringArg(); + void stringLength(); + void stringToByteArray(); + void structuredValueType(); + void testIsnan(); + void thisObject(); + void throwObjectName(); + void topLevelComponent(); + void translation(); + void trigraphs(); + void trivialSignalHandler(); + void typePropagationLoop(); + void typePropertyClash(); + void typedArray(); + void undefinedResets(); + void undefinedToDouble(); + void unknownAttached(); + void unknownParameter(); + void unstoredUndefined(); + void unusedAttached(); + void urlString(); void valueTypeBehavior(); - void invisibleSingleton(); + void valueTypeLists(); + void valueTypeProperty(); + void variantMapLookup(); + void variantReturn(); + void variantlist(); + void variantMap(); + void voidConversion(); + void voidFunction(); + void writeBack(); + void writeVariantMap(); }; +static QByteArray arg1() +{ + const QStringList args = QCoreApplication::instance()->arguments(); + return args.size() > 1 ? args[1].toUtf8() : QByteArray("undefined"); +} + +namespace QmlCacheGeneratedCode { +namespace _qt_qml_TestTypes_failures_qml { +extern const QQmlPrivate::AOTCompiledFunction aotBuiltFunctions[]; +} +} + +static void checkColorProperties(QQmlComponent *component) +{ + QVERIFY2(component->isReady(), qPrintable(component->errorString())); + QScopedPointer<QObject> rootObject(component->create()); + QVERIFY(rootObject); + + const QMetaObject *mo = QMetaType::fromName("QQuickIcon").metaObject(); + QVERIFY(mo != nullptr); + + const QMetaProperty prop = mo->property(mo->indexOfProperty("color")); + QVERIFY(prop.isValid()); + + const QVariant a = rootObject->property("a"); + QVERIFY(a.isValid()); + + const QVariant iconColor = prop.readOnGadget(rootObject->property("icon").data()); + QVERIFY(iconColor.isValid()); + + const QMetaType colorType = QMetaType::fromName("QColor"); + QVERIFY(colorType.isValid()); + + QCOMPARE(a.metaType(), colorType); + QCOMPARE(iconColor.metaType(), colorType); + + QCOMPARE(iconColor, a); +} + +static const double numbers[] = { + qQNaN(), -qInf(), + std::numeric_limits<double>::min(), + std::numeric_limits<float>::min(), + std::numeric_limits<qint32>::min(), + -1000.2, -100, -2, -1.333, -1, -0.84, -0.5, + + // -0 and 0 are not different on the QML side. Therefore, don't keep them adjacent. + // Otherwise the bindings won't get re-evaluated. + std::copysign(0.0, -1), 1, 0.0, + + 0.5, 0.77, 1.4545, 2, 199, 2002.13, + std::numeric_limits<qint32>::max(), + std::numeric_limits<quint32>::max(), + std::numeric_limits<float>::max(), + std::numeric_limits<double>::max(), + qInf() +}; + +class MyCppType : public QObject +{ + Q_OBJECT + Q_PROPERTY(bool useListDelegate + READ useListDelegate + WRITE setUseListDelegate + NOTIFY useListDelegateChanged) +public: + explicit MyCppType(QObject * parent = nullptr) : QObject(parent) {} + + bool useListDelegate() const { return m_useListDelegate; } + void setUseListDelegate(bool useListDelegate) + { + if (useListDelegate != m_useListDelegate) { + m_useListDelegate = useListDelegate; + emit useListDelegateChanged(); + } + } + +signals: + void useListDelegateChanged(); + +private: + bool m_useListDelegate = false; +}; + +class InvisibleListElementType : public QObject +{ + Q_OBJECT +public: + InvisibleListElementType(QObject *parent = nullptr) : QObject(parent) {} +}; + +template<typename T> +QString toOperand(double arg); + +template<> +QString toOperand<double>(double arg) +{ + if (qIsNull(arg)) + return std::signbit(arg) ? QStringLiteral("(-0)") : QStringLiteral("(0)"); + + return u'(' + QJSPrimitiveValue(arg).toString() + u')'; +} + +template<> +QString toOperand<int>(double arg) +{ + const int iArg = QJSPrimitiveValue(arg).toInteger(); + return u'(' + QJSPrimitiveValue(iArg).toString() + u')'; +} + +template<> +QString toOperand<bool>(double arg) +{ + const bool bArg = QJSPrimitiveValue(arg).toBoolean(); + return u'(' + QJSPrimitiveValue(bArg).toString() + u')'; +} + +template<typename T1, typename T2> +double jsEval(double arg1, double arg2, const QString &op, QJSEngine *engine) +{ + auto evalBinary = [&](const QString &jsOp) { + return engine->evaluate(toOperand<T1>(arg1) + jsOp + toOperand<T2>(arg2)).toNumber(); + }; + + auto evalBinaryConst = [&](const QString &jsOp) { + return engine->evaluate(toOperand<T1>(arg1) + jsOp + u'9').toNumber(); + }; + + auto evalUnary = [&](const QString &jsOp) { + return engine->evaluate(jsOp + toOperand<T1>(arg1)).toNumber(); + }; + + auto evalInPlace = [&](const QString &jsOp) { + return engine->evaluate( + u"(function() {var a = "_s + toOperand<T1>(arg1)+ u"; return "_s + + jsOp + u"a;})()"_s).toNumber(); + }; + + if (op == u"unot") + return evalUnary(u"!"_s); + if (op == u"uplus") + return evalUnary(u"+"_s); + if (op == u"uminus") + return evalUnary(u"-"_s); + if (op == u"ucompl") + return evalUnary(u"~"_s); + + if (op == u"increment") + return evalInPlace(u"++"_s); + if (op == u"decrement") + return evalInPlace(u"--"_s); + + if (op == u"add") + return evalBinary(u"+"_s); + if (op == u"sub") + return evalBinary(u"-"_s); + if (op == u"mul") + return evalBinary(u"*"_s); + if (op == u"div") + return evalBinary(u"/"_s); + if (op == u"exp") + return evalBinary(u"**"_s); + if (op == u"mod") + return evalBinary(u"%"_s); + + if (op == u"bitAnd") + return evalBinary(u"&"_s); + if (op == u"bitOr") + return evalBinary(u"|"_s); + if (op == u"bitXor") + return evalBinary(u"^"_s); + + if (op == u"bitAndConst") + return evalBinaryConst(u"&"_s); + if (op == u"bitOrConst") + return evalBinaryConst(u"|"_s); + if (op == u"bitXorConst") + return evalBinaryConst(u"^"_s); + + if (op == u"ushr") + return evalBinary(u">>>"_s); + if (op == u"shr") + return evalBinary(u">>"_s); + if (op == u"shl") + return evalBinary(u"<<"_s); + + if (op == u"ushrConst") + return evalBinaryConst(u">>>"_s); + if (op == u"shrConst") + return evalBinaryConst(u">>"_s); + if (op == u"shlConst") + return evalBinaryConst(u"<<"_s); + + qDebug() << op; + Q_UNREACHABLE_RETURN(0); +} + +static QList<QString> convertToStrings(const QList<qint64> &ints) +{ + QList<QString> strings; + for (qint64 i : ints) + strings.append(QString::number(i)); + return strings; +} + +static QRegularExpression bindingLoopMessage(const QUrl &url, char var) +{ + // The actual string depends on how many times QObject* was registered with what parameters. + return QRegularExpression( + "%1:4:1: QML [^:]+: Binding loop detected for property \"%2\""_L1 + .arg(url.toString()).arg(QLatin1Char(var))); +} + +static void listsEqual(QObject *listProp, QObject *array, const char *method) +{ + const QByteArray listPropertyPropertyName = QByteArray("listProperty") + method; + const QByteArray jsArrayPropertyName = QByteArray("jsArray") + method; + + const QQmlListReference listPropertyProperty(listProp, listPropertyPropertyName.constData()); + const QVariantList jsArrayProperty = array->property(jsArrayPropertyName.constData()).toList(); + + const qsizetype listPropertyCount = listPropertyProperty.count(); + QCOMPARE(listPropertyCount, jsArrayProperty.count()); + + for (qsizetype i = 0; i < listPropertyCount; ++i) + QCOMPARE(listPropertyProperty.at(i), jsArrayProperty.at(i).value<QObject *>()); +} + void tst_QmlCppCodegen::initTestCase() { #ifdef QT_TEST_FORCE_INTERPRETER @@ -173,40 +494,91 @@ void tst_QmlCppCodegen::initTestCase() #endif } -void tst_QmlCppCodegen::simpleBinding() +void tst_QmlCppCodegen::cleanupTestCase() +{ + // This code checks for basic blocks validation failures in the tests + QStringList expectedFailures = { + "codegen_test_module_basicBlocksWithBackJump_infinite_qml.cpp", + "codegen_test_module_verify_basicBlocksWithBackJump_infinite_qml.cpp", + }; + + QString generatedCppFolder = GENERATED_CPP_FOLDER; + QDirIterator dirIterator(generatedCppFolder, { "*.cpp" }, QDir::Files); + while (dirIterator.hasNext()) { + QFile file(dirIterator.next()); + if (!file.open(QIODeviceBase::ReadOnly | QIODeviceBase::Text)) { + qDebug() << "Couldn't open generated file"; + continue; + } + + const auto content = file.readAll(); + if (bool validationFailed = content.contains("// QV4_BASIC_BLOCK_VALIDATION_FAILED:"_L1)) { + if (expectedFailures.contains(dirIterator.fileInfo().fileName())) { + QEXPECT_FAIL("", "Expected failure", Continue); + } + const auto message = file.fileName() + ": Basic blocks validation failed."; + QVERIFY2(!validationFailed, message.toStdString().c_str()); + } + } +} + +void tst_QmlCppCodegen::accessModelMethodFromOutSide() { QQmlEngine engine; - QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/Test.qml"_s)); + QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/AccessModelMethodsFromOutside.qml"_s)); + QVERIFY2(component.isReady(), component.errorString().toUtf8()); + + QTest::ignoreMessage(QtDebugMsg, "3"); + QTest::ignoreMessage(QtDebugMsg, "Apple"); QScopedPointer<QObject> object(component.create()); - QVERIFY2(!object.isNull(), component.errorString().toUtf8().constData()); - QCOMPARE(object->property("foo").toInt(), int(3)); - { - CppBaseClass *base = qobject_cast<CppBaseClass *>(object.data()); - Q_ASSERT(base); - QVERIFY(!base->cppProp.hasBinding()); - QCOMPARE(base->cppProp.value(), 7); - QVERIFY(base->cppProp2.hasBinding()); - QCOMPARE(base->cppProp2.value(), 14); - base->cppProp.setValue(9); - QCOMPARE(base->cppProp.value(), 9); - QCOMPARE(base->cppProp2.value(), 18); - } + QCOMPARE(object->property("cost1").toDouble(), 3); + QCOMPARE(object->property("name1").toString(), u"Orange"_s); + QCOMPARE(object->property("cost2").toDouble(), 1.95); + QCOMPARE(object->property("name2").toString(), u"Banana"_s); } -void tst_QmlCppCodegen::cppValueTypeList() +void tst_QmlCppCodegen::aliasLookup() { QQmlEngine engine; - QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/Test.qml"_s)); + QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/aliasLookup.qml"_s)); + QVERIFY2(!component.isError(), component.errorString().toUtf8()); QScopedPointer<QObject> object(component.create()); - QVERIFY2(!object.isNull(), component.errorString().toUtf8().constData()); - QCOMPARE(object->property("a").toInt(), 16); - QMetaObject::invokeMethod(object.data(), "incA"); - QCOMPARE(object->property("a").toInt(), 17); + QVERIFY(!object.isNull()); - QCOMPARE(object->property("b").toDouble(), 0.25); - QMetaObject::invokeMethod(object.data(), "incB"); - QCOMPARE(object->property("b").toDouble(), 13.5); + const QVariant t = object->property("t"); + QCOMPARE(t.metaType(), QMetaType::fromType<QString>()); + QCOMPARE(t.toString(), u"12"_s); +} + +void tst_QmlCppCodegen::ambiguousAs() +{ + QQmlEngine e; + const QUrl url(u"qrc:/qt/qml/TestTypes/ambiguousAs.qml"_s); + QQmlComponent c(&e, url); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + + QScopedPointer<QObject> o(c.create()); + QVERIFY(!o.isNull()); + QCOMPARE(o->property("other").value<QObject *>(), o.data()); + o->setProperty("useSelf", QVariant::fromValue(false)); + QCOMPARE(o->property("other").value<QObject *>(), nullptr); +} + +void tst_QmlCppCodegen::ambiguousSignals() +{ + QQmlEngine engine; + QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/ambiguousSignals.qml"_s)); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> o(c.create()); + QVERIFY(!o.isNull()); + QCOMPARE(o->objectName(), u"tomorrow"_s); + Person *p = qobject_cast<Person *>(o.data()); + QVERIFY(p); + emit p->ambiguous(12); + QCOMPARE(o->objectName(), u"12foo"_s); + emit p->ambiguous(); + QCOMPARE(o->objectName(), u"9foo"_s); } void tst_QmlCppCodegen::anchorsFill() @@ -231,342 +603,629 @@ void tst_QmlCppCodegen::anchorsFill() QCOMPARE(child->property("width").toInt(), 47); } -void tst_QmlCppCodegen::signalHandler() +void tst_QmlCppCodegen::argumentConversion() { QQmlEngine engine; - QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/signal.qml"_s)); + QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/argumentConversion.qml"_s)); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> o(c.create()); + + auto checkNaN = [&](const char *propName) { + const QVariant prop = o->property(propName); + QCOMPARE(prop.metaType(), QMetaType::fromType<double>()); + QVERIFY(qIsNaN(prop.toDouble())); + }; + + checkNaN("a"); + checkNaN("b"); + checkNaN("e"); + + QCOMPARE(o->property("c").toDouble(), 3.0); + QCOMPARE(o->property("d").toDouble(), -1.0); + QCOMPARE(o->property("f").toDouble(), 10.0); +} + +void tst_QmlCppCodegen::array() +{ + QQmlEngine engine; + QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/array.qml"_s)); QVERIFY2(!component.isError(), component.errorString().toUtf8()); QScopedPointer<QObject> object(component.create()); QVERIFY(!object.isNull()); - QCOMPARE(object->objectName(), QString()); - QCOMPARE(object->property("ff").toInt(), 4); + const QJSValue value1 = object->property("values1").value<QJSValue>(); + QVERIFY(value1.isArray()); + QCOMPARE(value1.property(u"length"_s).toInt(), 3); + QCOMPARE(value1.property(0).toInt(), 1); + QCOMPARE(value1.property(1).toInt(), 2); + QCOMPARE(value1.property(2).toInt(), 3); - object->setObjectName(u"foo"_s); - QCOMPARE(object->property("ff").toInt(), 12); + const QJSValue value2 = object->property("values2").value<QJSValue>(); + QVERIFY(value2.isArray()); + QCOMPARE(value2.property(u"length"_s).toInt(), 0); } -void tst_QmlCppCodegen::idAccess() +void tst_QmlCppCodegen::arrayCtor() { QQmlEngine engine; - QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/idAccess.qml"_s)); + QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/arrayCtor.qml"_s)); QVERIFY2(!component.isError(), component.errorString().toUtf8()); QScopedPointer<QObject> object(component.create()); QVERIFY(!object.isNull()); - QVERIFY(object->property("y").toInt() != 48); - QCOMPARE(object->property("y").toInt(), 12); - object->setProperty("z", 13); - QCOMPARE(object->property("y").toInt(), 13); - object->setProperty("x", QVariant::fromValue(333)); - QCOMPARE(object->property("y").toInt(), 48); + QCOMPARE(object->property("defaultCtor"), QVariant::fromValue(QList<int>())); + QCOMPARE(object->property("oneArgCtor"), QVariant::fromValue(QList<int>(5))); + QCOMPARE(object->property("multiArgCtor"), QVariant::fromValue(QList<int>({2, 3, 3, 4}))); + QCOMPARE(object->property("arrayTrue"), QVariant::fromValue(QList<bool>({true}))); + QCOMPARE(object->property("arrayFalse"), QVariant::fromValue(QList<bool>({false}))); + QCOMPARE(object->property("arrayNegative"), QVariant::fromValue(QList<double>())); +} - // The binding was broken by setting the property - object->setProperty("z", 14); - QCOMPARE(object->property("y").toInt(), 48); +void tst_QmlCppCodegen::asCast() +{ + QQmlEngine engine; + QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/asCast.qml"_s)); + QVERIFY2(component.isReady(), component.errorString().toUtf8()); + QScopedPointer<QObject> root(component.create()); + QVERIFY(!root.isNull()); - QObject *ttt = qmlContext(object.data())->objectForName(u"ttt"_s); - QFont f = qvariant_cast<QFont>(ttt->property("font")); - QCOMPARE(f.pointSize(), 22); + QQmlContext *context = qmlContext(root.data()); + const QObject *object = context->objectForName(u"object"_s); + const QObject *item = context->objectForName(u"item"_s); + const QObject *rectangle = context->objectForName(u"rectangle"_s); + const QObject *dummy = context->objectForName(u"dummy"_s); + + QCOMPARE(qvariant_cast<QObject *>(root->property("objectAsObject")), object); + QCOMPARE(qvariant_cast<QObject *>(root->property("objectAsItem")), nullptr); + QCOMPARE(qvariant_cast<QObject *>(root->property("objectAsRectangle")), nullptr); + QCOMPARE(qvariant_cast<QObject *>(root->property("objectAsDummy")), nullptr); + + QCOMPARE(qvariant_cast<QObject *>(root->property("itemAsObject")), item); + QCOMPARE(qvariant_cast<QObject *>(root->property("itemAsItem")), item); + QCOMPARE(qvariant_cast<QObject *>(root->property("itemAsRectangle")), nullptr); + QCOMPARE(qvariant_cast<QObject *>(root->property("itemAsDummy")), nullptr); + + QCOMPARE(qvariant_cast<QObject *>(root->property("rectangleAsObject")), rectangle); + QCOMPARE(qvariant_cast<QObject *>(root->property("rectangleAsItem")), rectangle); + QCOMPARE(qvariant_cast<QObject *>(root->property("rectangleAsRectangle")), rectangle); + QCOMPARE(qvariant_cast<QObject *>(root->property("rectangleAsDummy")), nullptr); + + QCOMPARE(qvariant_cast<QObject *>(root->property("dummyAsObject")), dummy); + QCOMPARE(qvariant_cast<QObject *>(root->property("dummyAsItem")), dummy); + QCOMPARE(qvariant_cast<QObject *>(root->property("dummyAsRectangle")), nullptr); + QCOMPARE(qvariant_cast<QObject *>(root->property("dummyAsDummy")), dummy); + + QCOMPARE(qvariant_cast<QObject *>(root->property("nullAsObject")), nullptr); + QCOMPARE(qvariant_cast<QObject *>(root->property("nullAsItem")), nullptr); + QCOMPARE(qvariant_cast<QObject *>(root->property("nullAsRectangle")), nullptr); + QCOMPARE(qvariant_cast<QObject *>(root->property("nullAsDummy")), nullptr); + + QCOMPARE(qvariant_cast<QObject *>(root->property("undefinedAsObject")), nullptr); + QCOMPARE(qvariant_cast<QObject *>(root->property("undefinedAsItem")), nullptr); + QCOMPARE(qvariant_cast<QObject *>(root->property("undefinedAsRectangle")), nullptr); + QCOMPARE(qvariant_cast<QObject *>(root->property("undefinedAsDummy")), nullptr); } -static QByteArray arg1() +void tst_QmlCppCodegen::attachedBaseEnum() { - const QStringList args = QCoreApplication::instance()->arguments(); - return args.size() > 1 ? args[1].toUtf8() : QByteArray("undefined"); + QQmlEngine engine; + QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/attachedBaseEnum.qml"_s)); + QVERIFY2(!component.isError(), component.errorString().toUtf8()); + QScopedPointer<QObject> object(component.create()); + + QObject *drag = qvariant_cast<QObject *>(object->property("drag")); + QVERIFY(drag); + + // Drag.YAxis is 2, but we cannot #include it here. + bool ok = false; + QCOMPARE(drag->property("axis").toInt(&ok), 2); + QVERIFY(ok); } -void tst_QmlCppCodegen::globals() +void tst_QmlCppCodegen::attachedSelf() { QQmlEngine engine; - int exitCode = -1; - QObject::connect(&engine, &QQmlEngine::exit, [&](int code) { exitCode = code; }); - QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/globals.qml"_s)); - QVERIFY2(!component.isError(), component.errorString().toUtf8()); + QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/SelectionRectangle.qml"_s)); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> o(c.create()); + QVERIFY(o); - const QByteArray message = QByteArray("Start 2 ") + arg1(); - QTest::ignoreMessage(QtDebugMsg, message.constData()); + QObject *handle = qvariant_cast<QObject *>(o->property("aa")); + QVERIFY(handle); + QVERIFY(qvariant_cast<QObject *>(handle->property("rect")) != nullptr); +} +void tst_QmlCppCodegen::attachedType() +{ + QQmlEngine engine; + QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/text.qml"_s)); + QVERIFY2(!component.isError(), component.errorString().toUtf8()); QScopedPointer<QObject> object(component.create()); QVERIFY(!object.isNull()); - QTRY_COMPARE(exitCode, 0); + QCOMPARE(object->property("dayz").toDateTime(), QDateTime(QDate(1911, 3, 4), QTime())); + QCOMPARE(object->property("oParty").toDateTime(), QDateTime(QDate(1911, 3, 4), QTime())); - QObject *application = qvariant_cast<QObject *>(object->property("application")); - QVERIFY(application); - QCOMPARE(QString::fromUtf8(application->metaObject()->className()), - u"QQuickApplication"_s); + QObject *party = qvariant_cast<QObject *>(object->property("party")); + QVERIFY(party); + QCOMPARE(party->property("eee").toInt(), 21); + QCOMPARE(party->property("fff").toInt(), 33); + QCOMPARE(object->property("ggg").toInt(), 37); +} - QTest::ignoreMessage(QtDebugMsg, "End"); - QMetaObject::invokeMethod(application, "aboutToQuit"); +void tst_QmlCppCodegen::badSequence() +{ + QQmlEngine engine; + QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/badSequence.qml"_s)); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> o(c.create()); - const QVariant somewhere = object->property("somewhere"); - QCOMPARE(somewhere.userType(), QMetaType::QUrl); - QCOMPARE(qvariant_cast<QUrl>(somewhere).toString(), u"qrc:/somewhere/else.qml"_s); + Person *self = qobject_cast<Person *>(o.data()); + QVERIFY(self); + QVERIFY(self->barzles().isEmpty()); + QVERIFY(self->cousins().isEmpty()); - const QVariant somewhereString = object->property("somewhereString"); - QCOMPARE(somewhereString.userType(), QMetaType::QString); - QCOMPARE(somewhereString.toString(), u"qrc:/somewhere/else.qml"_s); + Person *other = o->property("other").value<Person *>(); + QVERIFY(other); - const QVariant plain = object->property("plain"); - QCOMPARE(plain.userType(), QMetaType::QUrl); - QCOMPARE(qvariant_cast<QUrl>(plain).toString(), u"/not/here.qml"_s); + QVERIFY(other->barzles().isEmpty()); + QVERIFY(other->cousins().isEmpty()); + + Barzle f1; + Barzle f2; + const QList<Barzle *> barzles { &f1, &f2 }; + const QList<Person *> cousins { self, other }; + + other->setBarzles(barzles); + QCOMPARE(self->barzles(), barzles); + QCOMPARE(self->property("l").toInt(), 2); + + other->setCousins(cousins); + QCOMPARE(self->cousins(), cousins); + QCOMPARE(self->property("m").toInt(), 2); + + QQmlListProperty<Person> others + = self->property("others").value<QQmlListProperty<Person>>(); + QCOMPARE(others.count(&others), 2); + QCOMPARE(others.at(&others, 0), cousins[0]); + QCOMPARE(others.at(&others, 1), cousins[1]); + + QQmlListProperty<Person> momsCousins + = self->property("momsCousins").value<QQmlListProperty<Person>>(); + QCOMPARE(momsCousins.count(&momsCousins), 2); + QCOMPARE(momsCousins.at(&momsCousins, 0), cousins[0]); + QCOMPARE(momsCousins.at(&momsCousins, 1), cousins[1]); + + QQmlListProperty<Person> dadsCousins + = self->property("dadsCousins").value<QQmlListProperty<Person>>(); + QCOMPARE(dadsCousins.count(&dadsCousins), 1); + QCOMPARE(dadsCousins.at(&dadsCousins, 0), other); } -void tst_QmlCppCodegen::multiLookup() +static bool expectingMessage = false; +static void handler(QtMsgType type, const QMessageLogContext &, const QString &message) +{ + QVERIFY(expectingMessage); + QCOMPARE(type, QtDebugMsg); + QCOMPARE(message, u"false"); + expectingMessage = false; +} + +void tst_QmlCppCodegen::basicBlocksWithBackJump() { - // Multiple lookups of singletons (Qt in this case) don't clash with one another. QQmlEngine engine; - QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/immediateQuit.qml"_s)); - QVERIFY2(!component.isError(), component.errorString().toUtf8()); + QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/basicBlocksWithBackJump.qml"_s)); + QVERIFY2(component.isReady(), qPrintable(component.errorString())); + QScopedPointer<QObject> o(component.create()); + QVERIFY(!o.isNull()); - const QByteArray message = QByteArray("End: ") + arg1(); - QTest::ignoreMessage(QtDebugMsg, message.constData()); + const auto oldHandler = qInstallMessageHandler(&handler); + const auto guard = qScopeGuard([oldHandler]() { qInstallMessageHandler(oldHandler); }); - QSignalSpy quitSpy(&engine, &QQmlEngine::quit); - QScopedPointer<QObject> object(component.create()); - QVERIFY(!object.isNull()); - QCOMPARE(quitSpy.size(), 1); + // t1 does not log anything + QMetaObject::invokeMethod(o.data(), "t1"); + + // t2 logs "false" exactly once + expectingMessage = true; + QMetaObject::invokeMethod(o.data(), "t2"); + QVERIFY(!expectingMessage); + + // t3 logs "false" exactly once + expectingMessage = true; + QMetaObject::invokeMethod(o.data(), "t3"); + QVERIFY(!expectingMessage); } -void tst_QmlCppCodegen::enums() +void tst_QmlCppCodegen::basicBlocksWithBackJump_infinite() { QQmlEngine engine; - { - QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/Enums.qml"_s)); - QVERIFY2(!component.isError(), component.errorString().toUtf8()); + QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/basicBlocksWithBackJump_infinite.qml"_s)); + QVERIFY2(component.isReady(), qPrintable(component.errorString())); + QScopedPointer<QObject> o(component.create()); + QVERIFY(!o.isNull()); +} - QTest::ignoreMessage(QtWarningMsg, "qrc:/qt/qml/TestTypes/Enums.qml:4:1: " - "QML Enums: Layout must be attached to Item elements"); - QScopedPointer<QObject> object(component.create()); +void tst_QmlCppCodegen::basicDTZ() +{ + QQmlEngine engine; + QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/basicDTZ.qml"_s)); + QVERIFY2(component.isReady(), qPrintable(component.errorString())); + QScopedPointer<QObject> o(component.create()); + QVERIFY(!o.isNull()); - QVERIFY(!object.isNull()); - bool ok = false; - QCOMPARE(object->property("appState").toInt(&ok), 2); - QVERIFY(ok); - QCOMPARE(object->property("color").toString(), u"blue"_s); + QCOMPARE(o->property("title").toString(), u"none"); - QTRY_COMPARE(object->property("appState").toInt(&ok), 1); - QVERIFY(ok); - QCOMPARE(object->property("color").toString(), u"green"_s); + QMetaObject::invokeMethod(o.data(), "t1"); + QMetaObject::invokeMethod(o.data(), "t2"); + QMetaObject::invokeMethod(o.data(), "t3"); + + QCOMPARE(o->property("title").toString(), u"Baz 41"); +} + +void tst_QmlCppCodegen::bindToValueType() +{ + QQmlEngine engine; + QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/bindToValueType.qml"_s)); + checkColorProperties(&component); +} + +void tst_QmlCppCodegen::bindingExpression() +{ + QQmlEngine engine; + QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/BindingExpression.qml"_s)); + QVERIFY2(component.isReady(), component.errorString().toUtf8()); + QScopedPointer<QObject> object(component.create()); + + QObject *child = qmlContext(object.data())->objectForName(u"child"_s); + + double width = 200; + double y = 10; + for (int i = 0; i < 10; ++i) { + QCOMPARE(object->property("width").toDouble(), width); + QCOMPARE(object->property("height").toDouble(), width); + QCOMPARE(object->property("y").toDouble(), y); - const auto func = qmlAttachedPropertiesFunction( - object.data(), QMetaType::fromName("QQuickLayout*").metaObject()); + const double childY = y + (width - 100) / 2; + QCOMPARE(child->property("y").toDouble(), childY); + QCOMPARE(object->property("mass"), childY > 100 ? u"heavy"_s : u"light"_s); + QCOMPARE(object->property("test_division").toDouble(), width / 1000 + 50); + QCOMPARE(object->property("test_ternary").toDouble(), 2.2); - QTest::ignoreMessage(QtWarningMsg, "qrc:/qt/qml/TestTypes/enumsInOtherObject.qml:4:25: " - "QML Enums: Layout must be attached to Item elements"); - QObject *attached = qmlAttachedPropertiesObject(object.data(), func); + const int test_switch = object->property("test_switch").toInt(); + switch (int(width) % 3) { + case 0: + QCOMPARE(test_switch, 130); + break; + case 1: + QCOMPARE(test_switch, 380); + break; + case 2: + QCOMPARE(test_switch, 630); + break; + } - const QVariant prop = attached->property("alignment"); - QVERIFY(prop.isValid()); - QCOMPARE(qvariant_cast<Qt::Alignment>(prop), Qt::AlignCenter); + width = 200 * i; + y = 10 + i; + object->setProperty("width", width); + object->setProperty("y", y); } - { - QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/enumsInOtherObject.qml"_s)); - QVERIFY2(!component.isError(), component.errorString().toUtf8()); - QScopedPointer<QObject> object(component.create()); - QVERIFY(!object.isNull()); - QCOMPARE(object->property("color").toString(), u"blue"_s); - QTRY_COMPARE(object->property("color").toString(), u"green"_s); +} + +void tst_QmlCppCodegen::blockComments() +{ + QQmlEngine engine; + QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/blockComments.qml"_s)); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> o(c.create()); + QVERIFY(o); + QCOMPARE(o->property("implicitHeight").toDouble(), 8.0); +} + +void tst_QmlCppCodegen::boolCoercions() +{ + QQmlEngine e; + const QUrl url(u"qrc:/qt/qml/TestTypes/boolCoercions.qml"_s); + QQmlComponent c(&e, url); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + + QTest::ignoreMessage( + QtWarningMsg, + qPrintable(url.toString() + ":41:5: Unable to assign [undefined] to bool"_L1)); + QScopedPointer<QObject> o(c.create()); + + for (char p = '1'; p <= '8'; ++p) { + const QVariant t = o->property(qPrintable(QLatin1String("t%1").arg(p))); + QCOMPARE(t.metaType(), QMetaType::fromType<bool>()); + QVERIFY(t.toBool()); + } + + for (char p = '1'; p <= '5'; ++p) { + const QVariant f = o->property(qPrintable(QLatin1String("f%1").arg(p))); + QCOMPARE(f.metaType(), QMetaType::fromType<bool>()); + QVERIFY(!f.toBool()); } } -void tst_QmlCppCodegen::funcWithParams() +void tst_QmlCppCodegen::boolPointerMerge() +{ + QQmlEngine e; + QQmlComponent c(&e, QUrl(u"qrc:/qt/qml/TestTypes/boolPointerMerge.qml"_s)); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> o(c.create()); + QVERIFY(!o.isNull()); + QObject *item = o->property("item").value<QObject *>(); + QVERIFY(item); + QCOMPARE(item->property("ppp").toInt(), -99); +} + +void tst_QmlCppCodegen::boundComponents() { QQmlEngine engine; - QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/funcWithParams.qml"_s)); - QVERIFY2(!component.isError(), component.errorString().toUtf8()); - QScopedPointer<QObject> object(component.create()); - QVERIFY(!object.isNull()); - QCOMPARE(object->property("bar").toInt(), 30); + QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/boundComponents.qml"_s)); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> o(c.create()); + + QObject *c1o = o->property("o").value<QObject *>(); + QVERIFY(c1o != nullptr); + QCOMPARE(c1o->objectName(), u"bar"_s); + + QObject *c2o = c1o->property("o").value<QObject *>(); + QVERIFY(c2o != nullptr); + QCOMPARE(c2o->objectName(), u"bar12"_s); } -void tst_QmlCppCodegen::intOverflow() +void tst_QmlCppCodegen::callContextPropertyLookupResult() { QQmlEngine engine; - QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/intOverflow.qml"_s)); - QVERIFY2(!component.isError(), component.errorString().toUtf8()); - QScopedPointer<QObject> object(component.create()); - QVERIFY(!object.isNull()); - QCOMPARE(object->property("a").toDouble(), 1.09951162778e+12); - QCOMPARE(object->property("b").toInt(), 5); + QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/callContextPropertyLookupResult.qml"_s)); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> o(c.create()); + QVERIFY(o); + + QVERIFY(qvariant_cast<QQmlComponent *>(o->property("c")) != nullptr); } -void tst_QmlCppCodegen::stringLength() +void tst_QmlCppCodegen::callWithSpread() { QQmlEngine engine; - QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/stringLength.qml"_s)); - QVERIFY2(!component.isError(), component.errorString().toUtf8()); - QScopedPointer<QObject> object(component.create()); - QVERIFY(!object.isNull()); - QCOMPARE(object->property("stringLength").toInt(), 8); + QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/callWithSpread.qml"_s)); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QTest::ignoreMessage(QtCriticalMsg, "That is great!"); + QScopedPointer<QObject> o(c.create()); + QVERIFY(!o.isNull()); } -void tst_QmlCppCodegen::scopeVsObject() +void tst_QmlCppCodegen::colorAsVariant() { QQmlEngine engine; - QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/scopeVsObject.qml"_s)); + QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/colorAsVariant.qml"_s)); + checkColorProperties(&component); +} + +void tst_QmlCppCodegen::colorString() +{ + QQmlEngine engine; + QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/colorString.qml"_s)); + + QVERIFY2(component.isReady(), qPrintable(component.errorString())); + QScopedPointer<QObject> rootObject(component.create()); + QVERIFY(rootObject); + + QCOMPARE(qvariant_cast<QColor>(rootObject->property("c")), QColor::fromRgb(0xdd, 0xdd, 0xdd)); + QCOMPARE(qvariant_cast<QColor>(rootObject->property("d")), QColor::fromRgb(0xaa, 0xaa, 0xaa)); + QCOMPARE(qvariant_cast<QColor>(rootObject->property("e")), QColor::fromRgb(0x11, 0x22, 0x33)); +} + +void tst_QmlCppCodegen::compareOriginals() +{ + QQmlEngine engine; + QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/compareOriginals.qml"_s)); QVERIFY2(!component.isError(), component.errorString().toUtf8()); QScopedPointer<QObject> object(component.create()); QVERIFY(!object.isNull()); - QCOMPARE(object->property("objectName").toString(), u"foobar"_s); + + QCOMPARE(object->property("compareOriginals").toInt(), 5); + QVERIFY(object->property("optionalThis").toBool()); } -void tst_QmlCppCodegen::compositeTypeMethod() +void tst_QmlCppCodegen::comparisonTypes() { QQmlEngine engine; - QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/compositeTypeMethod.qml"_s)); + QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/comparisonTypes.qml"_s)); QVERIFY2(!component.isError(), component.errorString().toUtf8()); QScopedPointer<QObject> object(component.create()); QVERIFY(!object.isNull()); - QSignalSpy spy(object.data(), SIGNAL(foo())); - QTRY_VERIFY(spy.size() > 0); + + QCOMPARE(object->property("found").toInt(), 1); + QCOMPARE(object->property("foundStrict").toInt(), 0); + + QCOMPARE(object->property("foundNot").toInt(), 2); + QCOMPARE(object->property("foundNotStrict").toInt(), 10); } -void tst_QmlCppCodegen::excessiveParameters() +void tst_QmlCppCodegen::componentReturnType() { QQmlEngine engine; - QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/excessiveParameters.qml"_s)); + QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/componentReturnType.qml"_s)); QVERIFY2(!component.isError(), component.errorString().toUtf8()); QScopedPointer<QObject> object(component.create()); - QVERIFY(!object.isNull()); - QSignalSpy spy(object.data(), SIGNAL(foo())); - QTRY_VERIFY(spy.size() > 0); + + QCOMPARE(object->property("count").toInt(), 10); + QCOMPARE(QQmlListReference(object.data(), "children").count(), 11); } -void tst_QmlCppCodegen::jsImport() +void tst_QmlCppCodegen::compositeSingleton() { QQmlEngine engine; - QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/jsimport.qml"_s)); + engine.addImportPath(u":/qt/qml/TestTypes/imports/"_s); + QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/compositesingleton.qml"_s)); QVERIFY2(!component.isError(), component.errorString().toUtf8()); - QScopedPointer<QObject> object(component.create()); - QVERIFY(!object.isNull()); - QCOMPARE(object->property("value").toInt(), 42); + QScopedPointer<QObject> o(component.create()); + QCOMPARE(o->property("x").toDouble(), 4.5); + QCOMPARE(o->property("y").toDouble(), 10.0); + QCOMPARE(o->property("smooth").toBool(), true); } -void tst_QmlCppCodegen::jsmoduleImport() +void tst_QmlCppCodegen::compositeTypeMethod() { QQmlEngine engine; - QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/jsmoduleimport.qml"_s)); + QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/compositeTypeMethod.qml"_s)); QVERIFY2(!component.isError(), component.errorString().toUtf8()); QScopedPointer<QObject> object(component.create()); QVERIFY(!object.isNull()); - QCOMPARE(object->property("ok").toBool(), true); - QVariant okFunc = object->property("okFunc"); - QCOMPARE(okFunc.metaType(), QMetaType::fromType<QJSValue>()); - QJSValue val = engine.toScriptValue(okFunc); - QJSValue result = val.call(); - QVERIFY(result.isBool()); - QVERIFY(result.toBool()); + QSignalSpy spy(object.data(), SIGNAL(foo())); + QTRY_VERIFY(spy.size() > 0); } -void tst_QmlCppCodegen::methods() +void tst_QmlCppCodegen::consoleObject() { QQmlEngine engine; - QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/methods.qml"_s)); - QVERIFY(component.isReady()); - - QTest::ignoreMessage(QtDebugMsg, "The Bar"); - QTest::ignoreMessage(QtWarningMsg, QRegularExpression(u"TypeError: .* is not a function"_s)); - QScopedPointer<QObject> obj(component.create()); - QVERIFY(obj); - BirthdayParty *party(qobject_cast<BirthdayParty *>(obj.data())); - - QVERIFY(party && party->host()); - QCOMPARE(party->guestCount(), 5); + static const QString urlString = u"qrc:/qt/qml/TestTypes/consoleObject.qml"_s; + QQmlComponent c(&engine, QUrl(urlString)); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); - bool foundGreen = false; - bool foundFoo = false; - for (int ii = 0; ii < party->guestCount(); ++ii) { - if (party->guest(ii)->name() == u"William Green"_s) - foundGreen = true; - if (party->guest(ii)->name() == u"The Foo"_s) - foundFoo = true; - } + QTest::ignoreMessage(QtDebugMsg, "b 4.55"); + QTest::ignoreMessage(QtDebugMsg, "b 4.55"); + QTest::ignoreMessage(QtInfoMsg, "b 4.55"); + QTest::ignoreMessage(QtWarningMsg, "b 4.55"); + QTest::ignoreMessage(QtCriticalMsg, "b 4.55"); - QVERIFY(foundGreen); - QVERIFY(foundFoo); + // Unfortunately we cannot check the logging category with QTest::ignoreMessage + QTest::ignoreMessage(QtDebugMsg, "b 4.55"); + QTest::ignoreMessage(QtDebugMsg, "b 4.55"); + QTest::ignoreMessage(QtInfoMsg, "b 4.55"); + QTest::ignoreMessage(QtWarningMsg, "b 4.55"); + QTest::ignoreMessage(QtCriticalMsg, "b 4.55"); - QCOMPARE(obj->property("n1").toString(), u"onGurk"_s); - QCOMPARE(obj->property("n2").toString(), u"onSemmeln"_s); - QCOMPARE(obj->property("n3"), QVariant()); + const QRegularExpression re(u"QQmlComponentAttached\\(0x[0-9a-f]+\\) b 4\\.55"_s); + QTest::ignoreMessage(QtDebugMsg, re); + QTest::ignoreMessage(QtDebugMsg, re); + QTest::ignoreMessage(QtInfoMsg, re); + QTest::ignoreMessage(QtWarningMsg, re); + QTest::ignoreMessage(QtCriticalMsg, re); - { - QVariant ret; - obj->metaObject()->invokeMethod(obj.data(), "retrieveVar", Q_RETURN_ARG(QVariant, ret)); - QCOMPARE(ret.typeId(), QMetaType::QString); - QCOMPARE(ret.toString(), u"Jack Smith"_s); - } + QTest::ignoreMessage(QtDebugMsg, "a undefined b false null 7"); + QTest::ignoreMessage(QtDebugMsg, ""); + QTest::ignoreMessage(QtDebugMsg, "4"); + QTest::ignoreMessage(QtDebugMsg, ""); - { - QString ret; - obj->metaObject()->invokeMethod(obj.data(), "retrieveString", Q_RETURN_ARG(QString, ret)); - QCOMPARE(ret, u"Jack Smith"_s); - } + const QRegularExpression re2(u"QQmlComponentAttached\\(0x[0-9a-f]+\\)"_s); + QTest::ignoreMessage(QtDebugMsg, re2); - QCOMPARE(party->host()->shoeSize(), 12); - obj->metaObject()->invokeMethod(obj.data(), "storeElement"); - QCOMPARE(party->host()->shoeSize(), 13); - QJSManagedValue v = engine.toManagedValue(obj->property("dresses")); - QVERIFY(v.isArray()); + QScopedPointer<QObject> o(c.create()); + QVERIFY(!o.isNull()); - QJSManagedValue inner(v.property(2), &engine); - QVERIFY(inner.isArray()); - QCOMPARE(inner.property(0).toInt(), 1); - QCOMPARE(inner.property(1).toInt(), 2); - QCOMPARE(inner.property(2).toInt(), 3); + auto oldHandler = qInstallMessageHandler( + [](QtMsgType, const QMessageLogContext &ctxt, const QString &) { + QCOMPARE(ctxt.file, urlString.toUtf8()); + QCOMPARE(ctxt.function, QByteArray("expression for onCompleted")); + QVERIFY(ctxt.line > 0); + }); + const auto guard = qScopeGuard([oldHandler]() { qInstallMessageHandler(oldHandler); }); - QCOMPARE(obj->property("enumValue").toInt(), 2); + QScopedPointer<QObject> p(c.create()); + QVERIFY(!p.isNull()); } -void tst_QmlCppCodegen::math() +void tst_QmlCppCodegen::consoleTrace() { QQmlEngine engine; - QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/math.qml"_s)); + QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/consoleTrace.qml"_s)); QVERIFY2(!component.isError(), component.errorString().toUtf8()); + +#if !defined(QT_NO_DEBUG) || defined(QT_TEST_FORCE_INTERPRETER) + // All line numbers in debug mode or when interpreting + + QTest::ignoreMessage(QtDebugMsg, R"(c (qrc:/qt/qml/TestTypes/consoleTrace.qml:6) +b (qrc:/qt/qml/TestTypes/consoleTrace.qml:5) +a (qrc:/qt/qml/TestTypes/consoleTrace.qml:4) +expression for onCompleted (qrc:/qt/qml/TestTypes/consoleTrace.qml:7))"); +#else + // Only top-most line number otherwise + + QTest::ignoreMessage(QtDebugMsg, R"(c (qrc:/qt/qml/TestTypes/consoleTrace.qml:6) +b (qrc:/qt/qml/TestTypes/consoleTrace.qml) +a (qrc:/qt/qml/TestTypes/consoleTrace.qml) +expression for onCompleted (qrc:/qt/qml/TestTypes/consoleTrace.qml))"); +#endif + QScopedPointer<QObject> object(component.create()); QVERIFY(!object.isNull()); - QCOMPARE(object->property("a").toInt(), 9); - QCOMPARE(object->property("b").toDouble(), 50.0 / 22.0); } -void tst_QmlCppCodegen::unknownParameter() +void tst_QmlCppCodegen::construct() { QQmlEngine engine; - QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/unknownParameter.qml"_s)); + QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/construct.qml"_s)); QVERIFY2(!component.isError(), component.errorString().toUtf8()); QScopedPointer<QObject> object(component.create()); QVERIFY(!object.isNull()); - QCOMPARE(object->property("cppProp").toInt(), 18); + + const QJSManagedValue v = engine.toManagedValue(object->property("foo")); + QVERIFY(v.isError()); + QCOMPARE(v.toString(), u"Error: bar"_s); + + QCOMPARE(object->property("aaa").toInt(), 12); + QTest::ignoreMessage(QtWarningMsg, "qrc:/qt/qml/TestTypes/construct.qml:9: Error: ouch"); + object->metaObject()->invokeMethod(object.data(), "ouch"); + QCOMPARE(object->property("aaa").toInt(), 13); } -void tst_QmlCppCodegen::array() +void tst_QmlCppCodegen::contextParam() { + // The compiler cannot resolve context parameters. + // Make sure the binding is interpreted. + QQmlEngine engine; - QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/array.qml"_s)); + + QVariantMap m; + m.insert(u"foo"_s, 10); + engine.rootContext()->setContextProperty(u"contextParam"_s, m); + + QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/contextParam.qml"_s)); QVERIFY2(!component.isError(), component.errorString().toUtf8()); QScopedPointer<QObject> object(component.create()); QVERIFY(!object.isNull()); - const QJSValue value1 = object->property("values1").value<QJSValue>(); - QVERIFY(value1.isArray()); - QCOMPARE(value1.property(u"length"_s).toInt(), 3); - QCOMPARE(value1.property(0).toInt(), 1); - QCOMPARE(value1.property(1).toInt(), 2); - QCOMPARE(value1.property(2).toInt(), 3); - const QJSValue value2 = object->property("values2").value<QJSValue>(); - QVERIFY(value2.isArray()); - QCOMPARE(value2.property(u"length"_s).toInt(), 0); + QCOMPARE(object->property("foo").toInt(), 10); } -void tst_QmlCppCodegen::equalsUndefined() +void tst_QmlCppCodegen::conversionDecrement() { QQmlEngine engine; - QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/equalsUndefined.qml"_s)); - QVERIFY2(!component.isError(), component.errorString().toUtf8()); - QScopedPointer<QObject> object(component.create()); - QVERIFY(!object.isNull()); + QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/conversionDecrement.qml"_s)); - QCOMPARE(object->property("a").toInt(), 50); - QCOMPARE(object->property("b").toInt(), 5000); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + + QScopedPointer<QObject> o(c.create()); + QVERIFY(!o.isNull()); + + QCOMPARE(o->property("currentPageIndex").toInt(), 0); + o->setProperty("pages", 5); + QCOMPARE(o->property("currentPageIndex").toInt(), 3); + o->setProperty("pages", 4); + QCOMPARE(o->property("currentPageIndex").toInt(), 0); + o->setProperty("pages", 6); + QCOMPARE(o->property("currentPageIndex").toInt(), 4); + o->setProperty("pages", 60); + QCOMPARE(o->property("currentPageIndex").toInt(), 3); +} + +void tst_QmlCppCodegen::conversionInDeadCode() +{ + QQmlEngine engine; + QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/conversionInDeadCode.qml"_s)); + + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + + QScopedPointer<QObject> o(c.create()); + QVERIFY(!o.isNull()); + + QCOMPARE(o->property("a").toInt(), -4); + QCOMPARE(o->property("b").toInt(), 12); + QCOMPARE(o->property("c").toInt(), 10); + QCOMPARE(o->property("d").toInt(), 10); + QCOMPARE(o->property("e").toInt(), 10); + QCOMPARE(o->property("f").toInt(), 20); + QCOMPARE(o->property("g").toInt(), 33); } void tst_QmlCppCodegen::conversions() @@ -699,157 +1358,260 @@ void tst_QmlCppCodegen::conversions() QVERIFY(!undef.isValid()); } -void tst_QmlCppCodegen::interestingFiles_data() +void tst_QmlCppCodegen::convertPrimitiveToVar() { - QTest::addColumn<QString>("file"); - QTest::addColumn<bool>("isValid"); + QQmlEngine engine; + QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/convertPrimitiveToVar.qml"_s)); - QTest::addRow("conversions2") << u"conversions2.qml"_s << true; - QTest::addRow("TestCase") << u"TestCase.qml"_s << true; - QTest::addRow("layouts") << u"layouts.qml"_s << true; - QTest::addRow("interactive") << u"interactive.qml"_s << true; - QTest::addRow("Panel") << u"Panel.qml"_s << true; - QTest::addRow("ProgressBar") << u"ProgressBar/ProgressBar.ui.qml"_s << true; - QTest::addRow("Root") << u"ProgressBar/Root.qml"_s << true; - QTest::addRow("noscope") << u"noscope.qml"_s << true; - QTest::addRow("dynamicscene") << u"dynamicscene.qml"_s << true; - QTest::addRow("curlygrouped") << u"curlygrouped.qml"_s << true; - QTest::addRow("cycleHead") << u"cycleHead.qml"_s << false; - QTest::addRow("deadStoreLoop") << u"deadStoreLoop.qml"_s << true; - QTest::addRow("moveRegVoid") << u"moveRegVoid.qml"_s << true; + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + + QScopedPointer<QObject> o(c.create()); + QVERIFY(!o.isNull()); + + QCOMPARE(o->property("offsetValue").toInt(), 41); } -void tst_QmlCppCodegen::interestingFiles() +void tst_QmlCppCodegen::convertQJSPrimitiveValueToIntegral() { - QFETCH(QString, file); - QFETCH(bool, isValid); - QQmlEngine engine; - QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/%1"_s.arg(file))); - if (isValid) { - QVERIFY2(component.isReady(), qPrintable(component.errorString())); - QScopedPointer<QObject> object(component.create()); - QVERIFY(!object.isNull()); - } else { - QVERIFY(component.isError()); - } + QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/convertQJSPrimitiveValueToIntegral.qml"_s)); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> o(c.create()); + QVERIFY(!o.isNull()); } -void tst_QmlCppCodegen::extendedTypes() +void tst_QmlCppCodegen::convertToOriginalReadAcumulatorForUnaryOperators() { QQmlEngine engine; - QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/extendedTypes.qml"_s)); - QVERIFY2(!component.isError(), component.errorString().toUtf8()); - - QTest::ignoreMessage(QtDebugMsg, "6 QSizeF(10, 20) 30"); - QScopedPointer<QObject> object(component.create()); - QVERIFY(!object.isNull()); + QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/convertToOriginalReadAcumulatorForUnaryOperators.qml"_s)); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> o(c.create()); + QVERIFY(!o.isNull()); +} - QCOMPARE(object->property("a").toInt(), 6); - QCOMPARE(qvariant_cast<QSizeF>(object->property("b")), QSizeF(10, 20)); - QCOMPARE(object->property("c").toInt(), 30); - QCOMPARE(object->property("d").toString(), u"QSizeF(10, 20)"_s); +void tst_QmlCppCodegen::cppMethodListReturnType() +{ + QQmlEngine engine; + QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/CppMethodListReturnType.qml"_s)); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> o(c.create()); + QVERIFY(!o.isNull()); - QCOMPARE(object->property("e").toInt(), 2); + QCOMPARE(o->property("list").toList()[2].toInt(), 2); } -void tst_QmlCppCodegen::construct() +void tst_QmlCppCodegen::cppValueTypeList() { QQmlEngine engine; - QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/construct.qml"_s)); - QVERIFY2(!component.isError(), component.errorString().toUtf8()); + QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/Test.qml"_s)); QScopedPointer<QObject> object(component.create()); - QVERIFY(!object.isNull()); - - const QJSManagedValue v = engine.toManagedValue(object->property("foo")); - QVERIFY(v.isError()); - QCOMPARE(v.toString(), u"Error: bar"_s); + QVERIFY2(!object.isNull(), component.errorString().toUtf8().constData()); + QCOMPARE(object->property("a").toInt(), 16); + QMetaObject::invokeMethod(object.data(), "incA"); + QCOMPARE(object->property("a").toInt(), 17); - QCOMPARE(object->property("aaa").toInt(), 12); - QTest::ignoreMessage(QtWarningMsg, "qrc:/qt/qml/TestTypes/construct.qml:9: Error: ouch"); - object->metaObject()->invokeMethod(object.data(), "ouch"); - QCOMPARE(object->property("aaa").toInt(), 13); + QCOMPARE(object->property("b").toDouble(), 0.25); + QMetaObject::invokeMethod(object.data(), "incB"); + QCOMPARE(object->property("b").toDouble(), 13.5); } -void tst_QmlCppCodegen::contextParam() +void tst_QmlCppCodegen::dateConstruction() { - // The compiler cannot resolve context parameters. - // Make sure the binding is interpreted. + QQmlEngine engine; + QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/dateConstruction.qml"_s)); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QDateTime now = QDateTime::currentDateTime(); + QScopedPointer<QObject> o(c.create()); + QVERIFY(!o.isNull()); + QVERIFY(o->property("now").value<QDateTime>().toMSecsSinceEpoch() >= now.toMSecsSinceEpoch()); + QCOMPARE(o->property("now2"), o->property("now")); + QCOMPARE(o->property("fromString").value<QDateTime>(), + QDateTime(QDate(1995, 12, 17), QTime(3, 24), QTimeZone::LocalTime)); + QCOMPARE(o->property("fromNumber").value<QDateTime>().toMSecsSinceEpoch(), 777); + QCOMPARE(o->property("fromPrimitive").value<QDateTime>().toMSecsSinceEpoch(), 57); + o->setObjectName("foo"_L1); + QCOMPARE(o->property("fromPrimitive").value<QDateTime>(), + QDateTime(QDate(1997, 2, 13), QTime(13, 4, 12), QTimeZone::LocalTime)); + + QCOMPARE(o->property("from2").value<QDateTime>(), + QDateTime(QDate(1996, 2, 1), QTime(), QTimeZone::LocalTime)); + QCOMPARE(o->property("from3").value<QDateTime>(), + QDateTime(QDate(1996, 3, 3), QTime(), QTimeZone::LocalTime)); + QCOMPARE(o->property("from4").value<QDateTime>(), + QDateTime(QDate(1996, 4, 4), QTime(5, 0), QTimeZone::LocalTime)); + QCOMPARE(o->property("from5").value<QDateTime>(), + QDateTime(QDate(1996, 5, 5), QTime(6, 7), QTimeZone::LocalTime)); + QCOMPARE(o->property("from6").value<QDateTime>(), + QDateTime(QDate(1996, 6, 6), QTime(7, 8, 9), QTimeZone::LocalTime)); + QCOMPARE(o->property("from7").value<QDateTime>(), + QDateTime(QDate(1996, 7, 7), QTime(8, 9, 10, 11), QTimeZone::LocalTime)); + QCOMPARE(o->property("from8").value<QDateTime>(), + QDateTime(QDate(1996, 8, 8), QTime(9, 10, 11, 12), QTimeZone::LocalTime)); + + QCOMPARE(o->property("withUnderflow").value<QDateTime>(), + QDateTime(QDate(-6, 7, 24), QTime(16, 51, 50, 990), QTimeZone::LocalTime)); + QCOMPARE(o->property("invalid").value<QDateTime>(), QDateTime()); +} +void tst_QmlCppCodegen::dateConversions() +{ QQmlEngine engine; + QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/dateConversions.qml"_s)); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> o(c.create()); - QVariantMap m; - m.insert(u"foo"_s, 10); - engine.rootContext()->setContextProperty(u"contextParam"_s, m); + Druggeljug *ref = engine.singletonInstance<Druggeljug *>("TestTypes", "Druggeljug"); - QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/contextParam.qml"_s)); - QVERIFY2(!component.isError(), component.errorString().toUtf8()); - QScopedPointer<QObject> object(component.create()); - QVERIFY(!object.isNull()); + const QDateTime refDate = engine.coerceValue<QDate, QDateTime>(ref->myDate()); + const QDateTime refTime = engine.coerceValue<QTime, QDateTime>(ref->myTime()); + + QCOMPARE(o->property("date").value<QDateTime>(), refDate); + QCOMPARE(o->property("time").value<QDateTime>(), refTime); + + QCOMPARE(o->property("dateString").toString(), + (engine.coerceValue<QDateTime, QString>(refDate))); + QCOMPARE(o->property("dateNumber").toDouble(), + (engine.coerceValue<QDateTime, double>(refDate))); + QCOMPARE(o->property("timeString").toString(), + (engine.coerceValue<QDateTime, QString>(refTime))); + QCOMPARE(o->property("timeNumber").toDouble(), + (engine.coerceValue<QDateTime, double>(refTime))); + + QMetaObject::invokeMethod(o.data(), "shuffle"); + + QCOMPARE(ref->myDate(), (engine.coerceValue<QDateTime, QDate>(refDate))); + QCOMPARE(ref->myTime(), (engine.coerceValue<QDateTime, QTime>(refTime))); + + const QDate date = ref->myDate(); + const QTime time = ref->myTime(); + + QCOMPARE(o->property("dateString").toString(), (engine.coerceValue<QDate, QString>(date))); + QCOMPARE(o->property("dateNumber").toDouble(), (engine.coerceValue<QDate, double>(date))); + QCOMPARE(o->property("timeString").toString(), (engine.coerceValue<QTime, QString>(time))); + QCOMPARE(o->property("timeNumber").toDouble(), (engine.coerceValue<QTime, double>(time))); + + QMetaObject::invokeMethod(o.data(), "fool"); + + QCOMPARE(ref->myDate(), (engine.coerceValue<QTime, QDate>(time))); + QCOMPARE(ref->myTime(), (engine.coerceValue<QDate, QTime>(date))); + + QMetaObject::invokeMethod(o.data(), "invalidate"); + QMetaObject::invokeMethod(o.data(), "shuffle"); + + QCOMPARE(o->property("dateString").toString(), "Invalid Date"_L1); + QVERIFY(qIsNaN(o->property("dateNumber").toDouble())); + QCOMPARE(o->property("timeString").toString(), "Invalid Date"_L1); + QVERIFY(qIsNaN(o->property("timeNumber").toDouble())); - QCOMPARE(object->property("foo").toInt(), 10); } -void tst_QmlCppCodegen::attachedType() +void tst_QmlCppCodegen::deadShoeSize() { QQmlEngine engine; - QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/text.qml"_s)); - QVERIFY2(!component.isError(), component.errorString().toUtf8()); - QScopedPointer<QObject> object(component.create()); - QVERIFY(!object.isNull()); - QCOMPARE(object->property("dayz").toDateTime(), QDateTime(QDate(1911, 3, 4), QTime())); - QCOMPARE(object->property("oParty").toDateTime(), QDateTime(QDate(1911, 3, 4), QTime())); - - QObject *party = qvariant_cast<QObject *>(object->property("party")); - QVERIFY(party); - QCOMPARE(party->property("eee").toInt(), 21); - QCOMPARE(party->property("fff").toInt(), 33); - QCOMPARE(object->property("ggg").toInt(), 37); + QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/deadShoeSize.qml"_s)); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QTest::ignoreMessage(QtWarningMsg, "qrc:/qt/qml/TestTypes/deadShoeSize.qml:5: Error: ouch"); + QScopedPointer<QObject> o(c.create()); + QVERIFY(o); + QCOMPARE(o->property("shoeSize").toInt(), 0); } -void tst_QmlCppCodegen::componentReturnType() +void tst_QmlCppCodegen::dialogButtonBox() { QQmlEngine engine; - QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/componentReturnType.qml"_s)); - QVERIFY2(!component.isError(), component.errorString().toUtf8()); - QScopedPointer<QObject> object(component.create()); + const QUrl copy(u"qrc:/qt/qml/TestTypes/dialogButtonBox.qml"_s); + QQmlComponent c(&engine, copy); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> o(c.create()); + QVERIFY(!o.isNull()); + QObject *footer = o->property("footer").value<QObject *>(); + QVERIFY(footer); - QCOMPARE(object->property("count").toInt(), 10); - QCOMPARE(QQmlListReference(object.data(), "children").count(), 11); + QCOMPARE(footer->property("standardButtons").value<QPlatformDialogHelper::StandardButton>(), + QPlatformDialogHelper::Ok | QPlatformDialogHelper::Cancel); } -void tst_QmlCppCodegen::onAssignment() +void tst_QmlCppCodegen::enumConversion() { QQmlEngine engine; - QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/pressAndHoldButton.qml"_s)); - QVERIFY2(!component.isError(), component.errorString().toUtf8()); - QScopedPointer<QObject> object(component.create()); - QCOMPARE(object->property("pressed").toBool(), false); - QCOMPARE(object->property("scale").toDouble(), 1.0); + QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/enumConversion.qml"_s)); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); - object->metaObject()->invokeMethod(object.data(), "press"); - QTRY_COMPARE(object->property("pressed").toBool(), true); - QCOMPARE(object->property("scale").toDouble(), 0.9); + QScopedPointer<QObject> o(c.create()); + QVERIFY(o); + QCOMPARE(o->property("test").toInt(), 0x04); + QCOMPARE(o->property("test_1").toBool(), true); + QCOMPARE(o->objectName(), u"0m"_s); +} - object->metaObject()->invokeMethod(object.data(), "release"); - QCOMPARE(object->property("pressed").toBool(), false); - QCOMPARE(object->property("scale").toDouble(), 1.0); +void tst_QmlCppCodegen::enumFromBadSingleton() +{ + QQmlEngine e; + const QUrl url(u"qrc:/qt/qml/TestTypes/enumFromBadSingleton.qml"_s); + QQmlComponent c(&e, url); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + +#if QT_DEPRECATED_SINCE(6,4) + QTest::ignoreMessage( + QtWarningMsg, qPrintable( + url.toString() + + u":5:5: TypeError: Cannot read property 'TestA' of undefined"_s)); +#else + QTest::ignoreMessage( + QtWarningMsg, qPrintable( + url.toString() + + u":5:5: ReferenceError: DummyObjekt is not defined"_s)); +#endif + + QScopedPointer<QObject> o(c.create()); + QVERIFY(o); + QVERIFY(o->objectName().isEmpty()); } -namespace QmlCacheGeneratedCode { -namespace _qt_qml_TestTypes_failures_qml { -extern const QQmlPrivate::TypedFunction aotBuiltFunctions[]; +void tst_QmlCppCodegen::enumLookup() +{ + QQmlEngine engine; + QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/enumLookup.qml"_s)); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> o(c.create()); + + QCOMPARE(o->property("ready").toBool(), true); } + +void tst_QmlCppCodegen::enumMarkedAsFlag() +{ + QQmlEngine engine; + + QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/enumMarkedAsFlag.qml"_s)); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> o(c.create()); + + QCOMPARE(o->property("flagValue").toInt(), 3); } -void tst_QmlCppCodegen::failures() +void tst_QmlCppCodegen::enumProblems() { - const auto &aotFailure - = QmlCacheGeneratedCode::_qt_qml_TestTypes_failures_qml::aotBuiltFunctions[0]; - QVERIFY(aotFailure.argumentTypes.isEmpty()); - QVERIFY(!aotFailure.functionPtr); - QCOMPARE(aotFailure.extraData, 0); + QQmlEngine engine; + QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/enumProblems.qml"_s)); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> outer(c.create()); + QVERIFY(!outer.isNull()); + QObject *inner = outer->property("o").value<QObject *>(); + QVERIFY(inner); + + Foo *bar = inner->property("bar").value<Foo *>(); + QVERIFY(bar); + QCOMPARE(bar->type(), Foo::Component); + + Foo *fighter = inner->property("fighter").value<Foo *>(); + QVERIFY(fighter); + QCOMPARE(fighter->type(), Foo::Fighter); + + QCOMPARE(outer->property("a").toInt(), FooFactory::B); + QCOMPARE(outer->property("b").toInt(), FooFactory::C); + QCOMPARE(outer->property("c").toInt(), FooFactory::D); + QCOMPARE(outer->property("d").toInt(), FooFactory::E); } void tst_QmlCppCodegen::enumScope() @@ -861,283 +1623,370 @@ void tst_QmlCppCodegen::enumScope() QCOMPARE(object->property("flow").toInt(), 1); } -void tst_QmlCppCodegen::unusedAttached() +void tst_QmlCppCodegen::enums() { QQmlEngine engine; - QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/unusedAttached.qml"_s)); + QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/Enums.qml"_s)); QVERIFY2(!component.isError(), component.errorString().toUtf8()); + + QTest::ignoreMessage(QtWarningMsg, "qrc:/qt/qml/TestTypes/Enums.qml:4:1: " + "QML Enums: Layout must be attached to Item elements"); QScopedPointer<QObject> object(component.create()); + QVERIFY(!object.isNull()); + bool ok = false; + QCOMPARE(object->property("appState").toInt(&ok), 2); + QVERIFY(ok); + QCOMPARE(object->property("color").toString(), u"blue"_s); + + QTRY_COMPARE(object->property("appState").toInt(&ok), 1); + QVERIFY(ok); + QCOMPARE(object->property("color").toString(), u"green"_s); + const auto func = qmlAttachedPropertiesFunction( - object.data(), QMetaType::fromName("QQuickKeyNavigationAttached*").metaObject()); + object.data(), QMetaType::fromName("QQuickLayout*").metaObject()); + QObject *attached = qmlAttachedPropertiesObject(object.data(), func); - const QVariant prop = attached->property("priority"); + + const QVariant prop = attached->property("alignment"); QVERIFY(prop.isValid()); - QCOMPARE(QByteArray(prop.metaType().name()), "QQuickKeyNavigationAttached::Priority"); - bool ok = false; - QCOMPARE(prop.toInt(&ok), 0); - QVERIFY(ok); + QCOMPARE(qvariant_cast<Qt::Alignment>(prop), Qt::AlignCenter); + } -void tst_QmlCppCodegen::attachedBaseEnum() +void tst_QmlCppCodegen::enforceSignature() { QQmlEngine engine; - QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/attachedBaseEnum.qml"_s)); + QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/enforceSignature.qml"_s)); QVERIFY2(!component.isError(), component.errorString().toUtf8()); QScopedPointer<QObject> object(component.create()); + QVERIFY(!object.isNull()); - QObject *drag = qvariant_cast<QObject *>(object->property("drag")); - QVERIFY(drag); + const QVariant a = object->property("a"); + QCOMPARE(a.metaType(), QMetaType::fromType<QObject *>()); + QCOMPARE(a.value<QObject *>(), nullptr); - // Drag.YAxis is 2, but we cannot #include it here. - bool ok = false; - QCOMPARE(drag->property("axis").toInt(&ok), 2); - QVERIFY(ok); + const QVariant b = object->property("b"); + QCOMPARE(b.metaType(), QMetaType::fromType<QObject *>()); + QCOMPARE(b.value<QObject *>(), nullptr); } -void tst_QmlCppCodegen::nullAccess() +void tst_QmlCppCodegen::enumsInOtherObject() { QQmlEngine engine; - QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/nullAccess.qml"_s)); + QTest::ignoreMessage(QtWarningMsg, "qrc:/qt/qml/TestTypes/enumsInOtherObject.qml:4:25: " + "QML Enums: Layout must be attached to Item elements"); + QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/enumsInOtherObject.qml"_s)); QVERIFY2(!component.isError(), component.errorString().toUtf8()); - - QTest::ignoreMessage(QtWarningMsg, - "qrc:/qt/qml/TestTypes/nullAccess.qml:4:5: TypeError: " - "Cannot read property 'width' of null"); - QTest::ignoreMessage(QtWarningMsg, - "qrc:/qt/qml/TestTypes/nullAccess.qml:5:5: TypeError: " - "Cannot read property 'height' of null"); - QTest::ignoreMessage(QtWarningMsg, - "qrc:/qt/qml/TestTypes/nullAccess.qml:6: TypeError: Value is null and " - "could not be converted to an object"); QScopedPointer<QObject> object(component.create()); + QVERIFY(!object.isNull()); + QCOMPARE(object->property("color").toString(), u"blue"_s); + QTRY_COMPARE(object->property("color").toString(), u"green"_s); +} - QCOMPARE(object->property("width").toDouble(), 0.0); - QCOMPARE(object->property("height").toDouble(), 0.0); +void tst_QmlCppCodegen::equalityQObjects() +{ + QQmlEngine engine; + QQmlComponent c1(&engine, QUrl(u"qrc:/qt/qml/TestTypes/equalityQObjects.qml"_s)); + QVERIFY2(c1.isReady(), qPrintable(c1.errorString())); + QScopedPointer<QObject> object(c1.create()); + QVERIFY(!object.isNull() && !c1.isError()); + + QVERIFY(object->property("derivedIsNotNull").toBool()); + QVERIFY(object->property("nullObjectIsNull").toBool()); + QVERIFY(object->property("nonNullObjectIsNotNull").toBool()); + QVERIFY(object->property("compareSameObjects").toBool()); + QVERIFY(object->property("compareDifferentObjects").toBool()); + QVERIFY(object->property("compareObjectWithNullObject").toBool()); + + QVERIFY(object->property("nonStrict_derivedIsNotNull").toBool()); + QVERIFY(object->property("nonStrict_nullObjectIsNull").toBool()); + QVERIFY(object->property("nonStrict_nonNullObjectIsNotNull").toBool()); + QVERIFY(object->property("nonStrict_compareSameObjects").toBool()); + QVERIFY(object->property("nonStrict_compareDifferentObjects").toBool()); + QVERIFY(object->property("nonStrict_compareObjectWithNullObject").toBool()); } -void tst_QmlCppCodegen::interceptor() +void tst_QmlCppCodegen::equalityQUrl() { QQmlEngine engine; - QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/interceptor.qml"_s)); - QVERIFY2(!component.isError(), component.errorString().toUtf8()); - QScopedPointer<QObject> object(component.create()); - QVERIFY(!object.isNull()); - QCOMPARE(object->property("x").toInt(), 100); - QCOMPARE(object->property("y").toInt(), 100); + QQmlComponent c1(&engine, QUrl(u"qrc:/qt/qml/TestTypes/equalityQUrl.qml"_s)); + QVERIFY2(c1.isReady(), qPrintable(c1.errorString())); - QVERIFY(object->property("width").toInt() != 200); - QVERIFY(object->property("height").toInt() != 200); - QVERIFY(object->property("qProperty1").toInt() != 300); - QVERIFY(object->property("qProperty2").toInt() != 300); - QTRY_COMPARE(object->property("width").toInt(), 200); - QTRY_COMPARE(object->property("height").toInt(), 200); - QTRY_COMPARE(object->property("qProperty1").toInt(), 300); - QTRY_COMPARE(object->property("qProperty2").toInt(), 300); + QScopedPointer<QObject> object(c1.create()); + QVERIFY(!object.isNull() && !c1.isError()); + QVERIFY(object->property("emptyUrlStrict").toBool()); + QVERIFY(object->property("emptyUrlWeak").toBool()); + QVERIFY(object->property("sourceUrlStrict").toBool()); + QVERIFY(object->property("sourceUrlWeak").toBool()); + QVERIFY(object->property("sourceIsNotEmptyStrict").toBool()); + QVERIFY(object->property("sourceIsNotEmptyWeak").toBool()); } -void tst_QmlCppCodegen::nonNotifyable() +void tst_QmlCppCodegen::equalityTestsWithNullOrUndefined() { QQmlEngine engine; - QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/nonNotifyable.qml"_s)); - QVERIFY2(!component.isError(), component.errorString().toUtf8()); - QScopedPointer<QObject> object(component.create()); - QVERIFY(!object.isNull()); + QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/equalityTestsWithNullOrUndefined.qml"_s)); + QVERIFY2(component.isReady(), component.errorString().toUtf8()); + QScopedPointer<QObject> o(component.create()); + QVERIFY(o); +} - QCOMPARE(qvariant_cast<QDateTime>(object->property("dayz")), - QDateTime(QDate(2121, 1, 12), QTime())); - QCOMPARE(qvariant_cast<QDateTime>(object->property("oParty")), - QDateTime(QDate(2111, 12, 11), QTime())); +void tst_QmlCppCodegen::equalityVarAndNonStorable() +{ + QQmlEngine engine; + + QQmlComponent c1(&engine, QUrl(u"qrc:/qt/qml/TestTypes/equalityVarAndNonStorable.qml"_s)); + QVERIFY2(c1.isReady(), qPrintable(c1.errorString())); + + QScopedPointer<QObject> object(c1.create()); + QVERIFY(!object.isNull() && !c1.isError()); + QVERIFY(!object->property("aIsNull").toBool()); + QVERIFY(object->property("aIsNotNull").toBool()); + QVERIFY(object->property("aIsNotUndefined").toBool()); + QVERIFY(object->property("objectIsNotNull").toBool()); + QVERIFY(!object->property("typedArrayIsNull").toBool()); + QVERIFY(object->property("isUndefined").toBool()); + QVERIFY(!object->property("derivedIsNull").toBool()); + + QVERIFY(object->property("primitiveIsNull").toBool()); + QVERIFY(object->property("primitiveIsDefined").toBool()); + QVERIFY(object->property("primitiveIsUndefined").toBool()); + + QVERIFY(object->property("jsValueIsNull").toBool()); + QVERIFY(object->property("jsValueIsDefined").toBool()); + QVERIFY(object->property("jsValueIsUndefined").toBool()); + + QVERIFY(object->property("nullVarIsUndefined").toBool()); + QVERIFY(object->property("nullIsUndefined").toBool()); + QVERIFY(object->property("nullVarIsNull").toBool()); + QVERIFY(object->property("nullIsNotUndefined").toBool()); } -void tst_QmlCppCodegen::importsFromImportPath() +void tst_QmlCppCodegen::equalityVarAndStorable() { QQmlEngine engine; - QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/importsFromImportPath.qml"_s)); + QQmlComponent planner(&engine, QUrl(u"qrc:/qt/qml/TestTypes/Planner.qml"_s)); + QVERIFY2(!planner.isError(), qPrintable(planner.errorString())); + QScopedPointer<QObject> p(planner.create()); + QVERIFY(!p.isNull()); - // We might propagate the import path, eventually, but for now instantiating is not important. - // If the compiler accepts the file, it's probably fine. - QVERIFY(component.isError()); - QCOMPARE(component.errorString(), - u"qrc:/qt/qml/TestTypes/importsFromImportPath.qml:1 module \"Module\" is not installed\n"_s); + QQmlComponent variable(&engine, QUrl(u"qrc:/qt/qml/TestTypes/Variable.qml"_s)); + QVERIFY2(!variable.isError(), qPrintable(variable.errorString())); + QScopedPointer<QObject> v(variable.create()); + QVERIFY(!v.isNull()); + + QVERIFY(p->objectName().isEmpty()); + QMetaObject::invokeMethod(p.data(), "typeErasedRemoveOne", v.data()); + QCOMPARE(p->objectName(), u"n"); + + v->setProperty("value", 1); + QMetaObject::invokeMethod(p.data(), "typeErasedRemoveOne", v.data()); + QCOMPARE(p->objectName(), u"nd"); + + QQmlComponent constraint(&engine, QUrl(u"qrc:/qt/qml/TestTypes/BaseConstraint.qml"_s)); + QVERIFY2(!constraint.isError(), qPrintable(constraint.errorString())); + QScopedPointer<QObject> c(constraint.create()); + QVERIFY(!c.isNull()); + + c->setProperty("output", QVariant::fromValue(v.data())); + QCOMPARE(v->property("mark").toInt(), 0); + QMetaObject::invokeMethod(p.data(), "typeErasedRun", c.data()); + QCOMPARE(v->property("mark").toInt(), 5); + + QTest::ignoreMessage(QtDebugMsg, "success"); + QMetaObject::invokeMethod(p.data(), "verify", 10); + + QTest::ignoreMessage(QtCriticalMsg, "failed 10 11"); + QMetaObject::invokeMethod(p.data(), "verify", 11); } -void tst_QmlCppCodegen::aliasLookup() +void tst_QmlCppCodegen::equalsUndefined() { QQmlEngine engine; - QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/aliasLookup.qml"_s)); + QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/equalsUndefined.qml"_s)); QVERIFY2(!component.isError(), component.errorString().toUtf8()); QScopedPointer<QObject> object(component.create()); QVERIFY(!object.isNull()); - const QVariant t = object->property("t"); - QCOMPARE(t.metaType(), QMetaType::fromType<QString>()); - QCOMPARE(t.toString(), u"12"_s); + QCOMPARE(object->property("a").toInt(), 50); + QCOMPARE(object->property("b").toInt(), 5000); } -void tst_QmlCppCodegen::outOfBoundsArray() +void tst_QmlCppCodegen::evadingAmbiguity() { QQmlEngine engine; - QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/outOfBounds.qml"_s)); - QVERIFY2(!component.isError(), component.errorString().toUtf8()); - QTest::ignoreMessage(QtDebugMsg, "oob undefined"); - QScopedPointer<QObject> object(component.create()); - QVERIFY(!object.isNull()); - QVERIFY(object->metaObject()->indexOfProperty("oob") > 0); - QVERIFY(!object->property("oob").isValid()); - const QVariant oob2 = object->property("oob2"); - QCOMPARE(oob2.metaType(), QMetaType::fromType<QObject *>()); - QCOMPARE(oob2.value<QObject *>(), nullptr); + // We need to add an import path here because we cannot namespace the implicit import. + // The implicit import is what we use for all the other tests, even if we explicitly + // import TestTypes. That is because the TestTypes module is in a subdirectory "data". + engine.addImportPath(u":/"_s); + + QQmlComponent c1(&engine, QUrl(u"qrc:/qt/qml/TestTypes/ambiguous1/Ambiguous.qml"_s)); + QVERIFY2(c1.isReady(), qPrintable(c1.errorString())); + QScopedPointer<QObject> o1(c1.create()); + QCOMPARE(o1->objectName(), QStringLiteral("Ambiguous")); + QCOMPARE(o1->property("i").toString(), QStringLiteral("Ambiguous1")); + + QQmlComponent c2(&engine, QUrl(u"qrc:/qt/qml/TestTypes/ambiguous2/Ambiguous.qml"_s)); + QVERIFY2(c2.isReady(), qPrintable(c2.errorString())); + QScopedPointer<QObject> o2(c2.create()); + QCOMPARE(o2->objectName(), QStringLiteral("Ambiguous")); + QCOMPARE(o2->property("i").toString(), QStringLiteral("Ambiguous2")); } -void tst_QmlCppCodegen::compositeSingleton() +void tst_QmlCppCodegen::exceptionFromInner() { QQmlEngine engine; - engine.addImportPath(u":/qt/qml/TestTypes/imports/"_s); - QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/compositesingleton.qml"_s)); + QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/exceptionFromInner.qml"_s)); QVERIFY2(!component.isError(), component.errorString().toUtf8()); - QScopedPointer<QObject> o(component.create()); - QCOMPARE(o->property("x").toDouble(), 4.5); - QCOMPARE(o->property("y").toDouble(), 10.0); - QCOMPARE(o->property("smooth").toBool(), true); + QScopedPointer<QObject> object(component.create()); + QVERIFY(!object.isNull()); + + QTest::ignoreMessage( + QtWarningMsg, + "qrc:/qt/qml/TestTypes/exceptionFromInner.qml:7: TypeError: " + "Cannot read property 'objectName' of null"); + QMetaObject::invokeMethod(object.data(), "disbelieveFail"); } -void tst_QmlCppCodegen::lotsOfRegisters() +void tst_QmlCppCodegen::excessiveParameters() { QQmlEngine engine; - QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/page.qml"_s)); + QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/excessiveParameters.qml"_s)); QVERIFY2(!component.isError(), component.errorString().toUtf8()); QScopedPointer<QObject> object(component.create()); QVERIFY(!object.isNull()); + QSignalSpy spy(object.data(), SIGNAL(foo())); + QTRY_VERIFY(spy.size() > 0); +} - const auto compare = [&]() { - const qreal implicitBackgroundWidth = object->property("implicitBackgroundWidth").toDouble(); - const qreal leftInset = object->property("leftInset").toDouble(); - const qreal rightInset = object->property("rightInset").toDouble(); - const qreal contentWidth = object->property("contentWidth").toDouble(); - const qreal leftPadding = object->property("leftPadding").toDouble(); - const qreal rightPadding = object->property("rightPadding").toDouble(); - const qreal implicitFooterWidth = object->property("implicitFooterWidth").toDouble(); - const qreal implicitHeaderWidth = object->property("implicitHeaderWidth").toDouble(); +void tst_QmlCppCodegen::extendedTypes() +{ + QQmlEngine engine; + QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/extendedTypes.qml"_s)); + QVERIFY2(!component.isError(), component.errorString().toUtf8()); - const qreal implicitWidth = object->property("implicitWidth").toDouble(); - QCOMPARE(implicitWidth, qMax(qMax(implicitBackgroundWidth + leftInset + rightInset, - contentWidth + leftPadding + rightPadding), - qMax(implicitHeaderWidth, implicitFooterWidth))); - }; + QTest::ignoreMessage(QtDebugMsg, "6 QSizeF(10, 20) 30"); + QScopedPointer<QObject> object(component.create()); + QVERIFY(!object.isNull()); - compare(); + QCOMPARE(object->property("a").toInt(), 6); + QCOMPARE(qvariant_cast<QSizeF>(object->property("b")), QSizeF(10, 20)); + QCOMPARE(object->property("c").toInt(), 30); + QCOMPARE(object->property("d").toString(), u"QSizeF(10, 20)"_s); - const QList<const char *> props = { - "leftInset", "rightInset", "contentWidth", "leftPadding", "rightPadding" - }; + QCOMPARE(object->property("e").toInt(), 2); +} - for (int i = 0; i < 100; ++i) { - QVERIFY(object->setProperty(props[i % props.size()], (i * 17) % 512)); - compare(); - } +void tst_QmlCppCodegen::failures() +{ + const auto &aotFailure + = QmlCacheGeneratedCode::_qt_qml_TestTypes_failures_qml::aotBuiltFunctions[0]; + QVERIFY(!aotFailure.functionPtr); + QCOMPARE(aotFailure.functionIndex, 0); } -void tst_QmlCppCodegen::inPlaceDecrement() +void tst_QmlCppCodegen::fallbackLookups() { QQmlEngine engine; - QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/dialog.qml"_s)); - QVERIFY2(component.isReady(), qPrintable(component.errorString())); - QScopedPointer<QObject> object(component.create()); - QVERIFY(!object.isNull()); - QObject *header = qvariant_cast<QObject *>(object->property("header")); - QVERIFY(header); - QObject *background = qvariant_cast<QObject *>(header->property("background")); - QObject *parent = qvariant_cast<QObject *>(background->property("parent")); + const QUrl document(u"qrc:/qt/qml/TestTypes/fallbacklookups.qml"_s); + QQmlComponent c(&engine, document); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> o(c.create()); + QVERIFY(o); - QCOMPARE(background->property("width").toInt(), parent->property("width").toInt() + 1); - QCOMPARE(background->property("height").toInt(), parent->property("height").toInt() - 1); + QCOMPARE(o->objectName(), QString()); + int result = 0; - QVERIFY(object->setProperty("width", QVariant::fromValue(17))); - QVERIFY(parent->property("width").toInt() > 0); - QVERIFY(object->setProperty("height", QVariant::fromValue(53))); - QVERIFY(parent->property("height").toInt() > 0); + QMetaObject::invokeMethod(o.data(), "withContext", Q_RETURN_ARG(int, result)); + QCOMPARE(result, 16); + QCOMPARE(o->objectName(), QStringLiteral("aa93")); - QCOMPARE(background->property("width").toInt(), parent->property("width").toInt() + 1); - QCOMPARE(background->property("height").toInt(), parent->property("height").toInt() - 1); + QMetaObject::invokeMethod(o.data(), "withId", Q_RETURN_ARG(int, result)); + QCOMPARE(result, 17); + QCOMPARE(o->objectName(), QStringLiteral("bb94")); - QCOMPARE(object->property("a").toInt(), 1024); + QObject *singleton = nullptr; + QMetaObject::invokeMethod(o.data(), "getSingleton", Q_RETURN_ARG(QObject*, singleton)); + QVERIFY(singleton); + + QMetaObject::invokeMethod(o.data(), "withSingleton", Q_RETURN_ARG(int, result)); + QCOMPARE(result, 18); + QCOMPARE(singleton->objectName(), QStringLiteral("cc95")); + + QMetaObject::invokeMethod(o.data(), "withProperty", Q_RETURN_ARG(int, result)); + QCOMPARE(result, 19); + QCOMPARE(singleton->objectName(), QStringLiteral("dd96")); } -void tst_QmlCppCodegen::shifts() +void tst_QmlCppCodegen::fileImportsContainCxxTypes() { QQmlEngine engine; - QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/shifts.qml"_s)); - QVERIFY2(component.isReady(), qPrintable(component.errorString())); - QScopedPointer<QObject> object(component.create()); - QVERIFY(!object.isNull()); - - QCOMPARE(object->property("a").toInt(), 9728); - QCOMPARE(object->property("b").toInt(), 4864); - QCOMPARE(object->property("c").toInt(), 19448); - QCOMPARE(object->property("d").toInt(), 9731); - QCOMPARE(object->property("e").toInt(), 0); + QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/usingCxxTypesFromFileImports.qml"_s)); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> o(c.create()); + QVERIFY(!o.isNull()); + QCOMPARE(o->objectName(), u"horst guenther"_s); } -void tst_QmlCppCodegen::valueTypeProperty() +void tst_QmlCppCodegen::flagEnum() { QQmlEngine engine; - QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/valueTypeProperty.qml"_s)); - QVERIFY2(component.isReady(), component.errorString().toUtf8()); - QScopedPointer<QObject> object(component.create()); + QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/flagEnum.qml"_s)); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> o(c.create()); + QVERIFY(!o.isNull()); - QFont font = qvariant_cast<QFont>(object->property("font")); - QCOMPARE(object->property("foo").toString(), font.family()); - font.setFamily(u"Bar"_s); - object->setProperty("font", QVariant::fromValue(font)); - QCOMPARE(object->property("foo").toString(), u"Bar"_s); + QQmlCommunicationPermission *p = qobject_cast<QQmlCommunicationPermission *>(o.data()); + QCOMPARE(p->communicationModes(), CommunicationPermission::Access); } -void tst_QmlCppCodegen::propertyOfParent() +void tst_QmlCppCodegen::flushBeforeCapture() { QQmlEngine engine; - QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/RootWithoutId.qml"_s)); - QVERIFY2(component.isReady(), component.errorString().toUtf8()); - QScopedPointer<QObject> object(component.create()); - - QObject *child = qmlContext(object.data())->objectForName(u"item"_s); + QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/noBindingLoop.qml"_s)); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> o(c.create()); + QVERIFY(o); - bool expected = false; + QCOMPARE(o->property("deviation").toDouble(), 9.0 / 3.3333); + QCOMPARE(o->property("samples").toInt(), 16); + QCOMPARE(o->property("radius").toDouble(), 8.0); +} - for (int i = 0; i < 3; ++i) { - const QVariant foo = object->property("foo"); - QCOMPARE(foo.metaType(), QMetaType::fromType<bool>()); - QCOMPARE(foo.toBool(), expected); +void tst_QmlCppCodegen::fromBoolValue() +{ + QQmlEngine engine; + QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/fromBoolValue.qml"_s)); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> o(c.create()); + QCOMPARE(o->property("a").toBool(), true); + o->setProperty("x", 100); + QCOMPARE(o->property("a").toBool(), false); - const QVariant bar = object->property("bar"); - QCOMPARE(bar.metaType(), QMetaType::fromType<bool>()); - QCOMPARE(bar.toBool(), expected); + QCOMPARE(o->property("width").toInt(), 100); + QCOMPARE(o->property("b").toBool(), false); - const QVariant visible = child->property("visible"); - QCOMPARE(visible.metaType(), QMetaType::fromType<bool>()); - QCOMPARE(visible.toBool(), expected); + QScopedPointer<QObject> parent(c.create()); + o->setProperty("parent", QVariant::fromValue(parent.data())); + QCOMPARE(o->property("width").toInt(), 100); + QCOMPARE(o->property("b").toBool(), false); - expected = !expected; - object->setProperty("foo", expected); - } + o->setProperty("state", QVariant::fromValue(u"foo"_s)); + QCOMPARE(o->property("width").toInt(), 0); + QCOMPARE(o->property("b").toBool(), false); } -void tst_QmlCppCodegen::accessModelMethodFromOutSide() +void tst_QmlCppCodegen::funcWithParams() { QQmlEngine engine; - QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/AccessModelMethodsFromOutside.qml"_s)); - QVERIFY2(component.isReady(), component.errorString().toUtf8()); - - QTest::ignoreMessage(QtDebugMsg, "3"); - QTest::ignoreMessage(QtDebugMsg, "Apple"); + QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/funcWithParams.qml"_s)); + QVERIFY2(!component.isError(), component.errorString().toUtf8()); QScopedPointer<QObject> object(component.create()); - - QCOMPARE(object->property("cost1").toDouble(), 3); - QCOMPARE(object->property("name1").toString(), u"Orange"_s); - QCOMPARE(object->property("cost2").toDouble(), 1.95); - QCOMPARE(object->property("name2").toString(), u"Banana"_s); + QVERIFY(!object.isNull()); + QCOMPARE(object->property("bar").toInt(), 30); } // QML-generated types have no C++ names, but we want to call a method that @@ -1150,42 +1999,55 @@ void tst_QmlCppCodegen::accessModelMethodFromOutSide() // the metatype of the argument we pass does not match the metatype of the // argument the method expects. In order to work around it, we specialize // qMetaTypeInterfaceForType() and produce a "correct" metatype this way. -class Dummy_QMLTYPE_0; +class Dummy2_QMLTYPE_0; // We set this to the actual value retrieved from an actual instance of the QML // type before retrieving the metatype interface for the first time. static const QtPrivate::QMetaTypeInterface *dummyMetaTypeInterface = nullptr; template<> -const QtPrivate::QMetaTypeInterface *QtPrivate::qMetaTypeInterfaceForType<Dummy_QMLTYPE_0 *>() { +const QtPrivate::QMetaTypeInterface *QtPrivate::qMetaTypeInterfaceForType<Dummy2_QMLTYPE_0 *>() { return dummyMetaTypeInterface; } void tst_QmlCppCodegen::functionArguments() { + qmlClearTypeRegistrations(); + QQmlEngine engine; + QQmlComponent preheat(&engine); + preheat.setData(R"( + import QtQml + import TestTypes + QtObject { + objectName: Style.objectName + } + )", QUrl(u"qrc:/qt/qml/TestTypes/preheat.qml"_s)); + QVERIFY2(preheat.isReady(), qPrintable(preheat.errorString())); + QScopedPointer<QObject> hot(preheat.create()); + QVERIFY(!hot.isNull()); - // Ensure that Dummy gets counter value 0. Don't do that at home + // Ensure that Dummy gets counter value 1. Don't do that at home QScopedValueRollback<QAtomicInt> rb(QQmlPropertyCacheCreatorBase::classIndexCounter, 0); - QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/Dummy.qml"_s)); + QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/Dummy2.qml"_s)); QVERIFY2(component.isReady(), component.errorString().toUtf8()); QScopedPointer<QObject> object(component.create()); const QMetaObject *metaObject = object->metaObject(); dummyMetaTypeInterface = metaObject->metaType().iface(); const QByteArray className = QByteArray(metaObject->className()); - QCOMPARE(className, "Dummy_QMLTYPE_0"); + QCOMPARE(className, "Dummy2_QMLTYPE_0"); int result; int a = 1; bool b = false; - Dummy_QMLTYPE_0 *c = nullptr; + Dummy2_QMLTYPE_0 *c = nullptr; double d = -1.2; int e = 3; metaObject->invokeMethod( object.data(), "someFunction", Q_RETURN_ARG(int, result), - Q_ARG(int, a), Q_ARG(bool, b), Q_ARG(Dummy_QMLTYPE_0 *, c), + Q_ARG(int, a), Q_ARG(bool, b), Q_ARG(Dummy2_QMLTYPE_0 *, c), Q_ARG(double, d), Q_ARG(int, e)); QCOMPARE(result, 42); @@ -1198,473 +2060,780 @@ void tst_QmlCppCodegen::functionArguments() QCOMPARE(concatenated, u"foobar"_s); } -void tst_QmlCppCodegen::bindingExpression() +void tst_QmlCppCodegen::functionCallOnNamespaced() { QQmlEngine engine; - QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/BindingExpression.qml"_s)); - QVERIFY2(component.isReady(), component.errorString().toUtf8()); - QScopedPointer<QObject> object(component.create()); - - QObject *child = qmlContext(object.data())->objectForName(u"child"_s); - - double width = 200; - double y = 10; - for (int i = 0; i < 10; ++i) { - QCOMPARE(object->property("width").toDouble(), width); - QCOMPARE(object->property("height").toDouble(), width); - QCOMPARE(object->property("y").toDouble(), y); - - const double childY = y + (width - 100) / 2; - QCOMPARE(child->property("y").toDouble(), childY); - QCOMPARE(object->property("mass"), childY > 100 ? u"heavy"_s : u"light"_s); - QCOMPARE(object->property("test_division").toDouble(), width / 1000 + 50); - QCOMPARE(object->property("test_ternary").toDouble(), 2.2); - - const int test_switch = object->property("test_switch").toInt(); - switch (int(width) % 3) { - case 0: - QCOMPARE(test_switch, 130); - break; - case 1: - QCOMPARE(test_switch, 380); - break; - case 2: - QCOMPARE(test_switch, 630); - break; - } + { + QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/themergood.qml"_s)); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> o(c.create()); + QVERIFY(o); + QCOMPARE(o->property("i").toInt(), 12); + } - width = 200 * i; - y = 10 + i; - object->setProperty("width", width); - object->setProperty("y", y); + { + QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/themerbad.qml"_s)); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> o(c.create()); + QVERIFY(o); + QCOMPARE(o->property("r"), QVariant::fromValue(QRectF(5.0, 10.0, 1.0, 1.0))); } } -void tst_QmlCppCodegen::voidFunction() +void tst_QmlCppCodegen::functionLookup() { QQmlEngine engine; - QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/voidfunction.qml"_s)); - QVERIFY2(component.isReady(), component.errorString().toUtf8()); - QScopedPointer<QObject> object(component.create()); - QVERIFY(!object.isNull()); - QVERIFY(object->objectName().isEmpty()); - object->metaObject()->invokeMethod(object.data(), "doesNotReturnValue"); - QCOMPARE(object->objectName(), u"barbar"_s); + QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/functionLookup.qml"_s)); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> o(c.create()); + QVERIFY(o); + const QVariant foo = o->property("bar"); + QCOMPARE(foo.metaType(), QMetaType::fromType<QJSValue>()); + const QJSManagedValue method(engine.toScriptValue(foo), &engine); + QVERIFY(method.isFunction()); + const QJSValue result = method.call(); + QVERIFY(result.isString()); + QCOMPARE(result.toString(), QStringLiteral("a99")); } -void tst_QmlCppCodegen::overriddenProperty() +void tst_QmlCppCodegen::functionReturningVoid() { QQmlEngine engine; - QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/childobject.qml"_s)); - QVERIFY2(component.isReady(), component.errorString().toUtf8()); - QScopedPointer<QObject> object(component.create()); - QVERIFY(!object.isNull()); - QCOMPARE(object->objectName(), u"kraut"_s); - QCOMPARE(object->property("doneThing").toInt(), 5); - QCOMPARE(object->property("usingFinal").toInt(), 5); + QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/functionReturningVoid.qml"_s)); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> o(c.create()); + QVERIFY(o); - auto checkAssignment = [&]() { - const QString newName = u"worscht"_s; - QMetaObject::invokeMethod(object.data(), "setChildObjectName", Q_ARG(QString, newName)); - QCOMPARE(object->objectName(), newName); - }; - checkAssignment(); + // It should be able to call the methods and wrap the void values into invalid QVariants, + // without crashing. + QVERIFY(o->metaObject()->indexOfProperty("aa") >= 0); + QVERIFY(o->metaObject()->indexOfProperty("bb") >= 0); + QVERIFY(!o->property("aa").isValid()); + QVERIFY(!o->property("bb").isValid()); +} - ObjectWithMethod *benign = new ObjectWithMethod(object.data()); - benign->theThing = 10; - benign->setObjectName(u"cabbage"_s); - object->setProperty("child", QVariant::fromValue(benign)); - QCOMPARE(object->objectName(), u"cabbage"_s); - checkAssignment(); - QCOMPARE(object->property("doneThing").toInt(), 10); - QCOMPARE(object->property("usingFinal").toInt(), 10); +void tst_QmlCppCodegen::functionTakingVar() +{ + QQmlEngine engine; + const QUrl document(u"qrc:/qt/qml/TestTypes/functionTakingVar.qml"_s); + QQmlComponent c(&engine, document); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> o(c.create()); + QVERIFY(o); - OverriddenObjectName *evil = new OverriddenObjectName(object.data()); - QTest::ignoreMessage(QtWarningMsg, - "Final member fff is overridden in class OverriddenObjectName. " - "The override won't be used."); - object->setProperty("child", QVariant::fromValue(evil)); + QVERIFY(!o->property("c").isValid()); - QCOMPARE(object->objectName(), u"borschtsch"_s); + int value = 11; + QQmlEnginePrivate *e = QQmlEnginePrivate::get(&engine); + void *args[] = { nullptr, reinterpret_cast<void *>(std::addressof(value)) }; + QMetaType types[] = { QMetaType::fromType<void>(), QMetaType::fromType<std::decay_t<int>>() }; + e->executeRuntimeFunction(document, 0, o.data(), 1, args, types); - checkAssignment(); - QCOMPARE(object->property("doneThing").toInt(), 7); - QCOMPARE(object->property("usingFinal").toInt(), 5); + QCOMPARE(o->property("c"), QVariant::fromValue<int>(11)); } -void tst_QmlCppCodegen::listLength() +void tst_QmlCppCodegen::getLookupOfScript() { QQmlEngine engine; - QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/listlength.qml"_s)); - QVERIFY2(component.isReady(), component.errorString().toUtf8()); - QScopedPointer<QObject> object(component.create()); - QVERIFY(!object.isNull()); - QCOMPARE(object->property("l").toInt(), 2); + QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/NotificationItem.qml"_s)); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> o(c.create()); + QVERIFY(!o.isNull()); + QCOMPARE(o->objectName(), u"heading"_s); } -void tst_QmlCppCodegen::parentProperty() +void tst_QmlCppCodegen::getOptionalLookup_data() { - QQmlEngine engine; - QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/parentProp.qml"_s)); - QVERIFY2(component.isReady(), component.errorString().toUtf8()); - QScopedPointer<QObject> object(component.create()); - QVERIFY(!object.isNull()); - QCOMPARE(object->property("c").toInt(), 11); - QCOMPARE(object->property("i").toInt(), 22); - object->setProperty("a", QVariant::fromValue(22)); - QCOMPARE(object->property("c").toInt(), 28); - object->setProperty("implicitWidth", QVariant::fromValue(14)); - QCOMPARE(object->property("i").toInt(), 26); + QTest::addColumn<QString>("propertyName"); + QTest::addColumn<QVariant>("expected"); - QObject *child = qmlContext(object.data())->objectForName(u"child"_s); - QObject *sibling = qmlContext(object.data())->objectForName(u"sibling"_s); - QObject *evil = qmlContext(object.data())->objectForName(u"evil"_s); + // Objects + QTest::addRow("int on object") << u"to1"_s << QVariant(5); + QTest::addRow("string on object") << u"to2"_s << QVariant("6"); + QTest::addRow("object on object") << u"to3"_s << QVariant::fromValue<QObject *>(nullptr); + QTest::addRow("int on null") << u"to4"_s << QVariant(); // undefined + QTest::addRow("any on undefined as object") << u"to5"_s << QVariant(); // undefined + QTest::addRow("int on string on object") << u"to6"_s << QVariant(1); - child->setProperty("parent", QVariant::fromValue(sibling)); + // Value Types + QTest::addRow("int on rect") << u"tv1"_s << QVariant(50); + QTest::addRow("int on point") << u"tv2"_s << QVariant(-10); + QTest::addRow("int on undefined as point") << u"tv4"_s << QVariant(); // undefined - QCOMPARE(child->property("b").toInt(), 0); - QCOMPARE(child->property("i").toInt(), 28); - QCOMPARE(object->property("i").toInt(), 56); + // Enums + QTest::addRow("enum on object") << u"te1"_s << QVariant(1); + QTest::addRow("enum on type") << u"te2"_s << QVariant(1); + QTest::addRow("enums comparison 1") << u"te3"_s << QVariant(false); + QTest::addRow("enums comparison 2") << u"te4"_s << QVariant(true); - child->setProperty("parent", QVariant::fromValue(evil)); + // Complex chains + QTest::addRow("mixed 1") << u"tc1"_s << QVariant(-10); + QTest::addRow("mixed 2") << u"tc2"_s << QVariant(0); + QTest::addRow("early out 1") << u"tc4"_s << QVariant(); // undefined + QTest::addRow("early out 2") << u"tc5"_s << QVariant(); // undefined +} - QCOMPARE(child->property("b").toInt(), 5994); - QCOMPARE(object->property("c").toInt(), 5996); +void tst_QmlCppCodegen::getOptionalLookup() +{ + QQmlEngine engine; + const QUrl document(u"qrc:/qt/qml/TestTypes/getOptionalLookup.qml"_s); + QQmlComponent c(&engine, document); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> o(c.create()); + QVERIFY(o); - QCOMPARE(child->property("i").toInt(), 443); - QCOMPARE(object->property("i").toInt(), 886); + QFETCH(QString, propertyName); + QFETCH(QVariant, expected); - { - QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/specificParent.qml"_s)); + QVariant actual = o->property(propertyName.toLocal8Bit()); + QCOMPARE(actual, expected); +} - QVERIFY2(component.isReady(), qPrintable(component.errorString())); - QScopedPointer<QObject> rootObject(component.create()); - QVERIFY(rootObject); +void tst_QmlCppCodegen::getOptionalLookupOnQJSValueNonStrict() +{ + QQmlEngine engine; + const QUrl document(u"qrc:/qt/qml/TestTypes/GetOptionalLookupOnQJSValueNonStrict.qml"_s); + QQmlComponent c(&engine, document); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> o(c.create()); + QVERIFY(o); - QCOMPARE(rootObject->property("a").toReal(), 77.0); - } + QVERIFY(o->property("b").toBool()); } -void tst_QmlCppCodegen::registerElimination() +void tst_QmlCppCodegen::getOptionalLookupShadowed() { QQmlEngine engine; - QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/registerelimination.qml"_s)); - QVERIFY2(component.isReady(), component.errorString().toUtf8()); + const QUrl document(u"qrc:/qt/qml/TestTypes/GetOptionalLookupShadowed.qml"_s); + QQmlComponent c(&engine, document); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> o(c.create()); + QVERIFY(o); + + QCOMPARE(o->property("res").toString(), "a"); +} + +void tst_QmlCppCodegen::globals() +{ + QQmlEngine engine; + int exitCode = -1; + QObject::connect(&engine, &QQmlEngine::exit, [&](int code) { exitCode = code; }); + QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/globals.qml"_s)); + QVERIFY2(!component.isError(), component.errorString().toUtf8()); + + const QByteArray message = QByteArray("Start 2 ") + arg1(); + QTest::ignoreMessage(QtDebugMsg, message.constData()); + QScopedPointer<QObject> object(component.create()); QVERIFY(!object.isNull()); + QTRY_COMPARE(exitCode, 0); - // Increment of 23 hits both 0 and 460 - for (int input = -23; input < 700; input += 23) { - object->setProperty("input", input); - if (input <= 0 || input >= 460) - QCOMPARE(object->property("output").toInt(), 459); - else - QCOMPARE(object->property("output").toInt(), input); - } + QObject *application = qvariant_cast<QObject *>(object->property("application")); + QVERIFY(application); + QCOMPARE(QString::fromUtf8(application->metaObject()->className()), + u"QQuickApplication"_s); + + QTest::ignoreMessage(QtDebugMsg, "End"); + QMetaObject::invokeMethod(application, "aboutToQuit"); + + const QVariant somewhere = object->property("somewhere"); + QCOMPARE(somewhere.userType(), QMetaType::QUrl); + QCOMPARE(qvariant_cast<QUrl>(somewhere).toString(), u"qrc:/somewhere/else.qml"_s); + + const QVariant somewhereString = object->property("somewhereString"); + QCOMPARE(somewhereString.userType(), QMetaType::QString); + QCOMPARE(somewhereString.toString(), u"qrc:/somewhere/else.qml"_s); + + const QVariant plain = object->property("plain"); + QCOMPARE(plain.userType(), QMetaType::QUrl); + QCOMPARE(qvariant_cast<QUrl>(plain).toString(), u"/not/here.qml"_s); } -void tst_QmlCppCodegen::asCast() +void tst_QmlCppCodegen::idAccess() { QQmlEngine engine; - QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/asCast.qml"_s)); - QVERIFY2(component.isReady(), component.errorString().toUtf8()); - QScopedPointer<QObject> root(component.create()); - QVERIFY(!root.isNull()); + QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/idAccess.qml"_s)); + QVERIFY2(!component.isError(), component.errorString().toUtf8()); + QScopedPointer<QObject> object(component.create()); + QVERIFY(!object.isNull()); - QQmlContext *context = qmlContext(root.data()); - const QObject *object = context->objectForName(u"object"_s); - const QObject *item = context->objectForName(u"item"_s); - const QObject *rectangle = context->objectForName(u"rectangle"_s); - const QObject *dummy = context->objectForName(u"dummy"_s); + QVERIFY(object->property("y").toInt() != 48); + QCOMPARE(object->property("y").toInt(), 12); + object->setProperty("z", 13); + QCOMPARE(object->property("y").toInt(), 13); + object->setProperty("x", QVariant::fromValue(333)); + QCOMPARE(object->property("y").toInt(), 48); - QCOMPARE(qvariant_cast<QObject *>(root->property("objectAsObject")), object); - QCOMPARE(qvariant_cast<QObject *>(root->property("objectAsItem")), nullptr); - QCOMPARE(qvariant_cast<QObject *>(root->property("objectAsRectangle")), nullptr); - QCOMPARE(qvariant_cast<QObject *>(root->property("objectAsDummy")), nullptr); + // The binding was broken by setting the property + object->setProperty("z", 14); + QCOMPARE(object->property("y").toInt(), 48); - QCOMPARE(qvariant_cast<QObject *>(root->property("itemAsObject")), item); - QCOMPARE(qvariant_cast<QObject *>(root->property("itemAsItem")), item); - QCOMPARE(qvariant_cast<QObject *>(root->property("itemAsRectangle")), nullptr); - QCOMPARE(qvariant_cast<QObject *>(root->property("itemAsDummy")), nullptr); + QObject *ttt = qmlContext(object.data())->objectForName(u"ttt"_s); + QFont f = qvariant_cast<QFont>(ttt->property("font")); + QCOMPARE(f.pointSize(), 22); - QCOMPARE(qvariant_cast<QObject *>(root->property("rectangleAsObject")), rectangle); - QCOMPARE(qvariant_cast<QObject *>(root->property("rectangleAsItem")), rectangle); - QCOMPARE(qvariant_cast<QObject *>(root->property("rectangleAsRectangle")), rectangle); - QCOMPARE(qvariant_cast<QObject *>(root->property("rectangleAsDummy")), nullptr); + QObject::connect(object.data(), &QObject::objectNameChanged, ttt, [&](){ + ttt->setParent(nullptr); + QJSEngine::setObjectOwnership(ttt, QJSEngine::CppOwnership); + object.reset(ttt); + }); - QCOMPARE(qvariant_cast<QObject *>(root->property("dummyAsObject")), dummy); - QCOMPARE(qvariant_cast<QObject *>(root->property("dummyAsItem")), dummy); - QCOMPARE(qvariant_cast<QObject *>(root->property("dummyAsRectangle")), nullptr); - QCOMPARE(qvariant_cast<QObject *>(root->property("dummyAsDummy")), dummy); + QVERIFY(object->objectName().isEmpty()); + QVERIFY(ttt->objectName().isEmpty()); + ttt->setProperty("text", u"kill"_s); + QCOMPARE(object.data(), ttt); + QCOMPARE(ttt->objectName(), u"context"_s); } -void tst_QmlCppCodegen::noQQmlData() +void tst_QmlCppCodegen::ignoredFunctionReturn() { QQmlEngine engine; - QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/noQQmlData.qml"_s)); - QVERIFY2(component.isReady(), component.errorString().toUtf8()); + QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/ignoredFunctionReturn.qml"_s)); + QVERIFY2(!component.isError(), component.errorString().toUtf8()); + QScopedPointer<QObject> object(component.create()); + QVERIFY(!object.isNull()); +} - QTest::ignoreMessage(QtWarningMsg, "qrc:/qt/qml/TestTypes/noQQmlData.qml:7: TypeError: " - "Cannot read property 'name' of null"); - QScopedPointer<QObject> root(component.create()); - QVERIFY(!root.isNull()); +void tst_QmlCppCodegen::importsFromImportPath() +{ + QQmlEngine engine; + QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/importsFromImportPath.qml"_s)); - BirthdayParty *party = qobject_cast<BirthdayParty *>(root.data()); - QVERIFY(party != nullptr); + // We might propagate the import path, eventually, but for now instantiating is not important. + // If the compiler accepts the file, it's probably fine. + QVERIFY(component.isError()); + QCOMPARE(component.errorString(), + u"qrc:/qt/qml/TestTypes/importsFromImportPath.qml:1 module \"Module\" is not installed\n"_s); +} - QCOMPARE(party->host(), nullptr); - QCOMPARE(party->property("n").toString(), QString()); +void tst_QmlCppCodegen::inPlaceDecrement() +{ + QQmlEngine engine; + QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/dialog.qml"_s)); + QVERIFY2(component.isReady(), qPrintable(component.errorString())); + QScopedPointer<QObject> object(component.create()); + QVERIFY(!object.isNull()); + QObject *header = qvariant_cast<QObject *>(object->property("header")); + QVERIFY(header); + QObject *background = qvariant_cast<QObject *>(header->property("background")); + QObject *parent = qvariant_cast<QObject *>(background->property("parent")); - Person *host1 = new Person(party); - party->setHost(host1); - QCOMPARE(party->property("n").toString(), u"Bart in da house!"_s); - host1->setName(u"Marge"_s); - QCOMPARE(party->property("n").toString(), u"Marge in da house!"_s); + QCOMPARE(background->property("width").toInt(), parent->property("width").toInt() + 1); + QCOMPARE(background->property("height").toInt(), parent->property("height").toInt() - 1); - QTest::ignoreMessage(QtWarningMsg, "qrc:/qt/qml/TestTypes/noQQmlData.qml:7: TypeError: " - "Cannot read property 'name' of null"); + QVERIFY(object->setProperty("width", QVariant::fromValue(17))); + QVERIFY(parent->property("width").toInt() > 0); + QVERIFY(object->setProperty("height", QVariant::fromValue(53))); + QVERIFY(parent->property("height").toInt() > 0); - // Doesn't crash - party->setHost(nullptr); + QCOMPARE(background->property("width").toInt(), parent->property("width").toInt() + 1); + QCOMPARE(background->property("height").toInt(), parent->property("height").toInt() - 1); - // Lookups are initialized now, and we introduce an object without QQmlData - Person *host2 = new Person(party); - party->setHost(host2); - QCOMPARE(party->property("n").toString(), u"Bart in da house!"_s); - host2->setName(u"Homer"_s); - QCOMPARE(party->property("n").toString(), u"Homer in da house!"_s); + QCOMPARE(object->property("a").toInt(), 1024); +} - QMetaObject::invokeMethod(party, "burn"); - engine.collectGarbage(); +void tst_QmlCppCodegen::inaccessibleProperty() +{ + QQmlEngine engine; - // Does not crash - party->setProperty("inDaHouse", u" burns!"_s); + QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/versionmismatch.qml"_s)); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> o(c.create()); - // Mr Burns may or may not burn, depending on whether we use lookups. - // If using lookups, the binding is aborted when we find the isQueuedForDeletion flag. - // If reading the property directly, we don't have to care about it. - QVERIFY(party->property("n").toString().startsWith(u"Mr Burns"_s)); + QCOMPARE(o->property("c").toInt(), 5); } -void tst_QmlCppCodegen::scopeObjectDestruction() +void tst_QmlCppCodegen::indirectlyShadowable() { QQmlEngine engine; - QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/fileDialog.qml"_s)); - QVERIFY2(component.isReady(), qPrintable(component.errorString())); - QScopedPointer<QObject> rootObject(component.create()); - QVERIFY(rootObject); + const QString url = u"qrc:/qt/qml/TestTypes/indirectlyShadowable.qml"_s; + QQmlComponent c(&engine, QUrl(url)); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> o(c.create()); + QVERIFY(!o.isNull()); - QObject *dialog = rootObject->property("dialog").value<QObject *>(); - QVERIFY(dialog); + const auto verifyShadowable = [&](const QString &objectName) { + QObject *outer = o->property("outer").value<QObject *>(); + QVERIFY(outer); + QObject *inner = outer->property("inner").value<QObject *>(); + QVERIFY(inner); + QObject *shadowable = inner->property("shadowable").value<QObject *>(); + QVERIFY(shadowable); + QCOMPARE(shadowable->objectName(), objectName); + }; - // We cannot check the warning messages. The AOT compiled code complains about reading the - // "parent" property of an object scheduled for deletion. The runtime silently returns undefined - // at that point and then complains about not being able to read a property on undefined. + const auto verifyNotShadowable = [&](const QString &objectName) { + QObject *notShadowable = o->property("notShadowable").value<QObject *>(); + QCOMPARE(notShadowable->objectName(), objectName); + }; - // Doesn't crash, even though it triggers bindings on scope objects scheduled for deletion. - QMetaObject::invokeMethod(dialog, "open"); -} + const auto verifyEvil = [&]() { + QObject *outer = o->property("outer").value<QObject *>(); + QVERIFY(outer); + QCOMPARE(outer->property("inner").toString(), u"evil"_s); + }; -static void checkColorProperties(QQmlComponent *component) -{ - QVERIFY2(component->isReady(), qPrintable(component->errorString())); - QScopedPointer<QObject> rootObject(component->create()); - QVERIFY(rootObject); + verifyShadowable(u"shadowable"_s); + verifyNotShadowable(u"notShadowable"_s); - const QMetaObject *mo = QMetaType::fromName("QQuickIcon").metaObject(); - QVERIFY(mo != nullptr); + QMetaObject::invokeMethod(o.data(), "setInnerShadowable"); - const QMetaProperty prop = mo->property(mo->indexOfProperty("color")); - QVERIFY(prop.isValid()); + verifyShadowable(u"self"_s); + verifyNotShadowable(u"notShadowable"_s); - const QVariant a = rootObject->property("a"); - QVERIFY(a.isValid()); + QMetaObject::invokeMethod(o.data(), "getInnerShadowable"); - const QVariant iconColor = prop.readOnGadget(rootObject->property("icon").data()); - QVERIFY(iconColor.isValid()); + verifyShadowable(u"self"_s); + verifyNotShadowable(u"self"_s); - const QMetaType colorType = QMetaType::fromName("QColor"); - QVERIFY(colorType.isValid()); + QMetaObject::invokeMethod(o.data(), "turnEvil"); - QCOMPARE(a.metaType(), colorType); - QCOMPARE(iconColor.metaType(), colorType); + verifyEvil(); + verifyNotShadowable(u"self"_s); - QCOMPARE(iconColor, a); + // Does not produce an error message because JavaScript. + QMetaObject::invokeMethod(o.data(), "setInnerShadowable"); + + verifyEvil(); + verifyNotShadowable(u"self"_s); + + QTest::ignoreMessage( + QtWarningMsg, qPrintable(url + u":29: Error: Cannot assign [undefined] to QObject*"_s)); + QMetaObject::invokeMethod(o.data(), "getInnerShadowable"); + + verifyEvil(); + verifyNotShadowable(u"self"_s); } -void tst_QmlCppCodegen::colorAsVariant() +void tst_QmlCppCodegen::infinities() { QQmlEngine engine; - QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/colorAsVariant.qml"_s)); - checkColorProperties(&component); + QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/infinities.qml"_s)); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> o(c.create()); + QVERIFY(o); + + QCOMPARE(o->property("positiveInfinity").toDouble(), std::numeric_limits<double>::infinity()); + QCOMPARE(o->property("negativeInfinity").toDouble(), -std::numeric_limits<double>::infinity()); + + const double positiveZero = o->property("positiveZero").toDouble(); + QCOMPARE(positiveZero, 0.0); + QVERIFY(!std::signbit(positiveZero)); + + const double negativeZero = o->property("negativeZero").toDouble(); + QCOMPARE(negativeZero, -0.0); + QVERIFY(std::signbit(negativeZero)); + + QVERIFY(qIsNaN(o->property("naN").toDouble())); } -void tst_QmlCppCodegen::bindToValueType() +void tst_QmlCppCodegen::infinitiesToInt() { QQmlEngine engine; - QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/bindToValueType.qml"_s)); - checkColorProperties(&component); + + QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/infinitiesToInt.qml"_s)); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> o(c.create()); + QVERIFY(!o.isNull()); + + const char *props[] = {"a", "b", "c"}; + for (const char *prop : props) { + const QVariant i = o->property(prop); + QCOMPARE(i.metaType(), QMetaType::fromType<int>()); + bool ok = false; + QCOMPARE(i.toInt(&ok), 0); + QVERIFY(ok); + } } -void tst_QmlCppCodegen::undefinedResets() +void tst_QmlCppCodegen::innerObjectNonShadowable() { QQmlEngine engine; - QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/undefinedResets.qml"_s)); + QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/ownProperty.qml"_s)); QVERIFY2(component.isReady(), qPrintable(component.errorString())); QScopedPointer<QObject> rootObject(component.create()); QVERIFY(rootObject); - Person *person = qobject_cast<Person *>(rootObject.data()); - QVERIFY(person); - QCOMPARE(person->shoeSize(), 0); - QCOMPARE(person->name(), u"Marge"_s); - - person->setShoeSize(11); + QCOMPARE(rootObject->objectName(), u"foo"_s); +} - QCOMPARE(person->shoeSize(), 11); - QCOMPARE(person->name(), u"Bart"_s); +void tst_QmlCppCodegen::intEnumCompare() +{ + QQmlEngine engine; + { + QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/intEnumCompare.qml"_s)); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> o(c.create()); + QVERIFY(o); + QCOMPARE(o->property("a").toBool(), true); + QCOMPARE(o->property("b").toBool(), false); + QCOMPARE(o->property("c").toBool(), true); + QCOMPARE(o->property("d").toBool(), false); + } - person->setShoeSize(10); - QCOMPARE(person->shoeSize(), 10); - QCOMPARE(person->name(), u"Marge"_s); + { + // We cannot use Qt.red in QML because it's lower case. + QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/enumInvalid.qml"_s)); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> o(c.create()); + QVERIFY(o); + QCOMPARE(o->property("c").toBool(), true); + QCOMPARE(o->property("d").toBool(), false); + } +} - person->setName(u"no one"_s); - QCOMPARE(person->name(), u"no one"_s); +void tst_QmlCppCodegen::intOverflow() +{ + QQmlEngine engine; + QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/intOverflow.qml"_s)); + QVERIFY2(!component.isError(), component.errorString().toUtf8()); + QScopedPointer<QObject> object(component.create()); + QVERIFY(!object.isNull()); + QCOMPARE(object->property("a").toDouble(), 1.09951162778e+12); + QCOMPARE(object->property("b").toInt(), 5); +} - person->setObjectName(u"the one"_s); - QCOMPARE(person->name(), u"Bart"_s); +void tst_QmlCppCodegen::intToEnum() +{ + QQmlEngine engine; + QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/intToEnum.qml"_s)); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> o(c.create()); + QVERIFY(!o.isNull()); + MyType *m = qobject_cast<MyType *>(o.data()); + QCOMPARE(m->a(), MyType::D); + QCOMPARE(m->property("b").toInt(), 24); } -void tst_QmlCppCodegen::innerObjectNonShadowable() +void tst_QmlCppCodegen::interceptor() { QQmlEngine engine; - QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/ownProperty.qml"_s)); + QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/interceptor.qml"_s)); + QVERIFY2(!component.isError(), component.errorString().toUtf8()); + QScopedPointer<QObject> object(component.create()); + QVERIFY(!object.isNull()); - QVERIFY2(component.isReady(), qPrintable(component.errorString())); - QScopedPointer<QObject> rootObject(component.create()); - QVERIFY(rootObject); + QCOMPARE(object->property("x").toInt(), 100); + QCOMPARE(object->property("y").toInt(), 100); - QCOMPARE(rootObject->objectName(), u"foo"_s); + QVERIFY(object->property("width").toInt() != 200); + QVERIFY(object->property("height").toInt() != 200); + QVERIFY(object->property("qProperty1").toInt() != 300); + QVERIFY(object->property("qProperty2").toInt() != 300); + QTRY_COMPARE(object->property("width").toInt(), 200); + QTRY_COMPARE(object->property("height").toInt(), 200); + QTRY_COMPARE(object->property("qProperty1").toInt(), 300); + QTRY_COMPARE(object->property("qProperty2").toInt(), 300); } -void tst_QmlCppCodegen::ownPropertiesNonShadowable() +void tst_QmlCppCodegen::interestingFiles() { + QFETCH(QString, file); + QFETCH(bool, isValid); + QQmlEngine engine; - QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/overriddenMember.qml"_s)); + QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/%1"_s.arg(file))); + if (isValid) { + QVERIFY2(component.isReady(), qPrintable(component.errorString())); + QScopedPointer<QObject> object(component.create()); + QVERIFY(!object.isNull()); + } else { + QVERIFY(component.isError()); + } +} - QVERIFY2(component.isReady(), qPrintable(component.errorString())); - QScopedPointer<QObject> rootObject(component.create()); - QVERIFY(rootObject); +void tst_QmlCppCodegen::interestingFiles_data() +{ + QTest::addColumn<QString>("file"); + QTest::addColumn<bool>("isValid"); - QCOMPARE(rootObject->property("ppp").toInt(), 16); - QCOMPARE(rootObject->property("ppp2").toInt(), 9); - QCOMPARE(rootObject->property("ppp3").toInt(), 12); + QTest::addRow("conversions2") << u"conversions2.qml"_s << true; + QTest::addRow("TestCase") << u"TestCase.qml"_s << true; + QTest::addRow("layouts") << u"layouts.qml"_s << true; + QTest::addRow("interactive") << u"interactive.qml"_s << true; + QTest::addRow("Panel") << u"Panel.qml"_s << true; + QTest::addRow("ProgressBar") << u"ProgressBar/ProgressBar.ui.qml"_s << true; + QTest::addRow("Root") << u"ProgressBar/Root.qml"_s << true; + QTest::addRow("noscope") << u"noscope.qml"_s << true; + QTest::addRow("dynamicscene") << u"dynamicscene.qml"_s << true; + QTest::addRow("curlygrouped") << u"curlygrouped.qml"_s << true; + QTest::addRow("cycleHead") << u"cycleHead.qml"_s << false; + QTest::addRow("deadStoreLoop") << u"deadStoreLoop.qml"_s << true; + QTest::addRow("moveRegVoid") << u"moveRegVoid.qml"_s << true; } -void tst_QmlCppCodegen::modulePrefix() +void tst_QmlCppCodegen::internalConversion() { QQmlEngine engine; - QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/modulePrefix.qml"_s)); + QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/internalConversion.qml"_s)); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> obj(c.create()); + QVERIFY(!obj.isNull()); - QVERIFY2(component.isReady(), qPrintable(component.errorString())); - QScopedPointer<QObject> rootObject(component.create()); - QVERIFY(rootObject); + QObject *offset = obj->property("offset").value<QObject *>(); + QVERIFY(offset); - QCOMPARE(rootObject->property("foo").toDateTime(), QDateTime(QDate(1911, 3, 4), QTime())); - QCOMPARE(rootObject->property("bar").toDateTime(), QDateTime(QDate(1911, 3, 4), QTime())); - QCOMPARE(rootObject->property("baz").toString(), QStringLiteral("ItIsTheSingleton")); + QCOMPARE(offset->objectName(), "hello"_L1); + QCOMPARE(offset->property("mark").toString(), "hello"_L1); } -void tst_QmlCppCodegen::colorString() +void tst_QmlCppCodegen::invalidPropertyType() { + // Invisible on purpose + qmlRegisterType<MyCppType>("App", 1, 0, "MyCppType"); + QQmlEngine engine; - QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/colorString.qml"_s)); + QQmlComponent okComponent(&engine, QUrl(u"qrc:/qt/qml/TestTypes/OkType.qml"_s)); + QVERIFY2(okComponent.isReady(), qPrintable(okComponent.errorString())); + QScopedPointer<QObject> picker(okComponent.create()); + QVERIFY2(!picker.isNull(), qPrintable(okComponent.errorString())); + QObject *inner = qmlContext(picker.data())->objectForName(u"inner"_s); + QVERIFY(inner); + MyCppType *myCppType = qobject_cast<MyCppType *>(inner); + QVERIFY(myCppType); + QVERIFY(!myCppType->useListDelegate()); - QVERIFY2(component.isReady(), qPrintable(component.errorString())); - QScopedPointer<QObject> rootObject(component.create()); - QVERIFY(rootObject); + QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/BadType.qml"_s)); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> o(c.createWithInitialProperties( + QVariantMap {{u"picker"_s, QVariant::fromValue(picker.data())}})); + QVERIFY2(!o.isNull(), qPrintable(c.errorString())); + QVERIFY(!myCppType->useListDelegate()); - QCOMPARE(qvariant_cast<QColor>(rootObject->property("c")), QColor::fromRgb(0xdd, 0xdd, 0xdd)); - QCOMPARE(qvariant_cast<QColor>(rootObject->property("d")), QColor::fromRgb(0xaa, 0xaa, 0xaa)); - QCOMPARE(qvariant_cast<QColor>(rootObject->property("e")), QColor::fromRgb(0x11, 0x22, 0x33)); + o->setProperty("useListDelegate", QVariant::fromValue<bool>(true)); + QVERIFY(myCppType->useListDelegate()); } -void tst_QmlCppCodegen::urlString() +void tst_QmlCppCodegen::invisibleBase() { QQmlEngine engine; - QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/urlString.qml"_s)); + QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/invisibleBase.qml"_s)); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> o(c.create()); + QVERIFY(o); + QCOMPARE(qvariant_cast<QObject *>(o->property("n")), o.data()); +} - QVERIFY2(component.isReady(), qPrintable(component.errorString())); - QScopedPointer<QObject> rootObject(component.create()); - QVERIFY(rootObject); +void tst_QmlCppCodegen::invisibleListElementType() +{ + qmlRegisterType<InvisibleListElementType>("Invisible", 1, 0, "InvisibleListElement"); + QQmlEngine engine; + QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/invisibleListElementType.qml"_s)); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> o(c.create()); + QVERIFY(!o.isNull()); - QCOMPARE(qvariant_cast<QUrl>(rootObject->property("c")), QUrl(u"http://dddddd.com"_s)); - QCOMPARE(qvariant_cast<QUrl>(rootObject->property("d")), QUrl(u"http://aaaaaa.com"_s)); - QCOMPARE(qvariant_cast<QUrl>(rootObject->property("e")), QUrl(u"http://a112233.de"_s)); + QObject *a = o->property("a").value<QObject *>(); + QVERIFY(a); + + const QVariant x = a->property("x"); + QCOMPARE(x.metaType(), QMetaType::fromType<QQmlListReference>()); + const QQmlListReference ref = x.value<QQmlListReference>(); + QVERIFY(ref.isValid()); + QCOMPARE(ref.size(), 0); } +void tst_QmlCppCodegen::invisibleSingleton() +{ + // We may have seen Style.qml as singleton before, which would make the assignment pass. + // So let's flush the type registry. + qmlClearTypeRegistrations(); -void tst_QmlCppCodegen::callContextPropertyLookupResult() + QQmlEngine engine; + const QUrl copy(u"qrc:/qt/qml/HiddenTestTypes/hidden/Main.qml"_s); + QQmlComponent c(&engine, copy); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + + QTest::ignoreMessage( + QtWarningMsg, + "qrc:/qt/qml/HiddenTestTypes/hidden/Main.qml:4:5: " + "Unable to assign [undefined] to QColor"); + QScopedPointer<QObject> o(c.create()); + QVERIFY(!o.isNull()); + QCOMPARE(o->property("c"), QVariant(QMetaType::fromName("QColor"))); +} + +void tst_QmlCppCodegen::invisibleTypes() { QQmlEngine engine; - QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/callContextPropertyLookupResult.qml"_s)); + QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/invisibleTypes.qml"_s)); QVERIFY2(c.isReady(), qPrintable(c.errorString())); QScopedPointer<QObject> o(c.create()); - QVERIFY(o); - QVERIFY(qvariant_cast<QQmlComponent *>(o->property("c")) != nullptr); + QObject *singleton = qvariant_cast<QObject *>(o->property("singleton")); + QVERIFY(singleton != nullptr); + QCOMPARE(singleton->metaObject()->className(), "SingletonModel"); + + QObject *attached = qvariant_cast<QObject *>(o->property("attached")); + QVERIFY(attached != nullptr); + QCOMPARE(attached->metaObject()->className(), "AttachedAttached"); + +// TODO: This doesn't work in interpreted mode: +// const QMetaObject *meta = qvariant_cast<const QMetaObject *>(o->property("metaobject")); +// QVERIFY(meta != nullptr); +// QCOMPARE(meta->className(), "DerivedFromInvisible"); } -void tst_QmlCppCodegen::deadShoeSize() +void tst_QmlCppCodegen::iteration() { QQmlEngine engine; - QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/deadShoeSize.qml"_s)); + QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/iteration.qml"_s)); QVERIFY2(c.isReady(), qPrintable(c.errorString())); - QTest::ignoreMessage(QtWarningMsg, "qrc:/qt/qml/TestTypes/deadShoeSize.qml:5: Error: ouch"); QScopedPointer<QObject> o(c.create()); - QVERIFY(o); - QCOMPARE(o->property("shoeSize").toInt(), 0); + QVERIFY(!o.isNull()); + + QCOMPARE(o->objectName(), "a345b345c345"_L1); } -void tst_QmlCppCodegen::listIndices() +void tst_QmlCppCodegen::javaScriptArgument() { QQmlEngine engine; - QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/listIndices.qml"_s)); + QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/javaScriptArgument.qml"_s)); QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> o(c.create()); - QVERIFY(o); + QVERIFY(!o.isNull()); - QQmlListReference list(o.data(), "items"); - QCOMPARE(list.count(), 3); - for (int i = 0; i < 3; ++i) - QCOMPARE(list.at(i), o.data()); - QCOMPARE(o->property("numItems").toInt(), 3); - QCOMPARE(qvariant_cast<QObject *>(o->property("fractional")), nullptr); - QCOMPARE(qvariant_cast<QObject *>(o->property("negativeZero")), o.data()); - QCOMPARE(qvariant_cast<QObject *>(o->property("infinity")), nullptr); - QCOMPARE(qvariant_cast<QObject *>(o->property("nan")), nullptr); + QCOMPARE(o->property("a").toDouble(), 4.0); + QCOMPARE(o->property("b").toDouble(), 9.0); + QCOMPARE(o->property("c").toString(), u"5t-1"_s); + QCOMPARE(o->property("d").toString(), u"9"_s); + QCOMPARE(o->property("e").toString(), u"10"_s); + QCOMPARE(o->property("f").toString(), u"-10"_s); + + const QStringList scales { + "0 ", "1 ", "10 ", "100 ", "1000 ", "9.77k", "97.7k", "977k", "9.54M", "95.4M", "954M", + "9.31G", "93.1G", "931G", "9.09T", "-1 ", "-10 ", "-100 ", "-1000 ", "-9.77k", "-97.7k", + "-977k", "-9.54M", "-95.4M", "-954M", "-9.31G", "-93.1G", "-931G", "-9.09T" + }; + + QCOMPARE(o->property("scales").value<QStringList>(), scales); + + double thing = 12.0; + QString result; + QMetaObject::invokeMethod( + o.data(), "forwardArg", Q_RETURN_ARG(QString, result), Q_ARG(double, thing)); + QCOMPARE(result, u"12 "); } -static const double numbers[] = { - qQNaN(), -qInf(), - std::numeric_limits<double>::min(), - std::numeric_limits<float>::min(), - std::numeric_limits<qint32>::min(), - -1000.2, -100, -2, -1.333, -1, -0.84, -0.5, +void tst_QmlCppCodegen::jsArrayMethods() +{ + QQmlEngine engine; + QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/jsArrayMethods.qml"_s)); + QVERIFY2(component.isReady(), qPrintable(component.errorString())); + QScopedPointer<QObject> object(component.create()); + QVERIFY(!object.isNull()); - // -0 and 0 are not different on the QML side. Therefore, don't keep them adjacent. - // Otherwise the bindings won't get re-evaluated. - std::copysign(0.0, -1), 1, 0.0, + QQmlComponent untyped(&engine, QUrl(u"qrc:/qt/qml/TestTypes/jsArrayMethodsUntyped.qml"_s)); + QVERIFY2(untyped.isReady(), qPrintable(untyped.errorString())); + QScopedPointer<QObject> check(untyped.create()); + QVERIFY(!check.isNull()); - 0.5, 0.77, 1.4545, 2, 199, 2002.13, - std::numeric_limits<qint32>::max(), - std::numeric_limits<quint32>::max(), - std::numeric_limits<float>::max(), - std::numeric_limits<double>::max(), - qInf() -}; + check->setProperty("l1", object->property("l1")); + check->setProperty("l2", object->property("l2")); + check->setProperty("l3", object->property("l3")); + + QCOMPARE(object->property("listPropertyToString"), object->property("jsArrayToString")); + QCOMPARE(object->property("listPropertyToString"), check->property("jsArrayToString")); + + QCOMPARE(object->property("listPropertyIncludes"), object->property("jsArrayIncludes")); + QVERIFY(object->property("listPropertyIncludes").toBool()); + + QCOMPARE(object->property("listPropertyJoin"), object->property("jsArrayJoin")); + QCOMPARE(object->property("listPropertyJoin"), check->property("jsArrayJoin")); + QVERIFY(object->property("listPropertyJoin").toString().contains(QStringLiteral("klaus"))); + + QCOMPARE(object->property("listPropertyIndexOf"), object->property("jsArrayIndexOf")); + QCOMPARE(object->property("listPropertyIndexOf").toInt(), 1); + + QCOMPARE(object->property("listPropertyLastIndexOf"), object->property("jsArrayLastIndexOf")); + QCOMPARE(object->property("listPropertyLastIndexOf").toInt(), 5); +} + +void tst_QmlCppCodegen::jsArrayMethodsWithParams() +{ + QFETCH(int, i); + QFETCH(int, j); + QFETCH(int, k); + QQmlEngine engine; + QQmlComponent component + (&engine, QUrl(u"qrc:/qt/qml/TestTypes/jsArrayMethodsWithParams.qml"_s)); + QVERIFY2(component.isReady(), qPrintable(component.errorString())); + QQmlComponent untyped( + &engine, QUrl(u"qrc:/qt/qml/TestTypes/jsArrayMethodsWithParamsUntyped.qml"_s)); + QVERIFY2(untyped.isReady(), qPrintable(component.errorString())); + QScopedPointer<QObject> object(component.createWithInitialProperties({ + {QStringLiteral("i"), i}, + {QStringLiteral("j"), j}, + {QStringLiteral("k"), k} + })); + QVERIFY(!object.isNull()); + QScopedPointer<QObject> check(untyped.createWithInitialProperties({ + {QStringLiteral("i"), i}, + {QStringLiteral("j"), j}, + {QStringLiteral("k"), k} + })); + QVERIFY(!check.isNull()); + check->setProperty("l1", object->property("l1")); + check->setProperty("l2", object->property("l2")); + check->setProperty("l3", object->property("l3")); + + listsEqual(object.data(), object.data(), "Slice"); + listsEqual(object.data(), check.data(), "Slice"); + QCOMPARE(object->property("listPropertyIndexOf"), object->property("jsArrayIndexOf")); + QCOMPARE(object->property("listPropertyIndexOf"), check->property("jsArrayIndexOf")); + QCOMPARE(object->property("listPropertyLastIndexOf"), object->property("jsArrayLastIndexOf")); + QCOMPARE(object->property("listPropertyLastIndexOf"), check->property("jsArrayLastIndexOf")); +} + +void tst_QmlCppCodegen::jsArrayMethodsWithParams_data() +{ + QTest::addColumn<int>("i"); + QTest::addColumn<int>("j"); + QTest::addColumn<int>("k"); + + const int indices[] = { + std::numeric_limits<int>::min(), + -10, -3, -2, -1, 0, 1, 2, 3, 10, + std::numeric_limits<int>::max(), + }; + + // We cannot test the full cross product. So, take a random sample instead. + const qsizetype numIndices = sizeof(indices) / sizeof(int); + qsizetype seed = QRandomGenerator::global()->generate(); + const int numSamples = 4; + for (int i = 0; i < numSamples; ++i) { + seed = qHash(i, seed); + const int vi = indices[qAbs(seed) % numIndices]; + for (int j = 0; j < numSamples; ++j) { + seed = qHash(j, seed); + const int vj = indices[qAbs(seed) % numIndices]; + for (int k = 0; k < numSamples; ++k) { + seed = qHash(k, seed); + const int vk = indices[qAbs(seed) % numIndices]; + const QString tag = QLatin1String("%1/%2/%3").arg( + QString::number(vi), QString::number(vj), QString::number(vk)); + QTest::newRow(qPrintable(tag)) << vi << vj << vk; + + // output all the tags so that we can find out + // what combination caused a test to hang. + qDebug().noquote() << "scheduling" << tag; + } + } + } +} + +void tst_QmlCppCodegen::jsImport() +{ + QQmlEngine engine; + QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/jsimport.qml"_s)); + QVERIFY2(!component.isError(), component.errorString().toUtf8()); + QScopedPointer<QObject> object(component.create()); + QVERIFY(!object.isNull()); + QCOMPARE(object->property("value").toInt(), 42); +} void tst_QmlCppCodegen::jsMathObject() { @@ -1707,708 +2876,1247 @@ void tst_QmlCppCodegen::jsMathObject() qDebug() << name << "failed."; } -void tst_QmlCppCodegen::intEnumCompare() +void tst_QmlCppCodegen::jsmoduleImport() { QQmlEngine engine; - { - QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/intEnumCompare.qml"_s)); - QVERIFY2(c.isReady(), qPrintable(c.errorString())); - QScopedPointer<QObject> o(c.create()); - QVERIFY(o); - QCOMPARE(o->property("a").toBool(), true); - QCOMPARE(o->property("b").toBool(), false); - QCOMPARE(o->property("c").toBool(), true); - QCOMPARE(o->property("d").toBool(), false); - } - - { - // We cannot use Qt.red in QML because it's lower case. - QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/enumInvalid.qml"_s)); - QVERIFY2(c.isReady(), qPrintable(c.errorString())); - QScopedPointer<QObject> o(c.create()); - QVERIFY(o); - QCOMPARE(o->property("c").toBool(), true); - QCOMPARE(o->property("d").toBool(), false); - } + QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/jsmoduleimport.qml"_s)); + QVERIFY2(!component.isError(), component.errorString().toUtf8()); + QScopedPointer<QObject> object(component.create()); + QVERIFY(!object.isNull()); + QCOMPARE(object->property("ok").toBool(), true); + QVariant okFunc = object->property("okFunc"); + QCOMPARE(okFunc.metaType(), QMetaType::fromType<QJSValue>()); + QJSValue val = engine.toScriptValue(okFunc); + QJSValue result = val.call(); + QVERIFY(result.isBool()); + QVERIFY(result.toBool()); } -void tst_QmlCppCodegen::attachedSelf() +void tst_QmlCppCodegen::lengthAccessArraySequenceCompat() { QQmlEngine engine; - QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/SelectionRectangle.qml"_s)); + QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/ArraySequenceLengthInterop.qml"_s)); QVERIFY2(c.isReady(), qPrintable(c.errorString())); QScopedPointer<QObject> o(c.create()); - QVERIFY(o); + QVERIFY(!o.isNull()); + QCOMPARE(o->property("length").toInt(), 100); +} - QObject *handle = qvariant_cast<QObject *>(o->property("aa")); - QVERIFY(handle); - QVERIFY(qvariant_cast<QObject *>(handle->property("rect")) != nullptr); +void tst_QmlCppCodegen::letAndConst() +{ + QQmlEngine engine; + QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/letAndConst.qml"_s)); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> o(c.create()); + QVERIFY(!o.isNull()); + QCOMPARE(o->objectName(), u"ab"_s); } -void tst_QmlCppCodegen::functionReturningVoid() +void tst_QmlCppCodegen::listAsArgument() { QQmlEngine engine; - QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/functionReturningVoid.qml"_s)); + + QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/listAsArgument.qml"_s)); QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> o(c.create()); - QVERIFY(o); + QCOMPARE(o->property("i").toInt(), 4); + QCOMPARE(o->property("j").toInt(), 2); + QCOMPARE(o->property("i1").toInt(), 2); + QCOMPARE(o->property("i2").toInt(), 4); + QCOMPARE(o->property("d").value<QObject *>()->objectName(), u"this one"_s); - // It should be able to call the methods and wrap the void values into invalid QVariants, - // without crashing. - QVERIFY(o->metaObject()->indexOfProperty("aa") >= 0); - QVERIFY(o->metaObject()->indexOfProperty("bb") >= 0); - QVERIFY(!o->property("aa").isValid()); - QVERIFY(!o->property("bb").isValid()); + int singleInt = 0; + QList<int> moreInts; + QMetaObject::invokeMethod(o.data(), "returnInts1", Q_RETURN_ARG(QList<int>, moreInts)); + QCOMPARE(moreInts, QList<int>({5, 4, 3, 2, 1})); + QMetaObject::invokeMethod(o.data(), "selectSecondInt", Q_RETURN_ARG(int, singleInt), Q_ARG(QList<int>, moreInts)); + QCOMPARE(singleInt, 4); } -void tst_QmlCppCodegen::functionCallOnNamespaced() +void tst_QmlCppCodegen::listConversion() { - QQmlEngine engine; - { - QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/themergood.qml"_s)); - QVERIFY2(c.isReady(), qPrintable(c.errorString())); - QScopedPointer<QObject> o(c.create()); - QVERIFY(o); - QCOMPARE(o->property("i").toInt(), 12); - } + QQmlEngine e; + QQmlComponent c(&e, QUrl(u"qrc:/qt/qml/TestTypes/listConversion.qml"_s)); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> o(c.create()); + QVERIFY(!o.isNull()); - { - QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/themerbad.qml"_s)); - QVERIFY2(c.isReady(), qPrintable(c.errorString())); - QScopedPointer<QObject> o(c.create()); - QVERIFY(o); - QCOMPARE(o->property("r"), QVariant::fromValue(QRectF(5.0, 10.0, 1.0, 1.0))); + QQmlListProperty<QObject> list = o->property("o").value<QQmlListProperty<QObject>>(); + QCOMPARE(list.count(&list), 3); + for (int i = 0; i < 3; ++i) { + QObject *entry = list.at(&list, i); + Person *person = qobject_cast<Person *>(entry); + QVERIFY(person); + QCOMPARE(person->name(), u"Horst %1"_s.arg(i + 1)); } + + QStringList strings = o->property("s").value<QStringList>(); + QCOMPARE(strings, QStringList({u"Horst 1"_s, u"Horst 2"_s, u"Horst 3"_s})); + + QVariantList vars = o->property("v").toList(); + QCOMPARE(vars, QVariantList({ + QString(), + QVariant::fromValue<qsizetype>(3), + QVariant::fromValue<Person *>(nullptr) + })); } -void tst_QmlCppCodegen::flushBeforeCapture() +void tst_QmlCppCodegen::listIndices() { QQmlEngine engine; - QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/noBindingLoop.qml"_s)); + QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/listIndices.qml"_s)); QVERIFY2(c.isReady(), qPrintable(c.errorString())); QScopedPointer<QObject> o(c.create()); QVERIFY(o); - QCOMPARE(o->property("deviation").toDouble(), 9.0 / 3.3333); - QCOMPARE(o->property("samples").toInt(), 16); - QCOMPARE(o->property("radius").toDouble(), 8.0); + QQmlListReference list(o.data(), "items"); + QCOMPARE(list.count(), 3); + for (int i = 0; i < 3; ++i) + QCOMPARE(list.at(i), o.data()); + QCOMPARE(o->property("numItems").toInt(), 3); + QCOMPARE(qvariant_cast<QObject *>(o->property("fractional")), nullptr); + QCOMPARE(qvariant_cast<QObject *>(o->property("negativeZero")), o.data()); + QCOMPARE(qvariant_cast<QObject *>(o->property("infinity")), nullptr); + QCOMPARE(qvariant_cast<QObject *>(o->property("nan")), nullptr); } -void tst_QmlCppCodegen::unknownAttached() +void tst_QmlCppCodegen::listLength() { QQmlEngine engine; - QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/unknownAttached.qml"_s)); - QVERIFY(c.isError()); + QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/listlength.qml"_s)); + QVERIFY2(component.isReady(), component.errorString().toUtf8()); + QScopedPointer<QObject> object(component.create()); + QVERIFY(!object.isNull()); + QCOMPARE(object->property("l").toInt(), 2); } -void tst_QmlCppCodegen::variantlist() +void tst_QmlCppCodegen::listOfInvisible() { QQmlEngine engine; - QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/variantlist.qml"_s)); - QVERIFY2(c.isReady(), qPrintable(c.errorString())); - QScopedPointer<QObject> o(c.create()); - QVERIFY(o); - - const QVariantList things = qvariant_cast<QVariantList>(o->property("things")); - QCOMPARE(things.size(), 2); - QCOMPARE(things[0].toString(), u"thing"_s); - QCOMPARE(things[1].toInt(), 30); + QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/listOfInvisible.qml"_s)); + QVERIFY2(component.isReady(), component.errorString().toUtf8()); + QScopedPointer<QObject> object(component.create()); + QVERIFY(!object.isNull()); + QCOMPARE(object->property("width").toDouble(), 27.0); } -void tst_QmlCppCodegen::popContextAfterRet() +void tst_QmlCppCodegen::listPropertyAsModel() { QQmlEngine engine; - QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/popContextAfterRet.qml"_s)); + QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/listPropertyAsModel.qml"_s)); QVERIFY2(c.isReady(), qPrintable(c.errorString())); QScopedPointer<QObject> o(c.create()); - QVERIFY(o); - QCOMPARE(o->objectName(), QString()); - o->setProperty("stackViewDepth", 1); - QCOMPARE(o->objectName(), u"backgroundImage"_s); - o->setProperty("stackViewDepth", 2); - QCOMPARE(o->objectName(), u"backgroundBlur"_s); - o->setProperty("stackViewDepth", 1); - QCOMPARE(o->objectName(), u"backgroundImage"_s); + QQmlListReference children(o.data(), "children"); + QCOMPARE(children.count(), 5); } -void tst_QmlCppCodegen::revisions() +void tst_QmlCppCodegen::listToString() { QQmlEngine engine; - QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/revisions.qml"_s)); + QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/listToString.qml"_s)); QVERIFY2(c.isReady(), qPrintable(c.errorString())); + + QTest::ignoreMessage(QtDebugMsg, "[one,two]"); + QTest::ignoreMessage(QtDebugMsg, "one,two"); + QTest::ignoreMessage(QtDebugMsg, "[1,2]"); + QTest::ignoreMessage(QtDebugMsg, "1,2"); + QTest::ignoreMessage( + QtDebugMsg, + QRegularExpression("\\[QObject_QML_[0-9]+\\(0x[0-9a-f]+\\)," + "QObject_QML_[0-9]+\\(0x[0-9a-f]+\\)\\]")); + QTest::ignoreMessage( + QtDebugMsg, + QRegularExpression("QObject_QML_[0-9]+\\(0x[0-9a-f]+\\)," + "QObject_QML_[0-9]+\\(0x[0-9a-f]+\\)")); + + QTest::ignoreMessage(QtDebugMsg, "[a,b]"); + QScopedPointer<QObject> o(c.create()); - QVERIFY(o); +} - QCOMPARE(o->property("delayed").toBool(), true); - QCOMPARE(o->property("gotten").toInt(), 5); +void tst_QmlCppCodegen::lotsOfRegisters() +{ + QQmlEngine engine; + QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/page.qml"_s)); + QVERIFY2(!component.isError(), component.errorString().toUtf8()); + QScopedPointer<QObject> object(component.create()); + QVERIFY(!object.isNull()); + + const auto compare = [&]() { + const qreal implicitBackgroundWidth = object->property("implicitBackgroundWidth").toDouble(); + const qreal leftInset = object->property("leftInset").toDouble(); + const qreal rightInset = object->property("rightInset").toDouble(); + const qreal contentWidth = object->property("contentWidth").toDouble(); + const qreal leftPadding = object->property("leftPadding").toDouble(); + const qreal rightPadding = object->property("rightPadding").toDouble(); + const qreal implicitFooterWidth = object->property("implicitFooterWidth").toDouble(); + const qreal implicitHeaderWidth = object->property("implicitHeaderWidth").toDouble(); + + const qreal implicitWidth = object->property("implicitWidth").toDouble(); + QCOMPARE(implicitWidth, qMax(qMax(implicitBackgroundWidth + leftInset + rightInset, + contentWidth + leftPadding + rightPadding), + qMax(implicitHeaderWidth, implicitFooterWidth))); + }; + + compare(); + + const QList<const char *> props = { + "leftInset", "rightInset", "contentWidth", "leftPadding", "rightPadding" + }; + + for (int i = 0; i < 100; ++i) { + QVERIFY(object->setProperty(props[i % props.size()], (i * 17) % 512)); + compare(); + } } -void tst_QmlCppCodegen::invisibleBase() +void tst_QmlCppCodegen::math() { QQmlEngine engine; - QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/invisibleBase.qml"_s)); - QVERIFY2(c.isReady(), qPrintable(c.errorString())); - QScopedPointer<QObject> o(c.create()); - QVERIFY(o); - QCOMPARE(qvariant_cast<QObject *>(o->property("n")), o.data()); + QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/math.qml"_s)); + QVERIFY2(!component.isError(), component.errorString().toUtf8()); + QScopedPointer<QObject> object(component.create()); + QVERIFY(!object.isNull()); + QCOMPARE(object->property("a").toInt(), 9); + QCOMPARE(object->property("b").toDouble(), 50.0 / 22.0); + QCOMPARE(object->property("c").toDouble(), std::atan(1.0) * 8.0); } -void tst_QmlCppCodegen::notEqualsInt() +void tst_QmlCppCodegen::mathMinMax() { QQmlEngine engine; - QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/notEqualsInt.qml"_s)); + QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/mathMinMax.qml"_s)); QVERIFY2(c.isReady(), qPrintable(c.errorString())); + + // Math.max() + QTest::ignoreMessage(QtDebugMsg, "1"); + QTest::ignoreMessage(QtDebugMsg, "2"); + QTest::ignoreMessage(QtDebugMsg, "2"); + QTest::ignoreMessage(QtDebugMsg, "0"); + QTest::ignoreMessage(QtDebugMsg, "0"); + QTest::ignoreMessage(QtDebugMsg, "0"); + QTest::ignoreMessage(QtDebugMsg, "-1"); + + QTest::ignoreMessage(QtDebugMsg, "0"); + QTest::ignoreMessage(QtDebugMsg, "1"); + QTest::ignoreMessage(QtDebugMsg, "2"); + QTest::ignoreMessage(QtDebugMsg, "2"); + QTest::ignoreMessage(QtDebugMsg, "9"); + + QTest::ignoreMessage(QtDebugMsg, "0"); + QTest::ignoreMessage(QtDebugMsg, "0.002"); + QTest::ignoreMessage(QtDebugMsg, "5.4"); + QTest::ignoreMessage(QtDebugMsg, "NaN"); + QTest::ignoreMessage(QtDebugMsg, "Infinity"); + QTest::ignoreMessage(QtDebugMsg, "1"); + QTest::ignoreMessage(QtDebugMsg, "0.08"); + QTest::ignoreMessage(QtDebugMsg, "Infinity"); + QTest::ignoreMessage(QtDebugMsg, "0"); + QTest::ignoreMessage(QtDebugMsg, "NaN"); + + // Math.min() + QTest::ignoreMessage(QtDebugMsg, "1"); + QTest::ignoreMessage(QtDebugMsg, "1"); + QTest::ignoreMessage(QtDebugMsg, "1"); + QTest::ignoreMessage(QtDebugMsg, "0"); + QTest::ignoreMessage(QtDebugMsg, "-1"); + QTest::ignoreMessage(QtDebugMsg, "-1"); + QTest::ignoreMessage(QtDebugMsg, "-1"); + + QTest::ignoreMessage(QtDebugMsg, "0"); + QTest::ignoreMessage(QtDebugMsg, "0"); + QTest::ignoreMessage(QtDebugMsg, "-2"); + QTest::ignoreMessage(QtDebugMsg, "-2"); + QTest::ignoreMessage(QtDebugMsg, "0"); + + QTest::ignoreMessage(QtDebugMsg, "0"); + QTest::ignoreMessage(QtDebugMsg, "-0.001"); + QTest::ignoreMessage(QtDebugMsg, "0.002"); + QTest::ignoreMessage(QtDebugMsg, "NaN"); + QTest::ignoreMessage(QtDebugMsg, "-1"); + QTest::ignoreMessage(QtDebugMsg, "-1"); + QTest::ignoreMessage(QtDebugMsg, "-1"); + QTest::ignoreMessage(QtDebugMsg, "-1"); + QTest::ignoreMessage(QtDebugMsg, "-8"); + QTest::ignoreMessage(QtDebugMsg, "NaN"); + QScopedPointer<QObject> o(c.create()); - QVERIFY(o); - QObject *t = qmlContext(o.data())->objectForName(u"t"_s); - QVERIFY(t); - QCOMPARE(t->property("text").toString(), u"Foo"_s); - QMetaObject::invokeMethod(o.data(), "foo"); - QCOMPARE(t->property("text").toString(), u"Bar"_s); + QVERIFY(!o.isNull()); } -void tst_QmlCppCodegen::infinities() +void tst_QmlCppCodegen::mathOperations() { QQmlEngine engine; - QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/infinities.qml"_s)); + QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/mathOperations.qml"_s)); QVERIFY2(c.isReady(), qPrintable(c.errorString())); QScopedPointer<QObject> o(c.create()); - QVERIFY(o); - QCOMPARE(o->property("positiveInfinity").toDouble(), std::numeric_limits<double>::infinity()); - QCOMPARE(o->property("negativeInfinity").toDouble(), -std::numeric_limits<double>::infinity()); + const QMetaObject *metaObject = o->metaObject(); - const double positiveZero = o->property("positiveZero").toDouble(); - QCOMPARE(positiveZero, 0.0); - QVERIFY(!std::signbit(positiveZero)); + char t1; + char t2; + QString name; + const auto guard = qScopeGuard([&]() { + if (QTest::currentTestFailed()) { + qDebug() << t1 << t2 << name << "failed on:"; + qDebug() << "doubles" << o->property("a").toDouble() << o->property("b").toDouble(); + qDebug() << "integers" << o->property("ia").toInt() << o->property("ib").toInt(); + qDebug() << "booleans" << o->property("ba").toBool() << o->property("bb").toBool(); + } + }); - const double negativeZero = o->property("negativeZero").toDouble(); - QCOMPARE(negativeZero, -0.0); - QVERIFY(std::signbit(negativeZero)); + for (double a : numbers) { + for (double b : numbers) { + o->setProperty("a", a); + o->setProperty("b", b); + for (int i = 0, end = metaObject->propertyCount(); i != end; ++i) { + const QMetaProperty prop = metaObject->property(i); + const QByteArray propName = prop.name(); - QVERIFY(qIsNaN(o->property("naN").toDouble())); + if (propName.size() < 3 || propName == "objectName") + continue; + + t1 = propName[0]; + t2 = propName[1]; + name = QString::fromUtf8(propName.mid(2)); + + double expected; + + switch (t2) { + case 'd': + case '_': + switch (t1) { + case 'd': + expected = jsEval<double, double>(a, b, name, &engine); + break; + case 'i': + expected = jsEval<int, double>(a, b, name, &engine); + break; + case 'b': + expected = jsEval<bool, double>(a, b, name, &engine); + break; + } + break; + case 'i': + switch (t1) { + case 'd': + expected = jsEval<double, int>(a, b, name, &engine); + break; + case 'i': + expected = jsEval<int, int>(a, b, name, &engine); + break; + case 'b': + expected = jsEval<bool, int>(a, b, name, &engine); + break; + } + break; + case 'b': + switch (t1) { + case 'd': + expected = jsEval<double, bool>(a, b, name, &engine); + break; + case 'i': + expected = jsEval<int, bool>(a, b, name, &engine); + break; + case 'b': + expected = jsEval<bool, bool>(a, b, name, &engine); + break; + } + break; + } + + const double result = prop.read(o.data()).toDouble(); + QCOMPARE(result, expected); + } + } + } } -void tst_QmlCppCodegen::blockComments() +void tst_QmlCppCodegen::mathStaticProperties() { QQmlEngine engine; - QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/blockComments.qml"_s)); - QVERIFY2(c.isReady(), qPrintable(c.errorString())); - QScopedPointer<QObject> o(c.create()); - QVERIFY(o); - QCOMPARE(o->property("implicitHeight").toDouble(), 8.0); + QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/mathStaticProperties.qml"_s)); + QVERIFY2(!component.isError(), component.errorString().toUtf8()); + QScopedPointer<QObject> object(component.create()); + QVERIFY(!object.isNull()); + + // Approximate values based on + // https://262.ecma-international.org/14.0/#sec-value-properties-of-the-math-object + QCOMPARE(object->property("e").toDouble(), 2.7182818284590452354); + QCOMPARE(object->property("ln10").toDouble(), 2.302585092994046); + QCOMPARE(object->property("ln2").toDouble(), 0.6931471805599453); + QCOMPARE(object->property("log10e").toDouble(), 0.4342944819032518); + QCOMPARE(object->property("log2e").toDouble(), 1.4426950408889634); + QCOMPARE(object->property("pi").toDouble(), 3.1415926535897932); + QCOMPARE(object->property("sqrt1_2").toDouble(), 0.7071067811865476); + QCOMPARE(object->property("sqrt2").toDouble(), 1.4142135623730951); } -void tst_QmlCppCodegen::functionLookup() +void tst_QmlCppCodegen::mergedObjectReadWrite() +{ + QQmlEngine e; + { + QQmlComponent c(&e, QUrl(u"qrc:/qt/qml/TestTypes/mergedObjectRead.qml"_s)); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QTest::ignoreMessage(QtDebugMsg, "null"); + QTest::ignoreMessage( + QtWarningMsg, QRegularExpression("TypeError: Cannot read property 'x' of null")); + QScopedPointer<QObject> o(c.create()); + QVERIFY(!o.isNull()); + } + + { + QQmlComponent c(&e, QUrl(u"qrc:/qt/qml/TestTypes/mergedObjectWrite.qml"_s)); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QTest::ignoreMessage( + QtWarningMsg, + QRegularExpression( + "TypeError: Value is null and could not be converted to an object")); + QScopedPointer<QObject> o(c.create()); + QVERIFY(!o.isNull()); + } +} + +void tst_QmlCppCodegen::methodOnListLookup() { QQmlEngine engine; - QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/functionLookup.qml"_s)); - QVERIFY2(c.isReady(), qPrintable(c.errorString())); - QScopedPointer<QObject> o(c.create()); - QVERIFY(o); - const QVariant foo = o->property("bar"); - QCOMPARE(foo.metaType(), QMetaType::fromType<QJSValue>()); - const QJSManagedValue method(engine.toScriptValue(foo), &engine); - QVERIFY(method.isFunction()); - const QJSValue result = method.call(); - QVERIFY(result.isString()); - QCOMPARE(result.toString(), QStringLiteral("a99")); + const QUrl url(u"qrc:/qt/qml/TestTypes/methodOnListLookup.qml"_s); + QQmlComponent component(&engine, url); + QVERIFY2(component.isReady(), qPrintable(component.errorString())); + + QScopedPointer<QObject> o(component.create()); + QVERIFY(!o.isNull()); + + QCOMPARE(o->objectName(), u"no one"); + + QTest::ignoreMessage( + QtWarningMsg, + qPrintable(url.toString() + + ":14: TypeError: Cannot call method 'getName' of undefined"_L1)); + QMetaObject::invokeMethod(o.data(), "boom"); } -void tst_QmlCppCodegen::objectInVar() +void tst_QmlCppCodegen::methods() { QQmlEngine engine; - QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/objectInVar.qml"_s)); - QVERIFY2(c.isReady(), qPrintable(c.errorString())); - QScopedPointer<QObject> o(c.create()); - QVERIFY(o); - QCOMPARE(qvariant_cast<QObject*>(o->property("thing")), o.data()); + QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/methods.qml"_s)); + QVERIFY(component.isReady()); - bool result = false; - QVERIFY(QMetaObject::invokeMethod(o.data(), "doThing", Q_RETURN_ARG(bool, result))); - QVERIFY(result); + QTest::ignoreMessage(QtDebugMsg, "The Bar"); + QTest::ignoreMessage(QtWarningMsg, QRegularExpression(u"TypeError: .* is not a function"_s)); + QScopedPointer<QObject> obj(component.create()); + QVERIFY(obj); + BirthdayParty *party(qobject_cast<BirthdayParty *>(obj.data())); - o->setProperty("thing", QVariant::fromValue<std::nullptr_t>(nullptr)); - QVERIFY(QMetaObject::invokeMethod(o.data(), "doThing", Q_RETURN_ARG(bool, result))); - QVERIFY(!result); + QVERIFY(party && party->host()); + QCOMPARE(party->guestCount(), 5); + + bool foundGreen = false; + bool foundFoo = false; + for (int ii = 0; ii < party->guestCount(); ++ii) { + if (party->guest(ii)->name() == u"William Green"_s) + foundGreen = true; + if (party->guest(ii)->name() == u"The Foo"_s) + foundFoo = true; + } + + QVERIFY(foundGreen); + QVERIFY(foundFoo); + + QCOMPARE(obj->property("n1").toString(), u"onGurk"_s); + QCOMPARE(obj->property("n2").toString(), u"onSemmeln"_s); + QCOMPARE(obj->property("n3"), QVariant()); + + { + QVariant ret; + obj->metaObject()->invokeMethod(obj.data(), "retrieveVar", Q_RETURN_ARG(QVariant, ret)); + QCOMPARE(ret.typeId(), QMetaType::QString); + QCOMPARE(ret.toString(), u"Jack Smith"_s); + } + + { + QString ret; + obj->metaObject()->invokeMethod(obj.data(), "retrieveString", Q_RETURN_ARG(QString, ret)); + QCOMPARE(ret, u"Jack Smith"_s); + } + + QCOMPARE(party->host()->shoeSize(), 12); + obj->metaObject()->invokeMethod(obj.data(), "storeElement"); + QCOMPARE(party->host()->shoeSize(), 13); + QJSManagedValue v = engine.toManagedValue(obj->property("dresses")); + QVERIFY(v.isArray()); + + QJSManagedValue inner(v.property(2), &engine); + QVERIFY(inner.isArray()); + QCOMPARE(inner.property(0).toInt(), 1); + QCOMPARE(inner.property(1).toInt(), 2); + QCOMPARE(inner.property(2).toInt(), 3); + + QCOMPARE(obj->property("enumValue").toInt(), 2); } -void tst_QmlCppCodegen::functionTakingVar() +void tst_QmlCppCodegen::modulePrefix() { QQmlEngine engine; - const QUrl document(u"qrc:/qt/qml/TestTypes/functionTakingVar.qml"_s); - QQmlComponent c(&engine, document); - QVERIFY2(c.isReady(), qPrintable(c.errorString())); - QScopedPointer<QObject> o(c.create()); - QVERIFY(o); + QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/modulePrefix.qml"_s)); - QVERIFY(!o->property("c").isValid()); + QVERIFY2(component.isReady(), qPrintable(component.errorString())); + QScopedPointer<QObject> rootObject(component.create()); + QVERIFY(rootObject); - int value = 11; - QQmlEnginePrivate *e = QQmlEnginePrivate::get(&engine); - void *args[] = { nullptr, reinterpret_cast<void *>(std::addressof(value)) }; - QMetaType types[] = { QMetaType::fromType<void>(), QMetaType::fromType<std::decay_t<int>>() }; - e->executeRuntimeFunction(document, 0, o.data(), 1, args, types); + QCOMPARE(rootObject->property("foo").toDateTime(), QDateTime(QDate(1911, 3, 4), QTime())); + QCOMPARE(rootObject->property("bar").toDateTime(), QDateTime(QDate(1911, 3, 4), QTime())); + QCOMPARE(rootObject->property("baz").toString(), QStringLiteral("ItIsTheSingleton")); +} - QCOMPARE(o->property("c"), QVariant::fromValue<int>(11)); +void tst_QmlCppCodegen::multiDirectory_data() +{ + QTest::addColumn<QUrl>("url"); + QTest::addRow("from qt_add_qml_module") + << QUrl(u"qrc:/qt/qml/TestTypes/extra/extra.qml"_s); +#ifndef VERY_OLD_CMAKE + QTest::addRow("from qt_target_qml_sources") + << QUrl(u"qrc:/qt/qml/TestTypes/extra2/extra.qml"_s); +#endif } -void tst_QmlCppCodegen::testIsnan() +void tst_QmlCppCodegen::multiDirectory() { + QFETCH(QUrl, url); QQmlEngine engine; - const QUrl document(u"qrc:/qt/qml/TestTypes/isnan.qml"_s); - QQmlComponent c(&engine, document); + QQmlComponent component(&engine, url); + QVERIFY2(component.isReady(), qPrintable(component.errorString())); + QScopedPointer<QObject> rootObject(component.create()); + QVERIFY(rootObject); + + QCOMPARE(rootObject->property("r").value<QRectF>(), QRectF(4, 6, 8, 10)); +} + +void tst_QmlCppCodegen::multiForeign() +{ + QQmlEngine engine; + QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/multiforeign.qml"_s)); QVERIFY2(c.isReady(), qPrintable(c.errorString())); QScopedPointer<QObject> o(c.create()); - QVERIFY(o); + QVERIFY(!o.isNull()); + QCOMPARE(o->objectName(), u"not here and not there"_s); +} - QCOMPARE(o->property("good").toDouble(), 10.1); - QVERIFY(qIsNaN(o->property("bad").toDouble())); +void tst_QmlCppCodegen::multiLookup() +{ + // Multiple lookups of singletons (Qt in this case) don't clash with one another. + QQmlEngine engine; + QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/immediateQuit.qml"_s)); + QVERIFY2(!component.isError(), component.errorString().toUtf8()); - const QVariant a = o->property("a"); - QCOMPARE(a.metaType(), QMetaType::fromType<bool>()); - QVERIFY(!a.toBool()); + const QByteArray message = QByteArray("End: ") + arg1(); + QTest::ignoreMessage(QtDebugMsg, message.constData()); - const QVariant b = o->property("b"); - QCOMPARE(b.metaType(), QMetaType::fromType<bool>()); - QVERIFY(b.toBool()); + QSignalSpy quitSpy(&engine, &QQmlEngine::quit); + QScopedPointer<QObject> object(component.create()); + QVERIFY(!object.isNull()); + QCOMPARE(quitSpy.size(), 1); } -void tst_QmlCppCodegen::fallbackLookups() +void tst_QmlCppCodegen::multipleCtors() { QQmlEngine engine; - const QUrl document(u"qrc:/qt/qml/TestTypes/fallbacklookups.qml"_s); - QQmlComponent c(&engine, document); + QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/multipleCtors.qml"_s)); QVERIFY2(c.isReady(), qPrintable(c.errorString())); QScopedPointer<QObject> o(c.create()); - QVERIFY(o); + QVERIFY(!o.isNull()); + QCOMPARE(o->property("wr").value<ValueTypeWithLength>().length(), 3); + QCOMPARE(o->property("wp").value<ValueTypeWithLength>().length(), 11); + QCOMPARE(o->property("wi").value<ValueTypeWithLength>().length(), 17); +} - QCOMPARE(o->objectName(), QString()); - int result = 0; +void tst_QmlCppCodegen::namespaceWithEnum() +{ + QQmlEngine engine; + QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/namespaceWithEnum.qml"_s)); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> o(c.create()); + QVERIFY(!o.isNull()); + QCOMPARE(o->property("i").toInt(), 2); +} - QMetaObject::invokeMethod(o.data(), "withContext", Q_RETURN_ARG(int, result)); - QCOMPARE(result, 16); - QCOMPARE(o->objectName(), QStringLiteral("aa93")); +void tst_QmlCppCodegen::noQQmlData() +{ + QQmlEngine engine; + QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/noQQmlData.qml"_s)); + QVERIFY2(component.isReady(), component.errorString().toUtf8()); - QMetaObject::invokeMethod(o.data(), "withId", Q_RETURN_ARG(int, result)); - QCOMPARE(result, 17); - QCOMPARE(o->objectName(), QStringLiteral("bb94")); + QTest::ignoreMessage(QtWarningMsg, "qrc:/qt/qml/TestTypes/noQQmlData.qml:7: TypeError: " + "Cannot read property 'name' of null"); + QScopedPointer<QObject> root(component.create()); + QVERIFY(!root.isNull()); - QObject *singleton = nullptr; - QMetaObject::invokeMethod(o.data(), "getSingleton", Q_RETURN_ARG(QObject*, singleton)); - QVERIFY(singleton); + BirthdayParty *party = qobject_cast<BirthdayParty *>(root.data()); + QVERIFY(party != nullptr); - QMetaObject::invokeMethod(o.data(), "withSingleton", Q_RETURN_ARG(int, result)); - QCOMPARE(result, 18); - QCOMPARE(singleton->objectName(), QStringLiteral("cc95")); + QCOMPARE(party->host(), nullptr); + QCOMPARE(party->property("n").toString(), QString()); - QMetaObject::invokeMethod(o.data(), "withProperty", Q_RETURN_ARG(int, result)); - QCOMPARE(result, 19); - QCOMPARE(singleton->objectName(), QStringLiteral("dd96")); + Person *host1 = new Person(party); + party->setHost(host1); + QCOMPARE(party->property("n").toString(), u"Bart in da house!"_s); + host1->setName(u"Marge"_s); + QCOMPARE(party->property("n").toString(), u"Marge in da house!"_s); + + QTest::ignoreMessage(QtWarningMsg, "qrc:/qt/qml/TestTypes/noQQmlData.qml:7: TypeError: " + "Cannot read property 'name' of null"); + + // Doesn't crash + party->setHost(nullptr); + + // Lookups are initialized now, and we introduce an object without QQmlData + Person *host2 = new Person(party); + party->setHost(host2); + QCOMPARE(party->property("n").toString(), u"Bart in da house!"_s); + host2->setName(u"Homer"_s); + QCOMPARE(party->property("n").toString(), u"Homer in da house!"_s); + + QMetaObject::invokeMethod(party, "burn"); + engine.collectGarbage(); + + // Does not crash + party->setProperty("inDaHouse", u" burns!"_s); + + // Mr Burns may or may not burn, depending on whether we use lookups. + // If using lookups, the binding is aborted when we find the isQueuedForDeletion flag. + // If reading the property directly, we don't have to care about it. + QVERIFY(party->property("n").toString().startsWith(u"Mr Burns"_s)); } -void tst_QmlCppCodegen::typedArray() +void tst_QmlCppCodegen::nonNotifyable() { QQmlEngine engine; - const QUrl document(u"qrc:/qt/qml/TestTypes/typedArray.qml"_s); - QQmlComponent c(&engine, document); + QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/nonNotifyable.qml"_s)); + QVERIFY2(!component.isError(), component.errorString().toUtf8()); + QScopedPointer<QObject> object(component.create()); + QVERIFY(!object.isNull()); + + QCOMPARE(qvariant_cast<QDateTime>(object->property("dayz")), + QDateTime(QDate(2121, 1, 12), QTime())); + QCOMPARE(qvariant_cast<QDateTime>(object->property("oParty")), + QDateTime(QDate(2111, 12, 11), QTime())); +} + +void tst_QmlCppCodegen::notEqualsInt() +{ + QQmlEngine engine; + QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/notEqualsInt.qml"_s)); QVERIFY2(c.isReady(), qPrintable(c.errorString())); QScopedPointer<QObject> o(c.create()); QVERIFY(o); - QDateTime date; - QVERIFY(qvariant_cast<QList<int>>(o->property("values2")).isEmpty()); - QCOMPARE(qvariant_cast<QList<int>>(o->property("values3")), - QList<int>({1, 2, 3, 4})); - QCOMPARE(qvariant_cast<QList<QDateTime>>(o->property("values4")), - QList<QDateTime>({date, date, date})); - QCOMPARE(qvariant_cast<QList<double>>(o->property("values5")), - QList<double>({1, 2, 3.4, 30, 0, 0})); - date = QDateTime::currentDateTime(); - o->setProperty("aDate", date); - QCOMPARE(qvariant_cast<QList<QDateTime>>(o->property("values4")), - QList<QDateTime>({date, date, date})); + QObject *t = qmlContext(o.data())->objectForName(u"t"_s); + QVERIFY(t); + QCOMPARE(t->property("text").toString(), u"Foo"_s); + QMetaObject::invokeMethod(o.data(), "foo"); + QCOMPARE(t->property("text").toString(), u"Bar"_s); +} - QQmlListProperty<QObject> values6 - = qvariant_cast<QQmlListProperty<QObject>>(o->property("values6")); - QCOMPARE(values6.count(&values6), 3); - for (int i = 0; i < 3; ++i) - QCOMPARE(values6.at(&values6, i), o.data()); +void tst_QmlCppCodegen::notNotString() +{ + QQmlEngine engine; + QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/notNotString.qml"_s)); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> o(c.create()); - QCOMPARE(o->property("inIntList").toInt(), 2); - QCOMPARE(qvariant_cast<QDateTime>(o->property("inDateList")), date); - QCOMPARE(o->property("inRealList").toDouble(), 30.0); - QCOMPARE(o->property("inCharList").toString(), QStringLiteral("f")); + QCOMPARE(o->property("notNotString").value<bool>(), false); + o->setObjectName(u"a"_s); + QCOMPARE(o->property("notNotString").value<bool>(), true); +} - const QMetaObject *metaObject = o->metaObject(); - QMetaMethod method = metaObject->method(metaObject->indexOfMethod("stringAt10(QString)")); - QVERIFY(method.isValid()); +void tst_QmlCppCodegen::nullAccess() +{ + QQmlEngine engine; + QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/nullAccess.qml"_s)); + QVERIFY2(!component.isError(), component.errorString().toUtf8()); - // If LoadElement threw an exception the function would certainly return neither 10 nor 20. - int result = 0; - method.invoke( - o.data(), Q_RETURN_ARG(int, result), Q_ARG(QString, QStringLiteral("a"))); - QCOMPARE(result, 10); - method.invoke( - o.data(), Q_RETURN_ARG(int, result), Q_ARG(QString, QStringLiteral("aaaaaaaaaaa"))); - QCOMPARE(result, 20); + QTest::ignoreMessage(QtWarningMsg, + "qrc:/qt/qml/TestTypes/nullAccess.qml:4:5: TypeError: " + "Cannot read property 'width' of null"); + QTest::ignoreMessage(QtWarningMsg, + "qrc:/qt/qml/TestTypes/nullAccess.qml:5:5: TypeError: " + "Cannot read property 'height' of null"); + QTest::ignoreMessage(QtWarningMsg, + "qrc:/qt/qml/TestTypes/nullAccess.qml:6: TypeError: Value is null and " + "could not be converted to an object"); + QScopedPointer<QObject> object(component.create()); + + QCOMPARE(object->property("width").toDouble(), 0.0); + QCOMPARE(object->property("height").toDouble(), 0.0); } -void tst_QmlCppCodegen::prefixedType() +void tst_QmlCppCodegen::nullAccessInsideSignalHandler() { QQmlEngine engine; + QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/nullAccessInsideSignalHandler.qml"_s)); + QVERIFY2(!component.isError(), component.errorString().toUtf8()); + QTest::ignoreMessage(QtWarningMsg, + "qrc:/qt/qml/TestTypes/nullAccessInsideSignalHandler.qml:15: ReferenceError: " + "text is not defined"); + QScopedPointer<QObject> object(component.create()); + QSignalSpy spy(object.data(), SIGNAL(say_hello())); + QTRY_VERIFY(spy.size() > 0); +} - // We need to add an import path here because we cannot namespace the implicit import. - // The implicit import is what we use for all the other tests, even if we explicitly - // import TestTypes. That is because the TestTypes module is in a subdirectory "data". - engine.addImportPath(u":/"_s); - const QUrl document(u"qrc:/qt/qml/TestTypes/prefixedMetaType.qml"_s); - QQmlComponent c(&engine, document); +void tst_QmlCppCodegen::nullComparison() +{ + QQmlEngine engine; + + QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/nullComparison.qml"_s)); QVERIFY2(c.isReady(), qPrintable(c.errorString())); QScopedPointer<QObject> o(c.create()); - QVERIFY(o); + QVERIFY(!o.isNull()); - QCOMPARE(o->property("state").toInt(), 2); - QVERIFY(qvariant_cast<QObject *>(o->property("a")) != nullptr); - QVERIFY(qvariant_cast<QObject *>(o->property("b")) != nullptr); - QVERIFY(qvariant_cast<QObject *>(o->property("c")) == nullptr); + QCOMPARE(o->property("v").toInt(), 1); + QCOMPARE(o->property("w").toInt(), 3); + QCOMPARE(o->property("x").toInt(), 1); + QCOMPARE(o->property("y").toInt(), 5); + QCOMPARE(o->property("z").toInt(), 18); +} - QVERIFY(qvariant_cast<QObject *>(o->property("d")) != nullptr); - QVERIFY(qvariant_cast<QObject *>(o->property("e")) != nullptr); - QVERIFY(qvariant_cast<QObject *>(o->property("f")) == nullptr); +void tst_QmlCppCodegen::nullishCoalescing_data() +{ + QTest::addColumn<QString>("propertyName"); + QTest::addColumn<QVariant>("expected"); - QVERIFY(qvariant_cast<QObject *>(o->property("g")) != nullptr); - QVERIFY(qvariant_cast<QObject *>(o->property("h")) != nullptr); + const auto undefinedValue = QVariant(); + const auto nullValue = QVariant::fromMetaType(QMetaType::fromType<std::nullptr_t>(), nullptr); - QCOMPARE(o->property("countG").toInt(), 11); - QCOMPARE(o->property("countH").toInt(), 11); -} + QTest::addRow("trivial-good-int") << "p1" << QVariant(5); + QTest::addRow("trivial-good-string") << "p2" << QVariant("6"); -void tst_QmlCppCodegen::evadingAmbiguity() -{ - QQmlEngine engine; + QTest::addRow("trivial-bad-undefined-undefined") << "p3" << undefinedValue; + QTest::addRow("trivial-bad-undefined-null") << "p4" << nullValue; + QTest::addRow("trivial-bad-undefined-int") << "p5" << QVariant(-1); + QTest::addRow("trivial-bad-undefined-string") << "p6" << QVariant("-1"); - // We need to add an import path here because we cannot namespace the implicit import. - // The implicit import is what we use for all the other tests, even if we explicitly - // import TestTypes. That is because the TestTypes module is in a subdirectory "data". - engine.addImportPath(u":/"_s); + QTest::addRow("trivial-bad-null-undefined") << "p7" << undefinedValue; + QTest::addRow("trivial-bad-null-null") << "p8" << nullValue; + QTest::addRow("trivial-bad-null-int") << "p9" << QVariant(-1); + QTest::addRow("trivial-bad-null-string") << "p10" << QVariant("-1"); - QQmlComponent c1(&engine, QUrl(u"qrc:/qt/qml/TestTypes/ambiguous1/Ambiguous.qml"_s)); - QVERIFY2(c1.isReady(), qPrintable(c1.errorString())); - QScopedPointer<QObject> o1(c1.create()); - QCOMPARE(o1->objectName(), QStringLiteral("Ambiguous")); - QCOMPARE(o1->property("i").toString(), QStringLiteral("Ambiguous1")); + QTest::addRow("enum1") << "p11" << QVariant(1); - QQmlComponent c2(&engine, QUrl(u"qrc:/qt/qml/TestTypes/ambiguous2/Ambiguous.qml"_s)); - QVERIFY2(c2.isReady(), qPrintable(c2.errorString())); - QScopedPointer<QObject> o2(c2.create()); - QCOMPARE(o2->objectName(), QStringLiteral("Ambiguous")); - QCOMPARE(o2->property("i").toString(), QStringLiteral("Ambiguous2")); + QTest::addRow("multiple ?? int") << "p12" << QVariant(1); + QTest::addRow("multiple ?? string") << "p13" << QVariant("1"); + QTest::addRow("multiple ?? mixed2") << "p14" << QVariant("2"); + QTest::addRow("multiple ?? mixed3") << "p15" << QVariant(1); + + QTest::addRow("optional + nullish bad") << "p16" << QVariant(-1); + QTest::addRow("optional + nullish good") << "p17" << QVariant(5); } -void tst_QmlCppCodegen::fromBoolValue() +void tst_QmlCppCodegen::nullishCoalescing() { QQmlEngine engine; - QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/fromBoolValue.qml"_s)); + const QUrl document(u"qrc:/qt/qml/TestTypes/nullishCoalescing.qml"_s); + QQmlComponent c(&engine, document); QVERIFY2(c.isReady(), qPrintable(c.errorString())); QScopedPointer<QObject> o(c.create()); - QCOMPARE(o->property("a").toBool(), true); - o->setProperty("x", 100); - QCOMPARE(o->property("a").toBool(), false); - - QCOMPARE(o->property("width").toInt(), 100); - QCOMPARE(o->property("b").toBool(), false); + QVERIFY(o); - QScopedPointer<QObject> parent(c.create()); - o->setProperty("parent", QVariant::fromValue(parent.data())); - QCOMPARE(o->property("width").toInt(), 100); - QCOMPARE(o->property("b").toBool(), false); + QFETCH(QString, propertyName); + QFETCH(QVariant, expected); - o->setProperty("state", QVariant::fromValue(u"foo"_s)); - QCOMPARE(o->property("width").toInt(), 0); - QCOMPARE(o->property("b").toBool(), false); + QVariant actual = o->property(propertyName.toLocal8Bit()); + QCOMPARE(actual, expected); } -void tst_QmlCppCodegen::invisibleTypes() +void tst_QmlCppCodegen::numbersInJsPrimitive() { QQmlEngine engine; - QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/invisibleTypes.qml"_s)); + QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/numbersInJsPrimitive.qml"_s)); QVERIFY2(c.isReady(), qPrintable(c.errorString())); QScopedPointer<QObject> o(c.create()); + QVERIFY(!o.isNull()); - QObject *singleton = qvariant_cast<QObject *>(o->property("singleton")); - QVERIFY(singleton != nullptr); - QCOMPARE(singleton->metaObject()->className(), "SingletonModel"); + const QList<qint64> zeroes + = {0, 0, 0, 0, 0, 0, 0, 0}; + const QList<qint64> written + = {35, 36, 37, 38, 39, 40, 41, 42}; + const QList<qint64> writtenNegative + = {-35, 220, -37, 65498, -39, 4294967256, -41, 4294967254}; + const QList<QList<qint64>> writtenShuffled = { + { -36, 219, -38, 65497, -40, 4294967255, -42, 4294967260 }, + { -37, 218, -39, 65496, -41, 4294967254, -36, 4294967259 }, + { -38, 217, -40, 65495, -42, 4294967260, -37, 4294967258 }, + { -39, 216, -41, 65494, -36, 4294967259, -38, 4294967257 }, + { -40, 215, -42, 65500, -37, 4294967258, -39, 4294967256 }, + { -41, 214, -36, 65499, -38, 4294967257, -40, 4294967255 }, + { -42, 220, -37, 65498, -39, 4294967256, -41, 4294967254 }, + { -36, 219, -38, 65497, -40, 4294967255, -42, 4294967260 }, + }; - QObject *attached = qvariant_cast<QObject *>(o->property("attached")); - QVERIFY(attached != nullptr); - QCOMPARE(attached->metaObject()->className(), "AttachedAttached"); + const QList<qint64> stored + = {50, 51, 1332, 1333, 1334, 1335, 1336, 1337}; + const QList<qint64> storedNegative + = {-50, 205, -1332, 64203, -1334, 4294965961, -1336, 4294965959}; + const QList<QList<qint64>> storedShuffled = { + { -51, 204, -1333, 64202, -1335, 4294965960, -1337, 4294967245 }, + { -52, 203, -1334, 64201, -1336, 4294965959, -51, 4294967244 }, + { -53, 202, -1335, 64200, -1337, 4294967245, -52, 4294967243 }, + { -54, 201, -1336, 64199, -51, 4294967244, -53, 4294967242 }, + { -55, 200, -1337, 65485, -52, 4294967243, -54, 4294967241 }, + { -56, 199, -51, 65484, -53, 4294967242, -55, 4294967240 }, + { -57, 205, -52, 65483, -54, 4294967241, -56, 4294967239 }, + { -51, 204, -53, 65482, -55, 4294967240, -57, 4294967245 }, + }; -// TODO: This doesn't work in interpreted mode: -// const QMetaObject *meta = qvariant_cast<const QMetaObject *>(o->property("metaobject")); -// QVERIFY(meta != nullptr); -// QCOMPARE(meta->className(), "DerivedFromInvisible"); -} + QStringList asStrings(8); + for (int i = 0; i < 8; ++i) { + QMetaObject::invokeMethod( + o.data(), "readValueAsString", + Q_RETURN_ARG(QString, asStrings[i]), Q_ARG(int, i)); + } + QCOMPARE(asStrings, convertToStrings(zeroes)); -class MyCppType : public QObject -{ - Q_OBJECT - Q_PROPERTY(bool useListDelegate - READ useListDelegate - WRITE setUseListDelegate - NOTIFY useListDelegateChanged) -public: - explicit MyCppType(QObject * parent = nullptr) : QObject(parent) {} + QMetaObject::invokeMethod(o.data(), "writeValues"); + for (int i = 0; i < 8; ++i) { + QMetaObject::invokeMethod( + o.data(), "readValueAsString", + Q_RETURN_ARG(QString, asStrings[i]), Q_ARG(int, i)); + } + QCOMPARE(asStrings, convertToStrings(written)); - bool useListDelegate() const { return m_useListDelegate; } - void setUseListDelegate(bool useListDelegate) - { - if (useListDelegate != m_useListDelegate) { - m_useListDelegate = useListDelegate; - emit useListDelegateChanged(); + QMetaObject::invokeMethod(o.data(), "negateValues"); + for (int i = 0; i < 8; ++i) { + QMetaObject::invokeMethod( + o.data(), "readValueAsString", + Q_RETURN_ARG(QString, asStrings[i]), Q_ARG(int, i)); + } + QCOMPARE(asStrings, convertToStrings(writtenNegative)); + + for (int i = 0; i < 8; ++i) { + QMetaObject::invokeMethod(o.data(), "shuffleValues"); + for (int i = 0; i < 8; ++i) { + QMetaObject::invokeMethod( + o.data(), "readValueAsString", + Q_RETURN_ARG(QString, asStrings[i]), Q_ARG(int, i)); } + QCOMPARE(asStrings, convertToStrings(writtenShuffled[i])); } -signals: - void useListDelegateChanged(); + QMetaObject::invokeMethod(o.data(), "storeValues"); + for (int i = 0; i < 8; ++i) { + QMetaObject::invokeMethod( + o.data(), "readValueAsString", + Q_RETURN_ARG(QString, asStrings[i]), Q_ARG(int, i)); + } + QCOMPARE(asStrings, convertToStrings(stored)); -private: - bool m_useListDelegate = false; -}; + QMetaObject::invokeMethod(o.data(), "negateValues"); + for (int i = 0; i < 8; ++i) { + QMetaObject::invokeMethod( + o.data(), "readValueAsString", + Q_RETURN_ARG(QString, asStrings[i]), Q_ARG(int, i)); + } + QCOMPARE(asStrings, convertToStrings(storedNegative)); + + for (int i = 0; i < 8; ++i) { + QMetaObject::invokeMethod(o.data(), "shuffleValues"); + for (int i = 0; i < 8; ++i) { + QMetaObject::invokeMethod( + o.data(), "readValueAsString", + Q_RETURN_ARG(QString, asStrings[i]), Q_ARG(int, i)); + } + QCOMPARE(asStrings, convertToStrings(storedShuffled[i])); + } +} -void tst_QmlCppCodegen::invalidPropertyType() +void tst_QmlCppCodegen::objectInVar() { - // Invisible on purpose - qmlRegisterType<MyCppType>("App", 1, 0, "MyCppType"); + QQmlEngine engine; + QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/objectInVar.qml"_s)); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> o(c.create()); + QVERIFY(o); + QCOMPARE(qvariant_cast<QObject*>(o->property("thing")), o.data()); + + bool result = false; + QVERIFY(QMetaObject::invokeMethod(o.data(), "doThing", Q_RETURN_ARG(bool, result))); + QVERIFY(result); + o->setProperty("thing", QVariant::fromValue<std::nullptr_t>(nullptr)); + QVERIFY(QMetaObject::invokeMethod(o.data(), "doThing", Q_RETURN_ARG(bool, result))); + QVERIFY(!result); +} + +void tst_QmlCppCodegen::objectLookupOnListElement() +{ QQmlEngine engine; - QQmlComponent okComponent(&engine, QUrl(u"qrc:/qt/qml/TestTypes/OkType.qml"_s)); - QVERIFY2(okComponent.isReady(), qPrintable(okComponent.errorString())); - QScopedPointer<QObject> picker(okComponent.create()); - QVERIFY2(!picker.isNull(), qPrintable(okComponent.errorString())); - QObject *inner = qmlContext(picker.data())->objectForName(u"inner"_s); - QVERIFY(inner); - MyCppType *myCppType = qobject_cast<MyCppType *>(inner); - QVERIFY(myCppType); - QVERIFY(!myCppType->useListDelegate()); - QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/BadType.qml"_s)); + const QUrl url(u"qrc:/qt/qml/TestTypes/objectLookupOnListElement.qml"_s); + QQmlComponent c1(&engine, url); + QVERIFY2(c1.isReady(), qPrintable(c1.errorString())); + + QScopedPointer<QObject> object(c1.create()); + QVERIFY(!object.isNull()); + + QList<int> zOrders; + QMetaObject::invokeMethod(object.data(), "zOrders", Q_RETURN_ARG(QList<int>, zOrders)); + QCOMPARE(zOrders, (QList<int>{1, 0, 0})); + object->setProperty("current", 1); + QMetaObject::invokeMethod(object.data(), "zOrders", Q_RETURN_ARG(QList<int>, zOrders)); + QCOMPARE(zOrders, (QList<int>{0, 1, 0})); + + QMetaObject::invokeMethod(object.data(), "clearChildren"); + QTest::ignoreMessage( + QtWarningMsg, + qPrintable(url.toString() + + u":21: TypeError: Cannot read property 'z' of undefined"_s)); + QMetaObject::invokeMethod(object.data(), "zOrders", Q_RETURN_ARG(QList<int>, zOrders)); + QCOMPARE(zOrders, (QList<int>())); +} + +void tst_QmlCppCodegen::objectToString() +{ + QQmlEngine engine; + QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/toString.qml"_s)); QVERIFY2(c.isReady(), qPrintable(c.errorString())); - QScopedPointer<QObject> o(c.createWithInitialProperties( - QVariantMap {{u"picker"_s, QVariant::fromValue(picker.data())}})); - QVERIFY2(!o.isNull(), qPrintable(c.errorString())); - QVERIFY(!myCppType->useListDelegate()); - o->setProperty("useListDelegate", QVariant::fromValue<bool>(true)); - QVERIFY(myCppType->useListDelegate()); + QTest::ignoreMessage(QtWarningMsg, "qrc:/qt/qml/TestTypes/toString.qml:6: no"); + QScopedPointer<QObject> o(c.create()); + QVERIFY(!o.isNull()); + + QCOMPARE(o->property("yes").toString(), u"yes yes"_s); + QCOMPARE(o->property("no").toString(), u" no"_s); // throws, but that is ignored } -void tst_QmlCppCodegen::valueTypeLists() +void tst_QmlCppCodegen::objectWithStringListMethod() { QQmlEngine engine; - QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/valueTypeLists.qml"_s)); + QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/objectWithStringListMethod.qml"_s)); QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QTest::ignoreMessage(QtDebugMsg, "2"); QScopedPointer<QObject> o(c.create()); + QVERIFY(!o.isNull()); +} - QCOMPARE(qvariant_cast<QRectF>(o->property("rectInBounds")), QRectF(1, 2, 3, 4)); - QVERIFY(o->metaObject()->indexOfProperty("rectOutOfBounds") > 0); - QVERIFY(!o->property("rectOutOfBounds").isValid()); +void tst_QmlCppCodegen::onAssignment() +{ + QQmlEngine engine; + QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/pressAndHoldButton.qml"_s)); + QVERIFY2(!component.isError(), component.errorString().toUtf8()); + QScopedPointer<QObject> object(component.create()); - QCOMPARE(qvariant_cast<QString>(o->property("stringInBounds")), QStringLiteral("bbb")); - QVERIFY(o->metaObject()->indexOfProperty("stringOutOfBounds") > 0); - QVERIFY(!o->property("stringOutOfBounds").isValid()); + QCOMPARE(object->property("pressed").toBool(), false); + QCOMPARE(object->property("scale").toDouble(), 1.0); - QCOMPARE(qvariant_cast<int>(o->property("intInBounds")), 7); - QVERIFY(o->metaObject()->indexOfProperty("intOutOfBounds") > 0); - QVERIFY(!o->property("intOutOfBounds").isValid()); + object->metaObject()->invokeMethod(object.data(), "press"); + QTRY_COMPARE(object->property("pressed").toBool(), true); + QCOMPARE(object->property("scale").toDouble(), 0.9); - QCOMPARE(qvariant_cast<QString>(o->property("charInBounds")), QStringLiteral("d")); - QVERIFY(o->metaObject()->indexOfProperty("charOutOfBounds") > 0); - QVERIFY(!o->property("charOutOfBounds").isValid()); + object->metaObject()->invokeMethod(object.data(), "release"); + QCOMPARE(object->property("pressed").toBool(), false); + QCOMPARE(object->property("scale").toDouble(), 1.0); } -void tst_QmlCppCodegen::boundComponents() +void tst_QmlCppCodegen::optionalComparison() { QQmlEngine engine; - QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/boundComponents.qml"_s)); - QVERIFY2(c.isReady(), qPrintable(c.errorString())); - QScopedPointer<QObject> o(c.create()); + QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/optionalComparison.qml"_s)); + QVERIFY2(!component.isError(), component.errorString().toUtf8()); + QScopedPointer<QObject> object(component.create()); + QVERIFY(!object.isNull()); - QObject *c1o = o->property("o").value<QObject *>(); - QVERIFY(c1o != nullptr); - QCOMPARE(c1o->objectName(), u"bar"_s); + QCOMPARE(object->property("found").toInt(), 1); + QCOMPARE(object->property("foundStrict").toInt(), 1); + QCOMPARE(object->property("foundNot").toInt(), 2); + QCOMPARE(object->property("foundStrictNot").toInt(), 2); - QObject *c2o = c1o->property("o").value<QObject *>(); - QVERIFY(c2o != nullptr); - QCOMPARE(c2o->objectName(), u"bar12"_s); + // this === this, null === null (x4), undefined === undefined + QCOMPARE(object->property("undefinedEqualsUndefined").toInt(), 6); + + QCOMPARE(object->property("optionalNull").toBool(), true); + object->setObjectName("foo"_L1); + QCOMPARE(object->property("optionalNull").toBool(), false); } -class InvisibleListElementType : public QObject +void tst_QmlCppCodegen::outOfBoundsArray() { - Q_OBJECT -public: - InvisibleListElementType(QObject *parent = nullptr) : QObject(parent) {} -}; + QQmlEngine engine; + QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/outOfBounds.qml"_s)); + QVERIFY2(!component.isError(), component.errorString().toUtf8()); -void tst_QmlCppCodegen::invisibleListElementType() + QTest::ignoreMessage(QtDebugMsg, "oob undefined"); + QScopedPointer<QObject> object(component.create()); + QVERIFY(!object.isNull()); + QVERIFY(object->metaObject()->indexOfProperty("oob") > 0); + QVERIFY(!object->property("oob").isValid()); + const QVariant oob2 = object->property("oob2"); + QCOMPARE(oob2.metaType(), QMetaType::fromType<QObject *>()); + QCOMPARE(oob2.value<QObject *>(), nullptr); +} + +void tst_QmlCppCodegen::overriddenProperty() { - qmlRegisterType<InvisibleListElementType>("Invisible", 1, 0, "InvisibleListElement"); QQmlEngine engine; - QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/invisibleListElementType.qml"_s)); - QVERIFY2(c.isReady(), qPrintable(c.errorString())); - QScopedPointer<QObject> o(c.create()); - QVERIFY(!o.isNull()); + QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/childobject.qml"_s)); + QVERIFY2(component.isReady(), component.errorString().toUtf8()); + QScopedPointer<QObject> object(component.create()); + QVERIFY(!object.isNull()); - QObject *a = o->property("a").value<QObject *>(); - QVERIFY(a); + QObject *child = object->property("child").value<QObject *>(); + QVERIFY(child); - const QVariant x = a->property("x"); - QCOMPARE(x.metaType(), QMetaType::fromType<QQmlListReference>()); - const QQmlListReference ref = x.value<QQmlListReference>(); - QVERIFY(ref.isValid()); - QCOMPARE(ref.size(), 0); + QCOMPARE(object->objectName(), u"kraut"_s); + QCOMPARE(object->property("doneThing").toInt(), 5); + QCOMPARE(object->property("usingFinal").toInt(), 5); + + auto checkAssignment = [&]() { + const QString newName = u"worscht"_s; + QMetaObject::invokeMethod(object.data(), "setChildObjectName", Q_ARG(QString, newName)); + QCOMPARE(object->objectName(), newName); + }; + checkAssignment(); + + QMetaObject::invokeMethod(child, "doString"); + QCOMPARE(child->objectName(), u"string"_s); + QMetaObject::invokeMethod(child, "doNumber"); + QCOMPARE(child->objectName(), u"double"_s); + QMetaObject::invokeMethod(child, "doArray"); + QCOMPARE(child->objectName(), u"javaScript"_s); + + QMetaObject::invokeMethod(child, "doString2"); + QCOMPARE(child->objectName(), u"string"_s); + QMetaObject::invokeMethod(child, "doNumber2"); + QCOMPARE(child->objectName(), u"double"_s); + QMetaObject::invokeMethod(child, "doArray2"); + QCOMPARE(child->objectName(), u"javaScript"_s); + + QMetaObject::invokeMethod(child, "doFoo"); + QCOMPARE(child->objectName(), u"ObjectWithMethod"_s); + + ObjectWithMethod *benign = new ObjectWithMethod(object.data()); + benign->theThing = 10; + benign->setObjectName(u"cabbage"_s); + object->setProperty("child", QVariant::fromValue(benign)); + QCOMPARE(object->objectName(), u"cabbage"_s); + checkAssignment(); + QCOMPARE(object->property("doneThing").toInt(), 10); + QCOMPARE(object->property("usingFinal").toInt(), 10); + + OverriddenObjectName *evil = new OverriddenObjectName(object.data()); + QTest::ignoreMessage(QtWarningMsg, + "Final member fff is overridden in class OverriddenObjectName. " + "The override won't be used."); + object->setProperty("child", QVariant::fromValue(evil)); + + QCOMPARE(object->objectName(), u"borschtsch"_s); + + checkAssignment(); + QCOMPARE(object->property("doneThing").toInt(), 7); + QCOMPARE(object->property("usingFinal").toInt(), 5); } -void tst_QmlCppCodegen::typePropertyClash() +void tst_QmlCppCodegen::ownPropertiesNonShadowable() { QQmlEngine engine; - engine.rootContext()->setContextProperty(u"size"_s, 5); - QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/typePropertyClash.qml"_s)); - QVERIFY2(c.isReady(), qPrintable(c.errorString())); - QScopedPointer<QObject> o(c.create()); - QVERIFY(!o.isNull()); - QCOMPARE(o->objectName(), u"Size: 5"_s); + QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/overriddenMember.qml"_s)); + + QVERIFY2(component.isReady(), qPrintable(component.errorString())); + QScopedPointer<QObject> rootObject(component.create()); + QVERIFY(rootObject); + + QCOMPARE(rootObject->property("ppp").toInt(), 16); + QCOMPARE(rootObject->property("ppp2").toInt(), 9); + QCOMPARE(rootObject->property("ppp3").toInt(), 12); } -void tst_QmlCppCodegen::objectToString() +void tst_QmlCppCodegen::parentProperty() { QQmlEngine engine; - QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/toString.qml"_s)); - QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/parentProp.qml"_s)); + QVERIFY2(component.isReady(), component.errorString().toUtf8()); + QScopedPointer<QObject> object(component.create()); + QVERIFY(!object.isNull()); + QCOMPARE(object->property("c").toInt(), 11); + QCOMPARE(object->property("i").toInt(), 22); + object->setProperty("a", QVariant::fromValue(22)); + QCOMPARE(object->property("c").toInt(), 28); + object->setProperty("implicitWidth", QVariant::fromValue(14)); + QCOMPARE(object->property("i").toInt(), 26); - QTest::ignoreMessage(QtWarningMsg, "qrc:/qt/qml/TestTypes/toString.qml:6: no"); - QScopedPointer<QObject> o(c.create()); - QVERIFY(!o.isNull()); + QObject *child = qmlContext(object.data())->objectForName(u"child"_s); + QObject *sibling = qmlContext(object.data())->objectForName(u"sibling"_s); + QObject *evil = qmlContext(object.data())->objectForName(u"evil"_s); - QCOMPARE(o->property("yes").toString(), u"yes yes"_s); - QCOMPARE(o->property("no").toString(), u" no"_s); // throws, but that is ignored + child->setProperty("parent", QVariant::fromValue(sibling)); + + QCOMPARE(child->property("b").toInt(), 0); + QCOMPARE(child->property("i").toInt(), 28); + QCOMPARE(object->property("i").toInt(), 56); + + child->setProperty("parent", QVariant::fromValue(evil)); + + QCOMPARE(child->property("b").toInt(), 5994); + QCOMPARE(object->property("c").toInt(), 5996); + + QCOMPARE(child->property("i").toInt(), 443); + QCOMPARE(object->property("i").toInt(), 886); + + { + QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/specificParent.qml"_s)); + + QVERIFY2(component.isReady(), qPrintable(component.errorString())); + QScopedPointer<QObject> rootObject(component.create()); + QVERIFY(rootObject); + + QCOMPARE(rootObject->property("a").toReal(), 77.0); + } } -void tst_QmlCppCodegen::throwObjectName() +void tst_QmlCppCodegen::popContextAfterRet() { QQmlEngine engine; - QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/throwObjectName.qml"_s)); + QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/popContextAfterRet.qml"_s)); QVERIFY2(c.isReady(), qPrintable(c.errorString())); - - QTest::ignoreMessage(QtWarningMsg, "qrc:/qt/qml/TestTypes/throwObjectName.qml:5:5: ouch"); QScopedPointer<QObject> o(c.create()); - QVERIFY(!o.isNull()); - QVERIFY(o->objectName().isEmpty()); + QVERIFY(o); + + QCOMPARE(o->objectName(), QString()); + o->setProperty("stackViewDepth", 1); + QCOMPARE(o->objectName(), u"backgroundImage"_s); + o->setProperty("stackViewDepth", 2); + QCOMPARE(o->objectName(), u"backgroundBlur"_s); + o->setProperty("stackViewDepth", 1); + QCOMPARE(o->objectName(), u"backgroundImage"_s); } -void tst_QmlCppCodegen::javaScriptArgument() +void tst_QmlCppCodegen::prefixedType() { QQmlEngine engine; - QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/javaScriptArgument.qml"_s)); - QVERIFY2(c.isReady(), qPrintable(c.errorString())); + // We need to add an import path here because we cannot namespace the implicit import. + // The implicit import is what we use for all the other tests, even if we explicitly + // import TestTypes. That is because the TestTypes module is in a subdirectory "data". + engine.addImportPath(u":/"_s); + + const QUrl document(u"qrc:/qt/qml/TestTypes/prefixedMetaType.qml"_s); + QQmlComponent c(&engine, document); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); QScopedPointer<QObject> o(c.create()); - QVERIFY(!o.isNull()); + QVERIFY(o); - QCOMPARE(o->property("a").toDouble(), 4.0); - QCOMPARE(o->property("b").toDouble(), 9.0); - QCOMPARE(o->property("c").toString(), u"5t-1"_s); - QCOMPARE(o->property("d").toString(), u"9"_s); - QCOMPARE(o->property("e").toString(), u"10"_s); - QCOMPARE(o->property("f").toString(), u"-10"_s); + QCOMPARE(o->property("state").toInt(), 2); + QVERIFY(qvariant_cast<QObject *>(o->property("a")) != nullptr); + QVERIFY(qvariant_cast<QObject *>(o->property("b")) != nullptr); + QVERIFY(qvariant_cast<QObject *>(o->property("c")) == nullptr); - const QStringList scales { - "0 ", "1 ", "10 ", "100 ", "1000 ", "9.77k", "97.7k", "977k", "9.54M", "95.4M", "954M", - "9.31G", "93.1G", "931G", "9.09T", "-1 ", "-10 ", "-100 ", "-1000 ", "-9.77k", "-97.7k", - "-977k", "-9.54M", "-95.4M", "-954M", "-9.31G", "-93.1G", "-931G", "-9.09T" - }; + QVERIFY(qvariant_cast<QObject *>(o->property("d")) != nullptr); + QVERIFY(qvariant_cast<QObject *>(o->property("e")) != nullptr); + QVERIFY(qvariant_cast<QObject *>(o->property("f")) == nullptr); - QCOMPARE(o->property("scales").value<QStringList>(), scales); + QVERIFY(qvariant_cast<QObject *>(o->property("g")) != nullptr); + QVERIFY(qvariant_cast<QObject *>(o->property("h")) != nullptr); - double thing = 12.0; - QString result; - QMetaObject::invokeMethod( - o.data(), "forwardArg", Q_RETURN_ARG(QString, result), Q_ARG(double, thing)); - QCOMPARE(result, u"12 "); + QCOMPARE(o->property("countG").toInt(), 11); + QCOMPARE(o->property("countH").toInt(), 11); } -void tst_QmlCppCodegen::translation() +void tst_QmlCppCodegen::propertyOfParent() { QQmlEngine engine; - QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/translation.qml"_s)); - QVERIFY2(c.isReady(), qPrintable(c.errorString())); - QScopedPointer<QObject> o(c.create()); + QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/RootWithoutId.qml"_s)); + QVERIFY2(component.isReady(), component.errorString().toUtf8()); + QScopedPointer<QObject> object(component.create()); - QCOMPARE(o->property("translate2"), u"s"_s); - QCOMPARE(o->property("translate3"), u"s"_s); - QCOMPARE(o->property("translate4"), u"s"_s); + QObject *child = qmlContext(object.data())->objectForName(u"item"_s); - QCOMPARE(o->property("translateNoop2"), u"s"_s); - QCOMPARE(o->property("translateNoop3"), u"s"_s); + bool expected = false; - QCOMPARE(o->property("tr1"), u"s"_s); - QCOMPARE(o->property("tr2"), u"s"_s); - QCOMPARE(o->property("tr3"), u"s"_s); + for (int i = 0; i < 3; ++i) { + const QVariant foo = object->property("foo"); + QCOMPARE(foo.metaType(), QMetaType::fromType<bool>()); + QCOMPARE(foo.toBool(), expected); - QCOMPARE(o->property("trNoop1"), u"s"_s); - QCOMPARE(o->property("trNoop2"), u"s"_s); + const QVariant bar = object->property("bar"); + QCOMPARE(bar.metaType(), QMetaType::fromType<bool>()); + QCOMPARE(bar.toBool(), expected); - QCOMPARE(o->property("trId1"), u"s"_s); - QCOMPARE(o->property("trId2"), u"s"_s); + const QVariant visible = child->property("visible"); + QCOMPARE(visible.metaType(), QMetaType::fromType<bool>()); + QCOMPARE(visible.toBool(), expected); - QCOMPARE(o->property("trIdNoop1"), u"s"_s); + expected = !expected; + object->setProperty("foo", expected); + } } -void tst_QmlCppCodegen::stringArg() +void tst_QmlCppCodegen::reduceWithNullThis() { QQmlEngine engine; - QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/stringArg.qml"_s)); - QVERIFY2(c.isReady(), qPrintable(c.errorString())); - QScopedPointer<QObject> o(c.create()); + QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/reduceWithNullThis.qml"_s)); + QVERIFY2(component.isReady(), component.errorString().toUtf8()); + QScopedPointer<QObject> object(component.create()); + QVERIFY(!object.isNull()); - QCOMPARE(o->property("stringArg"), u"a foozly thing"_s); - QCOMPARE(o->property("falseArg"), u"a 0 thing"_s); - QCOMPARE(o->property("trueArg"), u"a 1 thing"_s); - QCOMPARE(o->property("zeroArg"), u"a 0 thing"_s); - QCOMPARE(o->property("intArg"), u"a 11 thing"_s); - QCOMPARE(o->property("realArg"), u"a 12.25 thing"_s); + QCOMPARE(object->property("preferredHeight").toDouble(), 28.0); + QCOMPARE(object->property("preferredHeight2").toDouble(), 28.0); } -void tst_QmlCppCodegen::conversionDecrement() +void tst_QmlCppCodegen::readEnumFromInstance() { QQmlEngine engine; - QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/conversionDecrement.qml"_s)); - QVERIFY2(c.isReady(), qPrintable(c.errorString())); + const QString url = u"qrc:/qt/qml/TestTypes/readEnumFromInstance.qml"_s; - QScopedPointer<QObject> o(c.create()); - QVERIFY(!o.isNull()); + QQmlComponent component(&engine, QUrl(url)); + QVERIFY2(component.isReady(), component.errorString().toUtf8()); - QCOMPARE(o->property("currentPageIndex").toInt(), 0); - o->setProperty("pages", 5); - QCOMPARE(o->property("currentPageIndex").toInt(), 3); - o->setProperty("pages", 4); - QCOMPARE(o->property("currentPageIndex").toInt(), 0); - o->setProperty("pages", 6); - QCOMPARE(o->property("currentPageIndex").toInt(), 4); - o->setProperty("pages", 60); - QCOMPARE(o->property("currentPageIndex").toInt(), 3); + QTest::ignoreMessage( + QtWarningMsg, qPrintable(url + ":7:5: Unable to assign [undefined] to int"_L1)); + + QScopedPointer<QObject> object(component.create()); + QVERIFY(!object.isNull()); + + QCOMPARE(object->property("priority"), QVariant::fromValue<int>(0)); + QCOMPARE(object->property("prop2"), QVariant::fromValue<int>(1)); + QCOMPARE(object->property("priorityIsVeryHigh"), QVariant::fromValue<bool>(false)); + + QTest::ignoreMessage( + QtWarningMsg, qPrintable(url + ":13: Error: Cannot assign [undefined] to int"_L1)); + + int result = 0; + QMetaObject::invokeMethod(object.data(), "cyclePriority", Q_RETURN_ARG(int, result)); + QCOMPARE(result, 0); } -void tst_QmlCppCodegen::unstoredUndefined() +void tst_QmlCppCodegen::readonlyListProperty() { QQmlEngine engine; - QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/unstoredUndefined.qml"_s)); - QVERIFY2(c.isReady(), qPrintable(c.errorString())); - QScopedPointer<QObject> o(c.create()); - QCOMPARE(o->objectName(), u"NaN"_s); + QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/readonlyListProperty.qml"_s)); + QVERIFY2(component.isReady(), component.errorString().toUtf8()); + QScopedPointer<QObject> object(component.create()); + QVERIFY(!object.isNull()); + + QCOMPARE(object->property("l").toInt(), 4); +} + +void tst_QmlCppCodegen::registerElimination() +{ + QQmlEngine engine; + QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/registerelimination.qml"_s)); + QVERIFY2(component.isReady(), component.errorString().toUtf8()); + QScopedPointer<QObject> object(component.create()); + QVERIFY(!object.isNull()); + + // Increment of 23 hits both 0 and 460 + for (int input = -23; input < 700; input += 23) { + object->setProperty("input", input); + if (input <= 0 || input >= 460) + QCOMPARE(object->property("output").toInt(), 459); + else + QCOMPARE(object->property("output").toInt(), input); + } } void tst_QmlCppCodegen::registerPropagation() @@ -2431,784 +4139,989 @@ void tst_QmlCppCodegen::registerPropagation() QCOMPARE(undefined, u"undefined"_s); } -void tst_QmlCppCodegen::argumentConversion() +void tst_QmlCppCodegen::renameAdjust() { QQmlEngine engine; - QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/argumentConversion.qml"_s)); + QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/renameAdjust.qml"_s)); QVERIFY2(c.isReady(), qPrintable(c.errorString())); - QScopedPointer<QObject> o(c.create()); - auto checkNaN = [&](const char *propName) { - const QVariant prop = o->property(propName); - QCOMPARE(prop.metaType(), QMetaType::fromType<double>()); - QVERIFY(qIsNaN(prop.toDouble())); - }; + QTest::ignoreMessage(QtDebugMsg, "success"); + QTest::ignoreMessage(QtCriticalMsg, "failed 10 11"); - checkNaN("a"); - checkNaN("b"); - checkNaN("e"); - - QCOMPARE(o->property("c").toDouble(), 3.0); - QCOMPARE(o->property("d").toDouble(), -1.0); - QCOMPARE(o->property("f").toDouble(), 10.0); + QScopedPointer<QObject> o(c.create()); + QVERIFY(o); } -void tst_QmlCppCodegen::badSequence() +void tst_QmlCppCodegen::resettableProperty() { + QFETCH(QString, url); + QQmlEngine engine; - QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/badSequence.qml"_s)); + QQmlComponent c(&engine, QUrl(url)); QVERIFY2(c.isReady(), qPrintable(c.errorString())); - QScopedPointer<QObject> o(c.create()); - - Person *self = qobject_cast<Person *>(o.data()); - QVERIFY(self); - QVERIFY(self->barzles().isEmpty()); - QVERIFY(self->cousins().isEmpty()); - - Person *other = o->property("other").value<Person *>(); - QVERIFY(other); - QVERIFY(other->barzles().isEmpty()); - QVERIFY(other->cousins().isEmpty()); + QTest::ignoreMessage( + QtWarningMsg, qPrintable(url + u":10:5: Unable to assign [undefined] to double"_s)); - Barzle f1; - Barzle f2; - const QList<Barzle *> barzles { &f1, &f2 }; - const QList<Person *> cousins { self, other }; + QScopedPointer<QObject> o(c.create()); + QVERIFY(o); - other->setBarzles(barzles); - QCOMPARE(self->barzles(), barzles); - QCOMPARE(self->property("l").toInt(), 2); + QCOMPARE(o->property("value").toDouble(), 999); + QMetaObject::invokeMethod(o.data(), "doReset"); + QCOMPARE(o->property("value").toDouble(), 0); - other->setCousins(cousins); - QCOMPARE(self->cousins(), cousins); - QCOMPARE(self->property("m").toInt(), 2); + o->setProperty("value", double(82)); + QCOMPARE(o->property("value").toDouble(), 82); + QMetaObject::invokeMethod(o.data(), "doReset2"); + QCOMPARE(o->property("value").toDouble(), 0); - QQmlListProperty<Person> others - = self->property("others").value<QQmlListProperty<Person>>(); - QCOMPARE(others.count(&others), 2); - QCOMPARE(others.at(&others, 0), cousins[0]); - QCOMPARE(others.at(&others, 1), cousins[1]); + QTest::ignoreMessage( + QtWarningMsg, qPrintable(url + u":18: Error: Cannot assign [undefined] to double"_s)); + QCOMPARE(o->property("notResettable").toDouble(), 10); + QMetaObject::invokeMethod(o.data(), "doNotReset"); + QCOMPARE(o->property("notResettable").toDouble(), 10); + QCOMPARE(o->property("notResettable2").toDouble(), 0); // not NaN - QQmlListProperty<Person> momsCousins - = self->property("momsCousins").value<QQmlListProperty<Person>>(); - QCOMPARE(momsCousins.count(&momsCousins), 2); - QCOMPARE(momsCousins.at(&momsCousins, 0), cousins[0]); - QCOMPARE(momsCousins.at(&momsCousins, 1), cousins[1]); + o->setObjectName(u"namename"_s); + QTest::ignoreMessage( + QtWarningMsg, qPrintable(url + u":22: Error: Cannot assign [undefined] to QString"_s)); + QMetaObject::invokeMethod(o.data(), "aaa"); + QCOMPARE(o->objectName(), u"namename"_s); +} - QQmlListProperty<Person> dadsCousins - = self->property("dadsCousins").value<QQmlListProperty<Person>>(); - QCOMPARE(dadsCousins.count(&dadsCousins), 1); - QCOMPARE(dadsCousins.at(&dadsCousins, 0), other); +void tst_QmlCppCodegen::resettableProperty_data() +{ + QTest::addColumn<QString>("url"); + QTest::addRow("object lookups") << u"qrc:/qt/qml/TestTypes/resettable.qml"_s; + QTest::addRow("fallback lookups") << u"qrc:/qt/qml/TestTypes/fallbackresettable.qml"_s; } -void tst_QmlCppCodegen::enumLookup() +void tst_QmlCppCodegen::returnAfterReject() { QQmlEngine engine; - QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/enumLookup.qml"_s)); + QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/returnAfterReject.qml"_s)); QVERIFY2(c.isReady(), qPrintable(c.errorString())); QScopedPointer<QObject> o(c.create()); - - QCOMPARE(o->property("ready").toBool(), true); + QVERIFY(o); + QCOMPARE(o->property("bar").toInt(), 123); } -void tst_QmlCppCodegen::trivialSignalHandler() +void tst_QmlCppCodegen::revisions() { QQmlEngine engine; - QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/trivialSignalHandler.qml"_s)); + QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/revisions.qml"_s)); QVERIFY2(c.isReady(), qPrintable(c.errorString())); QScopedPointer<QObject> o(c.create()); + QVERIFY(o); - QCOMPARE(o->property("a").toString(), u"no"_s); - QCOMPARE(o->property("b").toInt(), -1); - QCOMPARE(o->property("b").toDouble(), -1.0); + QCOMPARE(o->property("delayed").toBool(), true); + QCOMPARE(o->property("gotten").toInt(), 5); +} - o->setObjectName(u"yes"_s); - QCOMPARE(o->property("a").toString(), u"yes"_s); - QCOMPARE(o->property("b").toInt(), 5); - QCOMPARE(o->property("c").toDouble(), 2.5); +void tst_QmlCppCodegen::scopeIdLookup() +{ + QQmlEngine engine; + QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/scopeIdLookup.qml"_s)); + QVERIFY2(!component.isError(), component.errorString().toUtf8()); + QScopedPointer<QObject> object(component.create()); + QVERIFY(!object.isNull()); + QCOMPARE(object->property("objectName").toString(), u"outer"_s); } -void tst_QmlCppCodegen::stringToByteArray() +void tst_QmlCppCodegen::scopeObjectDestruction() { QQmlEngine engine; - QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/stringToByteArray.qml"_s)); - QVERIFY2(c.isReady(), qPrintable(c.errorString())); - QScopedPointer<QObject> o(c.create()); + QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/fileDialog.qml"_s)); - Person *person = qobject_cast<Person *>(o.data()); - QVERIFY(person); + QVERIFY2(component.isReady(), qPrintable(component.errorString())); + QScopedPointer<QObject> rootObject(component.create()); + QVERIFY(rootObject); - QCOMPARE(person->dataBindable().value(), QByteArray("some data")); - QCOMPARE(person->name(), u"some data"_s); + QObject *dialog = rootObject->property("dialog").value<QObject *>(); + QVERIFY(dialog); + + // We cannot check the warning messages. The AOT compiled code complains about reading the + // "parent" property of an object scheduled for deletion. The runtime silently returns undefined + // at that point and then complains about not being able to read a property on undefined. + + // Doesn't crash, even though it triggers bindings on scope objects scheduled for deletion. + QMetaObject::invokeMethod(dialog, "open"); } -void tst_QmlCppCodegen::listPropertyAsModel() +void tst_QmlCppCodegen::scopeVsObject() { QQmlEngine engine; - QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/listPropertyAsModel.qml"_s)); - QVERIFY2(c.isReady(), qPrintable(c.errorString())); - QScopedPointer<QObject> o(c.create()); - - QQmlListReference children(o.data(), "children"); - QCOMPARE(children.count(), 5); + QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/scopeVsObject.qml"_s)); + QVERIFY2(!component.isError(), component.errorString().toUtf8()); + QScopedPointer<QObject> object(component.create()); + QVERIFY(!object.isNull()); + QCOMPARE(object->property("objectName").toString(), u"foobar"_s); } -void tst_QmlCppCodegen::notNotString() +void tst_QmlCppCodegen::scopedEnum() { QQmlEngine engine; - QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/notNotString.qml"_s)); - QVERIFY2(c.isReady(), qPrintable(c.errorString())); - QScopedPointer<QObject> o(c.create()); + const QString url = u"qrc:/qt/qml/TestTypes/scopedEnum.qml"_s; + QQmlComponent component(&engine, QUrl(url)); + QVERIFY2(!component.isError(), component.errorString().toUtf8()); - QCOMPARE(o->property("notNotString").value<bool>(), false); - o->setObjectName(u"a"_s); - QCOMPARE(o->property("notNotString").value<bool>(), true); -} + QTest::ignoreMessage( + QtWarningMsg, + qPrintable(url + u":6:5: Unable to assign [undefined] to int"_s)); + QTest::ignoreMessage( + QtWarningMsg, + qPrintable(url + u":8: TypeError: Cannot read property 'C' of undefined"_s)); + QTest::ignoreMessage( + QtWarningMsg, + qPrintable(url + u":14: TypeError: Cannot read property 'C' of undefined"_s)); -template<typename T> -QString toOperand(double arg); + QScopedPointer<QObject> object(component.create()); + QVERIFY(!object.isNull()); + QCOMPARE(object->property("good").toInt(), 27); + QCOMPARE(object->property("bad").toInt(), 0); + QCOMPARE(object->property("wrong").toInt(), 0); + QCOMPARE(object->property("right").toInt(), 7); -template<> -QString toOperand<double>(double arg) -{ - if (qIsNull(arg)) - return std::signbit(arg) ? QStringLiteral("(-0)") : QStringLiteral("(0)"); + QCOMPARE(object->property("notgood").toInt(), 26); + QCOMPARE(object->property("notbad").toInt(), 26); + QCOMPARE(object->property("notwrong").toInt(), 0); + QCOMPARE(object->property("notright").toInt(), 6); - return u'(' + QJSPrimitiveValue(arg).toString() + u')'; + QCOMPARE(object->property("passable").toInt(), 2); + QCOMPARE(object->property("wild").toInt(), 1); } -template<> -QString toOperand<int>(double arg) +void tst_QmlCppCodegen::sequenceToIterable() { - const int iArg = QJSPrimitiveValue(arg).toInteger(); - return u'(' + QJSPrimitiveValue(iArg).toString() + u')'; + QQmlEngine engine; + QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/sequenceToIterable.qml"_s)); + QVERIFY2(!component.isError(), component.errorString().toUtf8()); + QScopedPointer<QObject> object(component.create()); + QVERIFY(!object.isNull()); + QCOMPARE(object->property("c").toInt(), 11); + + QQmlListReference children(object.data(), "children"); + QCOMPARE(children.count(), 11); + static const QRegularExpression name("Entry\\(0x[0-9a-f]+, \"Item ([0-9])\"\\): ([0-9])"); + for (int i = 0; i < 10; ++i) { + const auto match = name.match(children.at(i)->objectName()); + QVERIFY(match.hasMatch()); + QCOMPARE(match.captured(1), QString::number(i)); + } } -template<> -QString toOperand<bool>(double arg) +void tst_QmlCppCodegen::setLookupConversion() { - const bool bArg = QJSPrimitiveValue(arg).toBoolean(); - return u'(' + QJSPrimitiveValue(bArg).toString() + u')'; + QQmlEngine e; + QQmlComponent c(&e, QUrl(u"qrc:/qt/qml/TestTypes/setLookupConversion.qml"_s)); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> o(c.create()); + QVERIFY(!o.isNull()); + QVERIFY(o->objectName().isEmpty()); + QMetaObject::invokeMethod(o.data(), "t"); + QCOMPARE(o->objectName(), u"a"_s); + QCOMPARE(o->property("value").toInt(), 9); } -template<typename T1, typename T2> -double jsEval(double arg1, double arg2, const QString &op, QJSEngine *engine) +void tst_QmlCppCodegen::setLookupOriginalScope() { - auto evalBinary = [&](const QString &jsOp) { - return engine->evaluate(toOperand<T1>(arg1) + jsOp + toOperand<T2>(arg2)).toNumber(); - }; + QQmlEngine e; + QQmlComponent c(&e, QUrl(u"qrc:/qt/qml/TestTypes/setLookupOriginalScope.qml"_s)); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> o(c.create()); + QVERIFY(!o.isNull()); - auto evalBinaryConst = [&](const QString &jsOp) { - return engine->evaluate(toOperand<T1>(arg1) + jsOp + u'9').toNumber(); - }; + QObject *v = o->property("variable").value<QObject *>(); + QCOMPARE(v->property("value").toInt(), 0); - auto evalUnary = [&](const QString &jsOp) { - return engine->evaluate(jsOp + toOperand<T1>(arg1)).toNumber(); - }; + QMetaObject::invokeMethod(o.data(), "trigger"); + QObject *edit = o->property("edit").value<QObject *>(); + QVERIFY(edit); + QCOMPARE(edit->property("myOutput").value<QObject *>(), v); + QCOMPARE(v->property("value").toInt(), 55); +} - auto evalInPlace = [&](const QString &jsOp) { - return engine->evaluate( - u"(function() {var a = "_s + toOperand<T1>(arg1)+ u"; return "_s - + jsOp + u"a;})()"_s).toNumber(); - }; +void tst_QmlCppCodegen::shadowedAsCasts() +{ + QQmlEngine e; + QQmlComponent c(&e, QUrl(u"qrc:/qt/qml/TestTypes/shadowedAsCasts.qml"_s)); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> obj(c.create()); + QVERIFY(!obj.isNull()); + + QObject *shadowed1 = obj->property("shadowed1").value<QObject *>(); + QVERIFY(shadowed1); + QVERIFY(shadowed1->objectName().isEmpty()); + const QVariant name1 = shadowed1->property("objectName"); + QCOMPARE(name1.metaType(), QMetaType::fromType<int>()); + QCOMPARE(name1.toInt(), 43); + + QObject *shadowed2 = obj->property("shadowed2").value<QObject *>(); + QVERIFY(shadowed2); + QVERIFY(shadowed2->objectName().isEmpty()); + const QVariant name2 = shadowed2->property("objectName"); + QCOMPARE(name2.metaType(), QMetaType::fromType<int>()); + QCOMPARE(name2.toInt(), 42); + + QObject *shadowed3 = obj->property("shadowed3").value<QObject *>(); + QVERIFY(shadowed3); + QVERIFY(shadowed3->objectName().isEmpty()); + const QVariant name3 = shadowed3->property("objectName"); + QCOMPARE(name3.metaType(), QMetaType::fromType<double>()); + QCOMPARE(name3.toDouble(), 41.0); +} + +void tst_QmlCppCodegen::shadowedMethod() +{ + QQmlEngine e; + QQmlComponent c(&e, QUrl(u"qrc:/qt/qml/TestTypes/shadowedMethod.qml"_s)); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> o(c.create()); + QVERIFY(!o.isNull()); + QCOMPARE(o->property("athing"), QVariant::fromValue<bool>(false)); + QCOMPARE(o->property("bthing"), QVariant::fromValue(u"b"_s)); + QCOMPARE(o->property("cthing"), QVariant::fromValue(u"c"_s)); +} - if (op == u"unot") - return evalUnary(u"!"_s); - if (op == u"uplus") - return evalUnary(u"+"_s); - if (op == u"uminus") - return evalUnary(u"-"_s); - if (op == u"ucompl") - return evalUnary(u"~"_s); +void tst_QmlCppCodegen::shadowedPrimitiveCmpEqNull() +{ + QQmlEngine e; + QQmlComponent c(&e, QUrl(u"qrc:/qt/qml/TestTypes/shadowedPrimitiveCmpEqNull.qml"_s)); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> o(c.create()); + QVERIFY(!o.isNull()); +} - if (op == u"increment") - return evalInPlace(u"++"_s); - if (op == u"decrement") - return evalInPlace(u"--"_s); +void tst_QmlCppCodegen::shifts() +{ + QQmlEngine engine; + QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/shifts.qml"_s)); + QVERIFY2(component.isReady(), qPrintable(component.errorString())); + QScopedPointer<QObject> object(component.create()); + QVERIFY(!object.isNull()); - if (op == u"add") - return evalBinary(u"+"_s); - if (op == u"sub") - return evalBinary(u"-"_s); - if (op == u"mul") - return evalBinary(u"*"_s); - if (op == u"div") - return evalBinary(u"/"_s); - if (op == u"exp") - return evalBinary(u"**"_s); - if (op == u"mod") - return evalBinary(u"%"_s); + QCOMPARE(object->property("a").toInt(), 9728); + QCOMPARE(object->property("b").toInt(), 4864); + QCOMPARE(object->property("c").toInt(), 19448); + QCOMPARE(object->property("d").toInt(), 9731); + QCOMPARE(object->property("e").toInt(), 0); +} - if (op == u"bitAnd") - return evalBinary(u"&"_s); - if (op == u"bitOr") - return evalBinary(u"|"_s); - if (op == u"bitXor") - return evalBinary(u"^"_s); +void tst_QmlCppCodegen::signalHandler() +{ + QQmlEngine engine; + QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/signal.qml"_s)); + QVERIFY2(!component.isError(), component.errorString().toUtf8()); + QScopedPointer<QObject> object(component.create()); + QVERIFY(!object.isNull()); + QCOMPARE(object->objectName(), QString()); + QCOMPARE(object->property("ff").toInt(), 4); - if (op == u"bitAndConst") - return evalBinaryConst(u"&"_s); - if (op == u"bitOrConst") - return evalBinaryConst(u"|"_s); - if (op == u"bitXorConst") - return evalBinaryConst(u"^"_s); + object->setObjectName(u"foo"_s); + QCOMPARE(object->property("ff").toInt(), 12); +} - if (op == u"ushr") - return evalBinary(u">>>"_s); - if (op == u"shr") - return evalBinary(u">>"_s); - if (op == u"shl") - return evalBinary(u"<<"_s); +void tst_QmlCppCodegen::signalIndexMismatch() +{ + QQmlEngine engine; - if (op == u"ushrConst") - return evalBinaryConst(u">>>"_s); - if (op == u"shrConst") - return evalBinaryConst(u">>"_s); - if (op == u"shlConst") - return evalBinaryConst(u"<<"_s); + QQmlComponent c1(&engine, QUrl(u"qrc:/qt/qml/TestTypes/signalIndexMismatch.qml"_s)); + QVERIFY2(c1.isReady(), qPrintable(c1.errorString())); - qDebug() << op; - Q_UNREACHABLE_RETURN(0); + QScopedPointer<QObject> item(c1.create()); + const auto visualIndexBeforeMoveList = item->property("visualIndexBeforeMove").toList(); + const auto visualIndexAfterMoveList = item->property("visualIndexAfterMove").toList(); + + QCOMPARE(visualIndexBeforeMoveList, QList<QVariant>({ 0, 1, 2 })); + QCOMPARE(visualIndexAfterMoveList, QList<QVariant>({ 0, 1, 2 })); } -void tst_QmlCppCodegen::mathOperations() +void tst_QmlCppCodegen::signalsWithLists() { QQmlEngine engine; - QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/mathOperations.qml"_s)); + QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/signalsWithLists.qml"_s)); QVERIFY2(c.isReady(), qPrintable(c.errorString())); QScopedPointer<QObject> o(c.create()); + QVERIFY(!o.isNull()); - const QMetaObject *metaObject = o->metaObject(); + QVariantList varlist = o->property("varlist").toList(); + QCOMPARE(varlist.size(), 5); + QCOMPARE(varlist[0], QVariant::fromValue(1)); + QCOMPARE(varlist[1], QVariant::fromValue(u"foo"_s)); + QCOMPARE(varlist[2], QVariant::fromValue(o.data())); + QCOMPARE(varlist[3], QVariant()); + QCOMPARE(varlist[4], QVariant::fromValue(true)); - char t1; - char t2; - QString name; - const auto guard = qScopeGuard([&]() { - if (QTest::currentTestFailed()) { - qDebug() << t1 << t2 << name << "failed on:"; - qDebug() << "doubles" << o->property("a").toDouble() << o->property("b").toDouble(); - qDebug() << "integers" << o->property("ia").toInt() << o->property("ib").toInt(); - qDebug() << "booleans" << o->property("ba").toBool() << o->property("bb").toBool(); - } - }); + QQmlListProperty<QObject> objlist = o->property("objlist").value<QQmlListProperty<QObject>>(); + QCOMPARE(objlist.count(&objlist), 3); + QCOMPARE(objlist.at(&objlist, 0), o.data()); + QCOMPARE(objlist.at(&objlist, 1), nullptr); + QCOMPARE(objlist.at(&objlist, 2), o.data()); - for (double a : numbers) { - for (double b : numbers) { - o->setProperty("a", a); - o->setProperty("b", b); - for (int i = 0, end = metaObject->propertyCount(); i != end; ++i) { - const QMetaProperty prop = metaObject->property(i); - const QByteArray propName = prop.name(); + QCOMPARE(o->property("happening").toInt(), 0); + o->metaObject()->invokeMethod(o.data(), "sendSignals"); + QCOMPARE(o->property("happening").toInt(), 8); +} - if (propName.size() < 3 || propName == "objectName") - continue; +void tst_QmlCppCodegen::signatureIgnored() +{ + QQmlEngine engine; - t1 = propName[0]; - t2 = propName[1]; - name = QString::fromUtf8(propName.mid(2)); + QQmlComponent c1(&engine, QUrl(u"qrc:/qt/qml/TestTypes/signatureIgnored.qml"_s)); + QVERIFY2(c1.isReady(), qPrintable(c1.errorString())); - double expected; + QScopedPointer<QObject> ignored(c1.create()); + QCOMPARE(ignored->property("l").toInt(), 5); + QCOMPARE(ignored->property("m").toInt(), 77); + QCOMPARE(ignored->property("n").toInt(), 67); +} - switch (t2) { - case 'd': - case '_': - switch (t1) { - case 'd': - expected = jsEval<double, double>(a, b, name, &engine); - break; - case 'i': - expected = jsEval<int, double>(a, b, name, &engine); - break; - case 'b': - expected = jsEval<bool, double>(a, b, name, &engine); - break; - } - break; - case 'i': - switch (t1) { - case 'd': - expected = jsEval<double, int>(a, b, name, &engine); - break; - case 'i': - expected = jsEval<int, int>(a, b, name, &engine); - break; - case 'b': - expected = jsEval<bool, int>(a, b, name, &engine); - break; - } - break; - case 'b': - switch (t1) { - case 'd': - expected = jsEval<double, bool>(a, b, name, &engine); - break; - case 'i': - expected = jsEval<int, bool>(a, b, name, &engine); - break; - case 'b': - expected = jsEval<bool, bool>(a, b, name, &engine); - break; - } - break; - } +void tst_QmlCppCodegen::simpleBinding() +{ + QQmlEngine engine; + QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/Test.qml"_s)); + QScopedPointer<QObject> object(component.create()); + QVERIFY2(!object.isNull(), component.errorString().toUtf8().constData()); + QCOMPARE(object->property("foo").toInt(), int(3)); - const double result = prop.read(o.data()).toDouble(); - QCOMPARE(result, expected); - } - } + { + CppBaseClass *base = qobject_cast<CppBaseClass *>(object.data()); + Q_ASSERT(base); + QVERIFY(!base->cppProp.hasBinding()); + QCOMPARE(base->cppProp.value(), 7); + QVERIFY(base->cppProp2.hasBinding()); + QCOMPARE(base->cppProp2.value(), 14); + base->cppProp.setValue(9); + QCOMPARE(base->cppProp.value(), 9); + QCOMPARE(base->cppProp2.value(), 18); } } -void tst_QmlCppCodegen::inaccessibleProperty() +void tst_QmlCppCodegen::storeElementSideEffects() { QQmlEngine engine; - QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/versionmismatch.qml"_s)); + QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/storeElementSideEffects.qml"_s)); QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> o(c.create()); + QVERIFY(o); - QCOMPARE(o->property("c").toInt(), 5); + const QJSValue prop = o->property("myItem").value<QJSValue>(); + QVERIFY(prop.isArray()); + QCOMPARE(prop.property(0).toInt(), 10); } -void tst_QmlCppCodegen::typePropagationLoop() +void tst_QmlCppCodegen::storeMetaEnum() { QQmlEngine engine; - QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/typePropagationLoop.qml"_s)); + QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/StoreMetaEnum.qml"_s)); QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> o(c.create()); + QVERIFY(o); - QCOMPARE(o->property("j").toInt(), 3); + QCOMPARE(o->property("bar").toInt(), 0); + QCOMPARE(o->property("baz").toInt(), 1); } -void tst_QmlCppCodegen::signatureIgnored() +void tst_QmlCppCodegen::stringArg() { QQmlEngine engine; + QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/stringArg.qml"_s)); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> o(c.create()); - QQmlComponent c1(&engine, QUrl(u"qrc:/qt/qml/TestTypes/signatureIgnored.qml"_s)); - QVERIFY2(c1.isReady(), qPrintable(c1.errorString())); - - QScopedPointer<QObject> ignored(c1.create()); - QCOMPARE(ignored->property("l").toInt(), 5); - QCOMPARE(ignored->property("m").toInt(), 77); - QCOMPARE(ignored->property("n").toInt(), 67); + QCOMPARE(o->property("stringArg"), u"a foozly thing"_s); + QCOMPARE(o->property("falseArg"), u"a 0 thing"_s); + QCOMPARE(o->property("trueArg"), u"a 1 thing"_s); + QCOMPARE(o->property("zeroArg"), u"a 0 thing"_s); + QCOMPARE(o->property("intArg"), u"a 11 thing"_s); + QCOMPARE(o->property("realArg"), u"a 12.25 thing"_s); } -void tst_QmlCppCodegen::listAsArgument() +void tst_QmlCppCodegen::stringLength() { QQmlEngine engine; + QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/stringLength.qml"_s)); + QVERIFY2(!component.isError(), component.errorString().toUtf8()); + QScopedPointer<QObject> object(component.create()); + QVERIFY(!object.isNull()); + QCOMPARE(object->property("stringLength").toInt(), 8); +} - QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/listAsArgument.qml"_s)); +void tst_QmlCppCodegen::stringToByteArray() +{ + QQmlEngine engine; + QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/stringToByteArray.qml"_s)); QVERIFY2(c.isReady(), qPrintable(c.errorString())); - QScopedPointer<QObject> o(c.create()); - QCOMPARE(o->property("i").toInt(), 4); - QCOMPARE(o->property("j").toInt(), 2); - QCOMPARE(o->property("i1").toInt(), 2); - QCOMPARE(o->property("i2").toInt(), 4); - QCOMPARE(o->property("d").value<QObject *>()->objectName(), u"this one"_s); - int singleInt = 0; - QList<int> moreInts; - QMetaObject::invokeMethod(o.data(), "returnInts1", Q_RETURN_ARG(QList<int>, moreInts)); - QCOMPARE(moreInts, QList<int>({5, 4, 3, 2, 1})); - QMetaObject::invokeMethod(o.data(), "selectSecondInt", Q_RETURN_ARG(int, singleInt), Q_ARG(QList<int>, moreInts)); - QCOMPARE(singleInt, 4); + Person *person = qobject_cast<Person *>(o.data()); + QVERIFY(person); + + QCOMPARE(person->dataBindable().value(), QByteArray("some data")); + QCOMPARE(person->name(), u"some data"_s); } -void tst_QmlCppCodegen::letAndConst() +void tst_QmlCppCodegen::structuredValueType() { QQmlEngine engine; - QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/letAndConst.qml"_s)); + QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/structuredValueType.qml"_s)); QVERIFY2(c.isReady(), qPrintable(c.errorString())); QScopedPointer<QObject> o(c.create()); QVERIFY(!o.isNull()); - QCOMPARE(o->objectName(), u"ab"_s); + + QCOMPARE(o->property("r").value<QRectF>(), QRectF(1, 2, 3, 4)); + QCOMPARE(o->property("r2").value<QRectF>(), QRectF(42, 0, 0, 0)); + + WeatherModelUrl w; + w.setStrings(QStringList({"one", "two", "three"})); + + QCOMPARE(o->property("w").value<WeatherModelUrl>(), w); } -void tst_QmlCppCodegen::signalIndexMismatch() +void tst_QmlCppCodegen::testIsnan() { QQmlEngine engine; + const QUrl document(u"qrc:/qt/qml/TestTypes/isnan.qml"_s); + QQmlComponent c(&engine, document); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> o(c.create()); + QVERIFY(o); - QQmlComponent c1(&engine, QUrl(u"qrc:/qt/qml/TestTypes/signalIndexMismatch.qml"_s)); - QVERIFY2(c1.isReady(), qPrintable(c1.errorString())); + QCOMPARE(o->property("good").toDouble(), 10.1); + QVERIFY(qIsNaN(o->property("bad").toDouble())); - QScopedPointer<QObject> item(c1.create()); - const auto visualIndexBeforeMoveList = item->property("visualIndexBeforeMove").toList(); - const auto visualIndexAfterMoveList = item->property("visualIndexAfterMove").toList(); + const QVariant a = o->property("a"); + QCOMPARE(a.metaType(), QMetaType::fromType<bool>()); + QVERIFY(!a.toBool()); - QCOMPARE(visualIndexBeforeMoveList, QList<QVariant>({ 0, 1, 2 })); - QCOMPARE(visualIndexAfterMoveList, QList<QVariant>({ 0, 1, 2 })); + const QVariant b = o->property("b"); + QCOMPARE(b.metaType(), QMetaType::fromType<bool>()); + QVERIFY(b.toBool()); } -void tst_QmlCppCodegen::callWithSpread() +void tst_QmlCppCodegen::thisObject() { - QQmlEngine engine; - QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/callWithSpread.qml"_s)); + QQmlEngine e; + QQmlComponent c(&e, QUrl(u"qrc:/qt/qml/TestTypes/thisObject.qml"_s)); QVERIFY2(c.isReady(), qPrintable(c.errorString())); - QTest::ignoreMessage(QtCriticalMsg, "That is great!"); QScopedPointer<QObject> o(c.create()); QVERIFY(!o.isNull()); + QCOMPARE(o->property("warned").value<QObject *>(), o.data()); } -void tst_QmlCppCodegen::nullComparison() +void tst_QmlCppCodegen::throwObjectName() { QQmlEngine engine; + QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/throwObjectName.qml"_s)); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); - QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/nullComparison.qml"_s)); + QTest::ignoreMessage(QtWarningMsg, "qrc:/qt/qml/TestTypes/throwObjectName.qml:5:5: ouch"); + QScopedPointer<QObject> o(c.create()); + QVERIFY(!o.isNull()); + QVERIFY(o->objectName().isEmpty()); +} + +void tst_QmlCppCodegen::topLevelComponent() +{ + // TODO: Once we stop accepting top level Component elements, this test can be removed. + + QQmlEngine e; + + const QUrl url(u"qrc:/qt/qml/TestTypes/topLevelComponent.qml"_s); + QTest::ignoreMessage( + QtWarningMsg, + qPrintable(url.toString() + u":4:1: Using a Component as the root of a QML document " + "is deprecated: types defined in qml documents are " + "automatically wrapped into Components when needed."_s)); + + QQmlComponent c(&e, url); QVERIFY2(c.isReady(), qPrintable(c.errorString())); QScopedPointer<QObject> o(c.create()); QVERIFY(!o.isNull()); - QCOMPARE(o->property("v").toInt(), 1); - QCOMPARE(o->property("w").toInt(), 3); - QCOMPARE(o->property("x").toInt(), 1); - QCOMPARE(o->property("y").toInt(), 5); + QQmlComponent *inner = qobject_cast<QQmlComponent *>(o.data()); + QVERIFY(inner); + + QScopedPointer<QObject> o2(inner->create()); + QCOMPARE(o2->objectName(), u"foo"_s); } -void tst_QmlCppCodegen::consoleObject() +void tst_QmlCppCodegen::translation() { QQmlEngine engine; - QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/consoleObject.qml"_s)); + QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/translation.qml"_s)); QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> o(c.create()); - QTest::ignoreMessage(QtDebugMsg, "b 4.55"); - QTest::ignoreMessage(QtDebugMsg, "b 4.55"); - QTest::ignoreMessage(QtInfoMsg, "b 4.55"); - QTest::ignoreMessage(QtWarningMsg, "b 4.55"); - QTest::ignoreMessage(QtCriticalMsg, "b 4.55"); + QCOMPARE(o->property("translate2"), u"s"_s); + QCOMPARE(o->property("translate3"), u"s"_s); + QCOMPARE(o->property("translate4"), u"s"_s); - // Unfortunately we cannot check the logging category with QTest::ignoreMessage - QTest::ignoreMessage(QtDebugMsg, "b 4.55"); - QTest::ignoreMessage(QtDebugMsg, "b 4.55"); - QTest::ignoreMessage(QtInfoMsg, "b 4.55"); - QTest::ignoreMessage(QtWarningMsg, "b 4.55"); - QTest::ignoreMessage(QtCriticalMsg, "b 4.55"); + QCOMPARE(o->property("translateNoop2"), u"s"_s); + QCOMPARE(o->property("translateNoop3"), u"s"_s); - const QRegularExpression re(u"QQmlComponentAttached\\(0x[0-9a-f]+\\) b 4\\.55"_s); - QTest::ignoreMessage(QtDebugMsg, re); - QTest::ignoreMessage(QtDebugMsg, re); - QTest::ignoreMessage(QtInfoMsg, re); - QTest::ignoreMessage(QtWarningMsg, re); - QTest::ignoreMessage(QtCriticalMsg, re); + QCOMPARE(o->property("tr1"), u"s"_s); + QCOMPARE(o->property("tr2"), u"s"_s); + QCOMPARE(o->property("tr3"), u"s"_s); - QTest::ignoreMessage(QtDebugMsg, "a undefined b false null 7"); - QTest::ignoreMessage(QtDebugMsg, ""); - QTest::ignoreMessage(QtDebugMsg, "4"); - QTest::ignoreMessage(QtDebugMsg, ""); + QCOMPARE(o->property("trNoop1"), u"s"_s); + QCOMPARE(o->property("trNoop2"), u"s"_s); - const QRegularExpression re2(u"QQmlComponentAttached\\(0x[0-9a-f]+\\)"_s); - QTest::ignoreMessage(QtDebugMsg, re2); + QCOMPARE(o->property("trId1"), u"s"_s); + QCOMPARE(o->property("trId2"), u"s"_s); - QScopedPointer<QObject> o(c.create()); - QVERIFY(!o.isNull()); + QCOMPARE(o->property("trIdNoop1"), u"s"_s); } -void tst_QmlCppCodegen::multiForeign() +void tst_QmlCppCodegen::trigraphs() { - QQmlEngine engine; - QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/multiforeign.qml"_s)); + QQmlEngine e; + QQmlComponent c(&e, QUrl(u"qrc:/qt/qml/TestTypes/trigraphs.qml"_s)); QVERIFY2(c.isReady(), qPrintable(c.errorString())); QScopedPointer<QObject> o(c.create()); - QVERIFY(!o.isNull()); - QCOMPARE(o->objectName(), u"not here and not there"_s); + QCOMPARE(o->objectName(), u"?""?= ?""?/ ?""?' ?""?( ?""?) ?""?! ?""?< ?""?> ?""?-"_s); } -void tst_QmlCppCodegen::namespaceWithEnum() +void tst_QmlCppCodegen::trivialSignalHandler() { QQmlEngine engine; - QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/namespaceWithEnum.qml"_s)); + QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/trivialSignalHandler.qml"_s)); QVERIFY2(c.isReady(), qPrintable(c.errorString())); QScopedPointer<QObject> o(c.create()); - QVERIFY(!o.isNull()); - QCOMPARE(o->property("i").toInt(), 2); + + QCOMPARE(o->property("a").toString(), u"no"_s); + QCOMPARE(o->property("b").toInt(), -1); + QCOMPARE(o->property("b").toDouble(), -1.0); + + o->setObjectName(u"yes"_s); + QCOMPARE(o->property("a").toString(), u"yes"_s); + QCOMPARE(o->property("b").toInt(), 5); + QCOMPARE(o->property("c").toDouble(), 2.5); } -void tst_QmlCppCodegen::enumProblems() +void tst_QmlCppCodegen::typePropagationLoop() { QQmlEngine engine; - QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/enumProblems.qml"_s)); - QVERIFY2(c.isReady(), qPrintable(c.errorString())); - QScopedPointer<QObject> outer(c.create()); - QVERIFY(!outer.isNull()); - QObject *inner = outer->property("o").value<QObject *>(); - QVERIFY(inner); - Foo *bar = inner->property("bar").value<Foo *>(); - QVERIFY(bar); - QCOMPARE(bar->type(), Foo::Component); + QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/typePropagationLoop.qml"_s)); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> o(c.create()); - Foo *fighter = inner->property("fighter").value<Foo *>(); - QVERIFY(fighter); - QCOMPARE(fighter->type(), Foo::Fighter); + QCOMPARE(o->property("j").toInt(), 3); } -void tst_QmlCppCodegen::enumConversion() +void tst_QmlCppCodegen::typePropertyClash() { QQmlEngine engine; - - QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/enumConversion.qml"_s)); + engine.rootContext()->setContextProperty(u"size"_s, 5); + QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/typePropertyClash.qml"_s)); QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> o(c.create()); + QVERIFY(!o.isNull()); + QCOMPARE(o->objectName(), u"Size: 5"_s); +} +void tst_QmlCppCodegen::typedArray() +{ + QQmlEngine engine; + const QUrl document(u"qrc:/qt/qml/TestTypes/typedArray.qml"_s); + QQmlComponent c(&engine, document); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); QScopedPointer<QObject> o(c.create()); QVERIFY(o); - QCOMPARE(o->property("test").toInt(), 0x04); - QCOMPARE(o->property("test_1").toBool(), true); + QDateTime date; + QVERIFY(qvariant_cast<QList<int>>(o->property("values2")).isEmpty()); + QCOMPARE(qvariant_cast<QList<int>>(o->property("values3")), + QList<int>({1, 2, 3, 4})); + QCOMPARE(qvariant_cast<QList<QDateTime>>(o->property("values4")), + QList<QDateTime>({date, date, date})); + { + const QList<double> actual + = qvariant_cast<QList<double>>(o->property("values5")); + const QList<double> expected + = QList<double>({1, 2, 3.4, 30, std::numeric_limits<double>::quiet_NaN(), 0}); + QCOMPARE(actual.size(), expected.size()); + for (qsizetype i = 0, end = actual.size(); i != end; ++i) { + if (std::isnan(expected[i])) + QVERIFY(std::isnan(actual[i])); + else + QCOMPARE(actual[i], expected[i]); + } + } + date = QDateTime::currentDateTime(); + o->setProperty("aDate", date); + QCOMPARE(qvariant_cast<QList<QDateTime>>(o->property("values4")), + QList<QDateTime>({date, date, date})); + + QQmlListProperty<QObject> values6 + = qvariant_cast<QQmlListProperty<QObject>>(o->property("values6")); + QCOMPARE(values6.count(&values6), 3); + for (int i = 0; i < 3; ++i) + QCOMPARE(values6.at(&values6, i), o.data()); + + QCOMPARE(o->property("inIntList").toInt(), 2); + QCOMPARE(qvariant_cast<QDateTime>(o->property("inDateList")), date); + QCOMPARE(o->property("inRealList").toDouble(), 30.0); + QCOMPARE(o->property("inCharList").toString(), QStringLiteral("f")); + + const QMetaObject *metaObject = o->metaObject(); + QMetaMethod method = metaObject->method(metaObject->indexOfMethod("stringAt10(QString)")); + QVERIFY(method.isValid()); + + // If LoadElement threw an exception the function would certainly return neither 10 nor 20. + int result = 0; + method.invoke( + o.data(), Q_RETURN_ARG(int, result), Q_ARG(QString, QStringLiteral("a"))); + QCOMPARE(result, 10); + method.invoke( + o.data(), Q_RETURN_ARG(int, result), Q_ARG(QString, QStringLiteral("aaaaaaaaaaa"))); + QCOMPARE(result, 20); } -void tst_QmlCppCodegen::storeElementSideEffects() +void tst_QmlCppCodegen::undefinedResets() { QQmlEngine engine; + QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/undefinedResets.qml"_s)); - QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/storeElementSideEffects.qml"_s)); - QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QVERIFY2(component.isReady(), qPrintable(component.errorString())); + QScopedPointer<QObject> rootObject(component.create()); + QVERIFY(rootObject); - QScopedPointer<QObject> o(c.create()); - QVERIFY(o); + Person *person = qobject_cast<Person *>(rootObject.data()); + QVERIFY(person); + QCOMPARE(person->shoeSize(), 0); + QCOMPARE(person->name(), u"Marge"_s); - const QJSValue prop = o->property("myItem").value<QJSValue>(); - QVERIFY(prop.isArray()); - QCOMPARE(prop.property(0).toInt(), 10); -}; + person->setShoeSize(11); -void tst_QmlCppCodegen::ambiguousSignals() + QCOMPARE(person->shoeSize(), 11); + QCOMPARE(person->name(), u"Bart"_s); + + person->setShoeSize(10); + QCOMPARE(person->shoeSize(), 10); + QCOMPARE(person->name(), u"Marge"_s); + + person->setName(u"no one"_s); + QCOMPARE(person->name(), u"no one"_s); + + person->setObjectName(u"the one"_s); + QCOMPARE(person->name(), u"Bart"_s); +} + +void tst_QmlCppCodegen::undefinedToDouble() { QQmlEngine engine; - QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/ambiguousSignals.qml"_s)); + QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/undefinedToDouble.qml"_s)); QVERIFY2(c.isReady(), qPrintable(c.errorString())); QScopedPointer<QObject> o(c.create()); QVERIFY(!o.isNull()); - QCOMPARE(o->objectName(), u"tomorrow"_s); - Person *p = qobject_cast<Person *>(o.data()); - QVERIFY(p); - emit p->ambiguous(12); - QCOMPARE(o->objectName(), u"12foo"_s); - emit p->ambiguous(); - QCOMPARE(o->objectName(), u"9foo"_s); + const QVariant d = o->property("d"); + QCOMPARE(d.metaType(), QMetaType::fromType<double>()); + QVERIFY(std::isnan(d.toDouble())); } -void tst_QmlCppCodegen::fileImportsContainCxxTypes() +void tst_QmlCppCodegen::unknownAttached() { QQmlEngine engine; - QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/usingCxxTypesFromFileImports.qml"_s)); - QVERIFY2(c.isReady(), qPrintable(c.errorString())); - QScopedPointer<QObject> o(c.create()); - QVERIFY(!o.isNull()); - QCOMPARE(o->objectName(), u"horst guenther"_s); + QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/unknownAttached.qml"_s)); + QVERIFY(c.isError()); } -void tst_QmlCppCodegen::lengthAccessArraySequenceCompat() +void tst_QmlCppCodegen::unknownParameter() { QQmlEngine engine; - QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/ArraySequenceLengthInterop.qml"_s)); + QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/unknownParameter.qml"_s)); + QVERIFY2(!component.isError(), component.errorString().toUtf8()); + QScopedPointer<QObject> object(component.create()); + QVERIFY(!object.isNull()); + QCOMPARE(object->property("cppProp").toInt(), 18); +} + +void tst_QmlCppCodegen::unstoredUndefined() +{ + QQmlEngine engine; + QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/unstoredUndefined.qml"_s)); QVERIFY2(c.isReady(), qPrintable(c.errorString())); QScopedPointer<QObject> o(c.create()); - QVERIFY(!o.isNull()); - QCOMPARE(o->property("length").toInt(), 100); + QCOMPARE(o->objectName(), u"NaN"_s); } -static QList<QString> convertToStrings(const QList<int> &ints) +void tst_QmlCppCodegen::unusedAttached() { - QList<QString> strings; - for (int i : ints) - strings.append(QString::number(i)); - return strings; + QQmlEngine engine; + QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/unusedAttached.qml"_s)); + QVERIFY2(!component.isError(), component.errorString().toUtf8()); + QScopedPointer<QObject> object(component.create()); + + const auto func = qmlAttachedPropertiesFunction( + object.data(), QMetaType::fromName("QQuickKeyNavigationAttached*").metaObject()); + QObject *attached = qmlAttachedPropertiesObject(object.data(), func); + const QVariant prop = attached->property("priority"); + QVERIFY(prop.isValid()); + QCOMPARE(QByteArray(prop.metaType().name()), "QQuickKeyNavigationAttached::Priority"); + bool ok = false; + QCOMPARE(prop.toInt(&ok), 0); + QVERIFY(ok); } -void tst_QmlCppCodegen::numbersInJsPrimitive() +void tst_QmlCppCodegen::urlString() { QQmlEngine engine; - QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/numbersInJsPrimitive.qml"_s)); - QVERIFY2(c.isReady(), qPrintable(c.errorString())); - QScopedPointer<QObject> o(c.create()); - QVERIFY(!o.isNull()); + QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/urlString.qml"_s)); - const QList<int> zeroes = {0, 0, 0, 0}; - const QList<int> written = {39, 40, 41, 42}; - const QList<int> stored = {1334, 1335, 1336, 1337}; - QStringList asStrings(4); + QVERIFY2(component.isReady(), qPrintable(component.errorString())); + QScopedPointer<QObject> rootObject(component.create()); + QVERIFY(rootObject); - for (int i = 0; i < 4; ++i) { - QMetaObject::invokeMethod( - o.data(), "readValueAsString", - Q_RETURN_ARG(QString, asStrings[i]), Q_ARG(int, i)); + QCOMPARE(qvariant_cast<QUrl>(rootObject->property("c")), QUrl(u"http://dddddd.com"_s)); + QCOMPARE(qvariant_cast<QUrl>(rootObject->property("d")), QUrl(u"http://aaaaaa.com"_s)); + QCOMPARE(qvariant_cast<QUrl>(rootObject->property("e")), QUrl(u"http://a112233.de"_s)); + QCOMPARE(rootObject->objectName(), QLatin1String("http://dddddd.com")); +} + +void tst_QmlCppCodegen::valueTypeBehavior() +{ + QQmlEngine engine; + + { + const QUrl url(u"qrc:/qt/qml/TestTypes/valueTypeCopy.qml"_s); + QQmlComponent c(&engine, url); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QTest::ignoreMessage(QtWarningMsg, bindingLoopMessage(url, 'e')); + QTest::ignoreMessage(QtWarningMsg, bindingLoopMessage(url, 'f')); + QScopedPointer<QObject> o(c.create()); + QVERIFY(!o.isNull()); + QCOMPARE(o->property("e").toDouble(), 45.0); + QCOMPARE(o->property("f").toDouble(), 1.0); } - QCOMPARE(asStrings, convertToStrings(zeroes)); - QMetaObject::invokeMethod(o.data(), "writeValues"); - for (int i = 0; i < 4; ++i) { - QMetaObject::invokeMethod( - o.data(), "readValueAsString", - Q_RETURN_ARG(QString, asStrings[i]), Q_ARG(int, i)); + { + const QUrl url(u"qrc:/qt/qml/TestTypes/valueTypeReference.qml"_s); + QQmlComponent c(&engine, url); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QTest::ignoreMessage(QtWarningMsg, bindingLoopMessage(url, 'e')); + QTest::ignoreMessage(QtWarningMsg, bindingLoopMessage(url, 'f')); + QScopedPointer<QObject> o(c.create()); + QVERIFY(!o.isNull()); + QVERIFY(qIsNaN(o->property("e").toDouble())); + QCOMPARE(o->property("f").toDouble(), 5.0); } - QCOMPARE(asStrings, convertToStrings(written)); - QMetaObject::invokeMethod(o.data(), "storeValues"); - for (int i = 0; i < 4; ++i) { - QMetaObject::invokeMethod( - o.data(), "readValueAsString", - Q_RETURN_ARG(QString, asStrings[i]), Q_ARG(int, i)); + { + const QUrl url(u"qrc:/qt/qml/TestTypes/valueTypeDefault.qml"_s); + QQmlComponent c(&engine, url); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QTest::ignoreMessage(QtWarningMsg, bindingLoopMessage(url, 'e')); + QTest::ignoreMessage(QtWarningMsg, bindingLoopMessage(url, 'f')); + QScopedPointer<QObject> o(c.create()); + QVERIFY(!o.isNull()); + QVERIFY(qIsNaN(o->property("e").toDouble())); + QCOMPARE(o->property("f").toDouble(), 5.0); + } + + { + const QUrl url(u"qrc:/qt/qml/TestTypes/valueTypeCast.qml"_s); + QQmlComponent c(&engine, url); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer o(c.create()); + QVERIFY(!o.isNull()); + QCOMPARE(o->property("x"), 10); + + QTest::ignoreMessage( + QtWarningMsg, + qPrintable(url.toString() + + u":8: TypeError: Cannot read property 'x' of undefined"_s)); + o->setProperty("v", QLatin1String("not a rect")); + + // If the binding throws an exception, the value doesn't change. + QCOMPARE(o->property("x"), 10); + + QCOMPARE(o->property("tv3"), 5); + QCOMPARE(o->property("tc3"), 5); + QCOMPARE(o->property("tc6"), QVariant()); + QCOMPARE(o->property("tc7"), QVariant()); + QCOMPARE(o->property("tc8"), 2); + + // The default greeting is never applied because undefined can be coerced to string + QCOMPARE(o->property("greeting1"), QLatin1String("undefined")); + QCOMPARE(o->property("greeting2"), QLatin1String("Custom Greeting")); } - QCOMPARE(asStrings, convertToStrings(stored)); } -void tst_QmlCppCodegen::infinitiesToInt() +void tst_QmlCppCodegen::valueTypeLists() { QQmlEngine engine; - - QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/infinitiesToInt.qml"_s)); + QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/valueTypeLists.qml"_s)); QVERIFY2(c.isReady(), qPrintable(c.errorString())); QScopedPointer<QObject> o(c.create()); - QVERIFY(!o.isNull()); - const char *props[] = {"a", "b", "c"}; - for (const char *prop : props) { - const QVariant i = o->property(prop); - QCOMPARE(i.metaType(), QMetaType::fromType<int>()); - bool ok = false; - QCOMPARE(i.toInt(&ok), 0); - QVERIFY(ok); - } + QCOMPARE(qvariant_cast<QRectF>(o->property("rectInBounds")), QRectF(1, 2, 3, 4)); + QVERIFY(o->metaObject()->indexOfProperty("rectOutOfBounds") > 0); + QVERIFY(!o->property("rectOutOfBounds").isValid()); + + QCOMPARE(qvariant_cast<QString>(o->property("stringInBounds")), QStringLiteral("bbb")); + QVERIFY(o->metaObject()->indexOfProperty("stringOutOfBounds") > 0); + QVERIFY(!o->property("stringOutOfBounds").isValid()); + + QCOMPARE(qvariant_cast<int>(o->property("intInBounds")), 7); + QVERIFY(o->metaObject()->indexOfProperty("intOutOfBounds") > 0); + QVERIFY(!o->property("intOutOfBounds").isValid()); + + QCOMPARE(qvariant_cast<QString>(o->property("charInBounds")), QStringLiteral("d")); + QVERIFY(o->metaObject()->indexOfProperty("charOutOfBounds") > 0); + QVERIFY(!o->property("charOutOfBounds").isValid()); } -void tst_QmlCppCodegen::equalityVarAndNonStorable() +void tst_QmlCppCodegen::valueTypeProperty() { QQmlEngine engine; + QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/valueTypeProperty.qml"_s)); + QVERIFY2(component.isReady(), component.errorString().toUtf8()); + QScopedPointer<QObject> object(component.create()); - QQmlComponent c1(&engine, QUrl(u"qrc:/qt/qml/TestTypes/equalityVarAndNonStorable.qml"_s)); - QVERIFY2(c1.isReady(), qPrintable(c1.errorString())); + QFont font = qvariant_cast<QFont>(object->property("font")); + QCOMPARE(object->property("foo").toString(), font.family()); + font.setFamily(u"Bar"_s); + object->setProperty("font", QVariant::fromValue(font)); + QCOMPARE(object->property("foo").toString(), u"Bar"_s); +} - QScopedPointer<QObject> object(c1.create()); - QVERIFY(!object.isNull() && !c1.isError()); - QVERIFY(!object->property("aIsNull").toBool()); - QVERIFY(object->property("aIsNotNull").toBool()); - QVERIFY(object->property("aIsNotUndefined").toBool()); - QVERIFY(object->property("objectIsNotNull").toBool()); - QVERIFY(!object->property("typedArrayIsNull").toBool()); - QVERIFY(object->property("isUndefined").toBool()); - QVERIFY(!object->property("derivedIsNull").toBool()); +void tst_QmlCppCodegen::variantMapLookup() +{ + QQmlEngine engine; + QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/variantMapLookup.qml"_s)); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> o(c.create()); + QVERIFY(!o.isNull()); + QCOMPARE(o->property("i"), 42); +} - QVERIFY(object->property("primitiveIsNull").toBool()); - QVERIFY(object->property("primitiveIsDefined").toBool()); - QVERIFY(object->property("primitiveIsUndefined").toBool()); +void tst_QmlCppCodegen::variantReturn() +{ + QQmlEngine e; + QQmlComponent c(&e, QUrl(u"qrc:/qt/qml/TestTypes/variantReturn.qml"_s)); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); - QVERIFY(object->property("jsValueIsNull").toBool()); - QVERIFY(object->property("jsValueIsDefined").toBool()); - QVERIFY(object->property("jsValueIsUndefined").toBool()); + QScopedPointer<QObject> o(c.create()); + QVERIFY(!o.isNull()); - QVERIFY(object->property("nullVarIsUndefined").toBool()); - QVERIFY(object->property("nullIsUndefined").toBool()); - QVERIFY(object->property("nullVarIsNull").toBool()); - QVERIFY(object->property("nullIsNotUndefined").toBool()); -}; + QObject *a = o->property("a").value<QObject *>(); + QVERIFY(a); + const QVariant x = a->property("x"); + const QMetaObject *meta = x.metaType().metaObject(); + QVERIFY(meta); + const QMetaProperty property = meta->property(meta->indexOfProperty("timeIndex")); + QVERIFY(property.isValid()); + const QVariant timeIndex = property.readOnGadget(x.data()); + QCOMPARE(timeIndex.metaType(), QMetaType::fromType<qsizetype>()); + QCOMPARE(timeIndex.value<qsizetype>(), qsizetype(1)); -void tst_QmlCppCodegen::equalityQObjects() + QObject *b = o->property("b").value<QObject *>(); + QVERIFY(b); + QCOMPARE(b->property("z").toInt(), 2); +} + +void tst_QmlCppCodegen::variantlist() { QQmlEngine engine; - QQmlComponent c1(&engine, QUrl(u"qrc:/qt/qml/TestTypes/equalityQObjects.qml"_s)); - QVERIFY2(c1.isReady(), qPrintable(c1.errorString())); - QScopedPointer<QObject> object(c1.create()); - QVERIFY(!object.isNull() && !c1.isError()); - - QVERIFY(object->property("derivedIsNotNull").toBool()); - QVERIFY(object->property("nullObjectIsNull").toBool()); - QVERIFY(object->property("nonNullObjectIsNotNull").toBool()); - QVERIFY(object->property("compareSameObjects").toBool()); - QVERIFY(object->property("compareDifferentObjects").toBool()); - QVERIFY(object->property("compareObjectWithNullObject").toBool()); + QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/variantlist.qml"_s)); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> o(c.create()); + QVERIFY(o); - QVERIFY(object->property("nonStrict_derivedIsNotNull").toBool()); - QVERIFY(object->property("nonStrict_nullObjectIsNull").toBool()); - QVERIFY(object->property("nonStrict_nonNullObjectIsNotNull").toBool()); - QVERIFY(object->property("nonStrict_compareSameObjects").toBool()); - QVERIFY(object->property("nonStrict_compareDifferentObjects").toBool()); - QVERIFY(object->property("nonStrict_compareObjectWithNullObject").toBool()); + const QVariantList things = qvariant_cast<QVariantList>(o->property("things")); + QCOMPARE(things.size(), 2); + QCOMPARE(things[0].toString(), u"thing"_s); + QCOMPARE(things[1].toInt(), 30); } -void tst_QmlCppCodegen::dateConversions() +void tst_QmlCppCodegen::variantMap() { QQmlEngine engine; - QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/dateConversions.qml"_s)); + QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/variantMap.qml"_s)); QVERIFY2(c.isReady(), qPrintable(c.errorString())); QScopedPointer<QObject> o(c.create()); + QVERIFY(o); - Druggeljug *ref = engine.singletonInstance<Druggeljug *>("TestTypes", "Druggeljug"); - - const QDateTime refDate = engine.coerceValue<QDate, QDateTime>(ref->myDate()); - const QDateTime refTime = engine.coerceValue<QTime, QDateTime>(ref->myTime()); - - QCOMPARE(o->property("date").value<QDateTime>(), refDate); - QCOMPARE(o->property("time").value<QDateTime>(), refTime); - - QCOMPARE(o->property("dateString").toString(), (engine.coerceValue<QDateTime, QString>(refDate))); - QCOMPARE(o->property("timeString").toString(), (engine.coerceValue<QDateTime, QString>(refTime))); + QCOMPARE(o->objectName(), "a b"_L1); + QCOMPARE(o->property("r"), QVariant::fromValue(QRectF(12, 13, 14, 15))); - QMetaObject::invokeMethod(o.data(), "shuffle"); + const QVariantMap expected = QVariantMap { + { u"1"_s, QVariant::fromValue<std::nullptr_t>(nullptr) }, + { u"19"_s, QVariant::fromValue(u"19"_s) }, + { u"25"_s, QVariant() } + }; - QCOMPARE(ref->myDate(), (engine.coerceValue<QDateTime, QDate>(refDate))); - QCOMPARE(ref->myTime(), (engine.coerceValue<QDateTime, QTime>(refTime))); + QCOMPARE(o->property("v").toMap(), expected); +} - const QDate date = ref->myDate(); - const QTime time = ref->myTime(); +void tst_QmlCppCodegen::voidConversion() +{ + QQmlEngine engine; + const QUrl url(u"qrc:/qt/qml/TestTypes/voidConversion.qml"_s); + QQmlComponent c(&engine, url); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); - QCOMPARE(o->property("dateString").toString(), (engine.coerceValue<QDate, QString>(date))); - QCOMPARE(o->property("timeString").toString(), (engine.coerceValue<QTime, QString>(time))); + QTest::ignoreMessage( + QtWarningMsg, + qPrintable(url.toString() + u":8: Error: Cannot assign [undefined] to QPointF"_s)); - QMetaObject::invokeMethod(o.data(), "fool"); + QScopedPointer<QObject> o(c.create()); + QVERIFY(o); - QCOMPARE(ref->myDate(), (engine.coerceValue<QTime, QDate>(time))); - QCOMPARE(ref->myTime(), (engine.coerceValue<QDate, QTime>(date))); + QCOMPARE(o->property("p"), QPointF(20, 10)); } -static QRegularExpression bindingLoopMessage(const QUrl &url, char var) +void tst_QmlCppCodegen::voidFunction() { - // The actual string depends on how many times QObject* was registered with what parameters. - return QRegularExpression( - "%1:4:1: QML [^:]+: Binding loop detected for property \"%2\""_L1 - .arg(url.toString()).arg(QLatin1Char(var))); + QQmlEngine engine; + QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/voidfunction.qml"_s)); + QVERIFY2(component.isReady(), component.errorString().toUtf8()); + QScopedPointer<QObject> object(component.create()); + QVERIFY(!object.isNull()); + QVERIFY(object->objectName().isEmpty()); + object->metaObject()->invokeMethod(object.data(), "doesNotReturnValue"); + QCOMPARE(object->objectName(), u"barbar"_s); } -void tst_QmlCppCodegen::valueTypeBehavior() +void tst_QmlCppCodegen::writeBack() { QQmlEngine engine; + QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/writeback.qml"_s)); + QVERIFY2(component.isReady(), qPrintable(component.errorString())); + QScopedPointer<QObject> object(component.create()); + QVERIFY(!object.isNull()); - const QUrl copy(u"qrc:/qt/qml/TestTypes/valueTypeCopy.qml"_s); + Person *person = qobject_cast<Person *>(object.data()); + QVERIFY(person); + QCOMPARE(person->area(), QRectF(4, 5, 16, 17)); + QCOMPARE(person->property("inner").toInt(), 99); - QQmlComponent c1(&engine, copy); - QVERIFY2(c1.isReady(), qPrintable(c1.errorString())); - QTest::ignoreMessage(QtWarningMsg, bindingLoopMessage(copy, 'e')); - QTest::ignoreMessage(QtWarningMsg, bindingLoopMessage(copy, 'f')); - QScopedPointer<QObject> o1(c1.create()); - QVERIFY(!o1.isNull()); - QCOMPARE(o1->property("e").toDouble(), 45.0); - QCOMPARE(o1->property("f").toDouble(), 1.0); + Person *shadowable = person->property("shadowable").value<Person *>(); + QVERIFY(shadowable); + QCOMPARE(shadowable->area(), QRectF(40, 50, 16, 17)); - const QUrl reference(u"qrc:/qt/qml/TestTypes/valueTypeReference.qml"_s); - QQmlComponent c2(&engine, reference); - QVERIFY2(c2.isReady(), qPrintable(c2.errorString())); - QTest::ignoreMessage(QtWarningMsg, bindingLoopMessage(reference, 'e')); - QTest::ignoreMessage(QtWarningMsg, bindingLoopMessage(reference, 'f')); - QScopedPointer<QObject> o2(c2.create()); - QVERIFY(!o2.isNull()); - QVERIFY(qIsNaN(o2->property("e").toDouble())); - QCOMPARE(o2->property("f").toDouble(), 5.0); + QCOMPARE(person->property("ints"), QVariant::fromValue(QList<int>({12, 22, 2, 1, 0, 0, 33}))); } -void tst_QmlCppCodegen::invisibleSingleton() +void tst_QmlCppCodegen::writeVariantMap() { QQmlEngine engine; - const QUrl copy(u"qrc:/qt/qml/TestTypes/hidden/Main.qml"_s); - QQmlComponent c(&engine, copy); - QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/StringBuilderTestTypes/writeVariantMap.qml"_s)); + QVERIFY2(component.isReady(), qPrintable(component.errorString())); + QScopedPointer<QObject> object(component.create()); + QVERIFY(!object.isNull()); + + const QVariantMap v = object->property("data").toMap(); + QCOMPARE(v.size(), 1); + const QVariant textPlain = v[u"text/plain"_s]; + QCOMPARE(textPlain.metaType(), QMetaType::fromType<QString>()); + QCOMPARE(textPlain.toString(), u"%Drag Me%"_s); - QTest::ignoreMessage( - QtWarningMsg, - "qrc:/qt/qml/TestTypes/hidden/Main.qml:4:5: " - "Unable to assign [undefined] to QColor"); - QScopedPointer<QObject> o(c.create()); - QVERIFY(!o.isNull()); - QCOMPARE(o->property("c"), QVariant(QMetaType::fromName("QColor"))); } QTEST_MAIN(tst_QmlCppCodegen) |