diff options
150 files changed, 2570 insertions, 874 deletions
diff --git a/README.pyside6.md b/README.pyside6.md index 8c70b1c8f..9178660d5 100644 --- a/README.pyside6.md +++ b/README.pyside6.md @@ -88,7 +88,7 @@ and [join our community](https://wiki.qt.io/Qt_for_Python#Community)! ### Licensing -PySide6 is available under both Open Source (LGPLv3/GPLv2) and commercial +PySide6 is available under both Open Source (LGPLv3/GPLv3) and commercial license. Using PyPi is the recommended installation source, because the content of the wheels is valid for both cases. For more information, refer to the [Qt Licensing page](https://www.qt.io/licensing/). diff --git a/README.pyside6_addons.md b/README.pyside6_addons.md index 3247a550d..e6044c4a4 100644 --- a/README.pyside6_addons.md +++ b/README.pyside6_addons.md @@ -65,7 +65,7 @@ and [join our community](https://wiki.qt.io/Qt_for_Python#Community)! ### Licensing -PySide6 is available under both Open Source (LGPLv3/GPLv2) and commercial +PySide6 is available under both Open Source (LGPLv3/GPLv3) and commercial license. Using PyPi is the recommended installation source, because the content of the wheels is valid for both cases. For more information, refer to the [Qt Licensing page](https://www.qt.io/licensing/). diff --git a/README.pyside6_essentials.md b/README.pyside6_essentials.md index ef0376e43..7f96c19b1 100644 --- a/README.pyside6_essentials.md +++ b/README.pyside6_essentials.md @@ -51,7 +51,7 @@ and [join our community](https://wiki.qt.io/Qt_for_Python#Community)! ### Licensing -PySide6 is available under both Open Source (LGPLv3/GPLv2) and commercial +PySide6 is available under both Open Source (LGPLv3/GPLv3) and commercial license. Using PyPi is the recommended installation source, because the content of the wheels is valid for both cases. For more information, refer to the [Qt Licensing page](https://www.qt.io/licensing/). diff --git a/README.pyside6_examples.md b/README.pyside6_examples.md index ffa7d83d5..15e318151 100644 --- a/README.pyside6_examples.md +++ b/README.pyside6_examples.md @@ -28,7 +28,7 @@ and [join our community](https://wiki.qt.io/Qt_for_Python#Community)! ### Licensing -PySide6 is available under both Open Source (LGPLv3/GPLv2) and commercial +PySide6 is available under both Open Source (LGPLv3/GPLv3) and commercial licenses. Using PyPi is the recommended installation source, because the content of the wheels is valid for both cases. For more information, refer to the [Qt Licensing page](https://www.qt.io/licensing/). diff --git a/build_scripts/__init__.py b/build_scripts/__init__.py index de28b82b6..128bb2394 100644 --- a/build_scripts/__init__.py +++ b/build_scripts/__init__.py @@ -17,7 +17,10 @@ PYSIDE_UNIX_BIN_TOOLS = ["lupdate", "lrelease", "qmllint", "qmlformat", - "qmlls"] + "qmlls", + "qsb", + "balsam", + "balsamui"] # tools that are bundled as .app in macOS, but are normal executables in Linux and Windows PYSIDE_UNIX_BUNDLED_TOOLS = ["assistant", diff --git a/build_scripts/wheel_files.py b/build_scripts/wheel_files.py index 0d3072773..2112bba9a 100644 --- a/build_scripts/wheel_files.py +++ b/build_scripts/wheel_files.py @@ -774,6 +774,8 @@ def module_QtQuick3D() -> ModuleData: data.qtlib.extend(_qtlib) data.metatypes.extend(_metatypes) data.extra_files.append("Qt/plugins/assetimporters/libassimp*") + data.extra_files.append("qsb*") + data.extra_files.append("balsam*") return data @@ -995,6 +997,8 @@ def module_QtSerialBus() -> ModuleData: def module_QtVirtualKeyboard() -> ModuleData: data = ModuleData("VirtualKeyboard") data.plugins.append("virtualkeyboard") + data.qtlib.append("libQt6VirtualKeyboardSettings") + return data diff --git a/coin/instructions/find_path_to_msvc_compiler.yaml b/coin/instructions/find_path_to_msvc_compiler.yaml index 8f9bed82f..015a86eab 100644 --- a/coin/instructions/find_path_to_msvc_compiler.yaml +++ b/coin/instructions/find_path_to_msvc_compiler.yaml @@ -18,6 +18,13 @@ instructions: condition: property property: host.compiler equals_value: MSVC2019 + - type: EnvironmentVariable + variableName: VC_SCRIPT + variableValue: "\\Program Files\\Microsoft Visual Studio\\2022\\Professional\\VC\\Auxiliary\\Build\\vcvarsall.bat" + enable_if: + condition: property + property: host.compiler + equals_value: MSVC2022 - type: WriteFile fileContents: "call \"{{.Env.VC_SCRIPT}}\" {{.Env.TARGET_ARCHITECTURE}} \r\ncmd /c %*" filename: "c:\\users\\qt\\MSVC.bat" diff --git a/doc/changelogs/changes-6.7.1 b/doc/changelogs/changes-6.7.1 new file mode 100644 index 000000000..66263ed80 --- /dev/null +++ b/doc/changelogs/changes-6.7.1 @@ -0,0 +1,61 @@ +Qt for Python 6.7.1 is a bug-fix release. + +For more details, refer to the online documentation included in this +distribution. The documentation is also available online: + +https://doc.qt.io/qtforpython/ + +Some of the changes listed in this file include issue tracking numbers +corresponding to tasks in the Qt Bug Tracker: + +https://bugreports.qt.io/ + +Each of these identifiers can be entered in the bug tracker to obtain more +information about a particular change. + +**************************************************************************** +* PySide6 * +**************************************************************************** + + - [PYSIDE-487] A number of missing classes have been added. + - [PYSIDE-2629] Tooling: pyside6-qsb, pyside6-balsam and pyside6-balsamui + have been added. + - [PYSIDE-2644] QtAsyncio: An issue with tasks with loop not cancelling + has been fixed. + - [PYSIDE-2663] A crash when browsing https://outlook.com has been fixed. + - [PYSIDE-2665] A syntax error in the pyi-files has been fixed. + - [PYSIDE-2668] The comparison of QOperatingSystemVersion.OSType has been + fixed. + - [PYSIDE-2675] Lazy Load: An issue with polymorphic classes has been + fixed. + - [PYSIDE-2676] A crash with Python 3.12 when creating classes from + meta classes has been fixed. + - [PYSIDE-2685] An error in the pyi-files related to the import of + NoneType has been fixed. + - [PYSIDE-2686] Missing imports for types of return values + have been added to the pyi-files. + - [PYSIDE-2698] A crash when querying the size of QtQml.ListProperty + has been fixed and documentation for QtQml.ListProperty + has been added. + - [PYSIDE-2705] Warnings about failures of QObject.disconnect() can + now be suppressed. + - [PYSIDE-2709] A bug using legacy qmlRegisterType() for class hierarchies + has been fixed. + - [QTBUG-123997] Multimedia: The renaming of the namespace QAudio to + QtAudio has been undone following a revert in Qt. + +**************************************************************************** +* Shiboken6 * +**************************************************************************** + + - [PYSIDE-2590] An attribute for global inline namespace scopes has been + added. + - [PYSIDE-2602] Generate Python override code for added virtuals + - [PYSIDE-2602] Support for virtual functions with return type + modifications has been added and the function + QWebEnginePage.javaScriptPrompt() + has been fixed accordingly. + - [PYSIDE-2675] A code snippet placeholder for the base class for + polymorphic-id-expressions has been added, fixing + a potentially undefined behavior when using the + derived classes. diff --git a/examples/async/eratosthenes/eratosthenes_asyncio.py b/examples/async/eratosthenes/eratosthenes_asyncio.py index 598d9b4bd..c5d7fc2fe 100644 --- a/examples/async/eratosthenes/eratosthenes_asyncio.py +++ b/examples/async/eratosthenes/eratosthenes_asyncio.py @@ -131,4 +131,4 @@ if __name__ == "__main__": main_window.show() - QtAsyncio.run(eratosthenes.start()) + QtAsyncio.run(eratosthenes.start(), handle_sigint=True) diff --git a/examples/async/minimal/minimal_asyncio.py b/examples/async/minimal/minimal_asyncio.py index 1b72f44c1..a6c4708b3 100644 --- a/examples/async/minimal/minimal_asyncio.py +++ b/examples/async/minimal/minimal_asyncio.py @@ -37,4 +37,4 @@ if __name__ == "__main__": main_window = MainWindow() main_window.show() - QtAsyncio.run() + QtAsyncio.run(handle_sigint=True) diff --git a/examples/qml/tutorials/extending-qml-advanced/properties/doc/properties.rst b/examples/qml/tutorials/extending-qml-advanced/properties/doc/properties.rst index 9337ad2ab..16924cdcd 100644 --- a/examples/qml/tutorials/extending-qml-advanced/properties/doc/properties.rst +++ b/examples/qml/tutorials/extending-qml-advanced/properties/doc/properties.rst @@ -75,11 +75,11 @@ The class contains a member to store the celebrant object, and also a list member storing the Person instances. In QML, the type of a list properties - and the guests property is a list of -people - are all of type ListProperty. ListProperty is simple value -type that contains a set of functions. QML calls these functions -whenever it needs to read from, write to or otherwise interact with -the list. In addition to concrete lists like the people list used in this -example, the use of QQmlListProperty allows for "virtual lists" and other advanced +people - are all of type :class:`~PySide6.QtQml.ListProperty`. +``ListProperty`` is a simple value type that contains a set of functions. +QML calls these functions whenever it needs to read from, write to or otherwise +interact with the list. In addition to concrete lists like the people list used in this +example, the use of ``ListProperty`` allows for "virtual lists" and other advanced scenarios. Running the Example diff --git a/examples/qml/tutorials/extending-qml/chapter5-listproperties/doc/chapter5-listproperties.rst b/examples/qml/tutorials/extending-qml/chapter5-listproperties/doc/chapter5-listproperties.rst index 90cb41107..a98f18c81 100644 --- a/examples/qml/tutorials/extending-qml/chapter5-listproperties/doc/chapter5-listproperties.rst +++ b/examples/qml/tutorials/extending-qml/chapter5-listproperties/doc/chapter5-listproperties.rst @@ -15,8 +15,9 @@ have a ``slices`` property that accepts a list of ``PieSlice`` items: :lines: 4-32 To do this, we replace the ``pieSlice`` property in ``PieChart`` with a -``slices`` property, declared as a class variable of the ``QQmlListProperty`` -type. The ``QQmlListProperty`` class enables the creation of list properties in +``slices`` property, declared as a class variable of the +:class:`~PySide6.QtQml.ListProperty` type. +The ``ListProperty`` class enables the creation of list properties in QML extensions. We replace the ``pieSlice()`` function with a ``slices()`` function that returns a list of slices, and add an internal ``appendSlice()`` function (discussed below). We also use a list to store the internal list of @@ -31,13 +32,13 @@ slices as ``_slices``: :lines: 75-79 Although the ``slices`` property does not have an associated setter, it is -still modifiable because of the way ``QQmlListProperty`` works. We indicate +still modifiable because of the way ``ListProperty`` works. We indicate that the internal ``PieChart.appendSlice()`` function is to be called whenever a request is made from QML to add items to the list. The ``appendSlice()`` function simply sets the parent item as before, and adds the new item to the ``_slices`` list. As you can see, the append function for -a ``QQmlListProperty`` is called with two arguments: the list property, and the +a ``ListProperty`` is called with two arguments: the list property, and the item that is to be appended. The ``PieSlice`` class has also been modified to include ``fromAngle`` and diff --git a/examples/quickcontrols/contactslist/main.py b/examples/quickcontrols/contactslist/main.py index 6c934ec89..d501bbdb4 100644 --- a/examples/quickcontrols/contactslist/main.py +++ b/examples/quickcontrols/contactslist/main.py @@ -23,5 +23,6 @@ if __name__ == '__main__': if not engine.rootObjects(): sys.exit(-1) + ex = app.exec() del engine - sys.exit(app.exec()) + sys.exit(ex) diff --git a/examples/quickcontrols/filesystemexplorer/main.py b/examples/quickcontrols/filesystemexplorer/main.py index 1477023e9..8fad951cb 100644 --- a/examples/quickcontrols/filesystemexplorer/main.py +++ b/examples/quickcontrols/filesystemexplorer/main.py @@ -22,7 +22,7 @@ if __name__ == '__main__': app.setOrganizationName("QtProject") app.setApplicationName("File System Explorer") app.setApplicationVersion(qVersion()) - app.setWindowIcon(QIcon("FileSystemModule/icons/app_icon.svg")) + app.setWindowIcon(QIcon(sys.path[0] + "/FileSystemModule/icons/app_icon.svg")) parser = QCommandLineParser() parser.setApplicationDescription("Qt Filesystemexplorer Example") diff --git a/requirements.txt b/requirements.txt index b89a895ad..65380e93b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,6 +11,3 @@ numpy==1.26.3; python_version >= '3.9' # For examples PyOpenGL - -# For tests -pyinstaller==3.6; platform_machine != 'aarch64' diff --git a/sources/pyside-tools/CMakeLists.txt b/sources/pyside-tools/CMakeLists.txt index d8a353300..e629ec570 100644 --- a/sources/pyside-tools/CMakeLists.txt +++ b/sources/pyside-tools/CMakeLists.txt @@ -44,7 +44,10 @@ else() "${TOOLS_PATH}/lupdate${CMAKE_EXECUTABLE_SUFFIX}" "${TOOLS_PATH}/qmllint${CMAKE_EXECUTABLE_SUFFIX}" "${TOOLS_PATH}/qmlformat${CMAKE_EXECUTABLE_SUFFIX}" - "${TOOLS_PATH}/qmlls${CMAKE_EXECUTABLE_SUFFIX}") + "${TOOLS_PATH}/qmlls${CMAKE_EXECUTABLE_SUFFIX}" + "${TOOLS_PATH}/qsb${CMAKE_EXECUTABLE_SUFFIX}" + "${TOOLS_PATH}/balsam${CMAKE_EXECUTABLE_SUFFIX}" + "${TOOLS_PATH}/balsamui${CMAKE_EXECUTABLE_SUFFIX}") if (APPLE) list(APPEND directories "${TOOLS_PATH}/Assistant.app" diff --git a/sources/pyside-tools/project.py b/sources/pyside-tools/project.py index 5367e75b7..3706a2985 100644 --- a/sources/pyside-tools/project.py +++ b/sources/pyside-tools/project.py @@ -27,7 +27,7 @@ from argparse import ArgumentParser, RawTextHelpFormatter from project import (QmlProjectData, check_qml_decorators, is_python_file, QMLDIR_FILE, MOD_CMD, METATYPES_JSON_SUFFIX, - TRANSLATION_SUFFIX, + SHADER_SUFFIXES, TRANSLATION_SUFFIX, requires_rebuild, run_command, remove_path, ProjectData, resolve_project_file, new_project, ProjectType, ClOptions) @@ -49,6 +49,7 @@ LRELEASE_CMD = "pyside6-lrelease" LUPDATE_CMD = "pyside6-lupdate" QMLTYPEREGISTRAR_CMD = "pyside6-qmltyperegistrar" QMLLINT_CMD = "pyside6-qmllint" +QSB_CMD = "pyside6-qsb" DEPLOY_CMD = "pyside6-deploy" NEW_PROJECT_TYPES = {"new-quick": ProjectType.QUICK, @@ -143,6 +144,11 @@ class Project: cmd = [LRELEASE_CMD, os.fspath(file), "-qm", qm_file] return ([Path(qm_file)], cmd) + if file.suffix in SHADER_SUFFIXES: + qsb_file = f"{file.parent}/{file.stem}.qsb" + cmd = [QSB_CMD, "-o", qsb_file, os.fspath(file)] + return ([Path(qsb_file)], cmd) + return ([], None) def _regenerate_qmldir(self): diff --git a/sources/pyside-tools/project/__init__.py b/sources/pyside-tools/project/__init__.py index 9ac8de813..e57a9ff88 100644 --- a/sources/pyside-tools/project/__init__.py +++ b/sources/pyside-tools/project/__init__.py @@ -16,6 +16,7 @@ QT_MODULES = "QT_MODULES" METATYPES_JSON_SUFFIX = "metatypes.json" TRANSLATION_SUFFIX = ".ts" +SHADER_SUFFIXES = ".vert", ".frag" class Singleton(type): diff --git a/sources/pyside-tools/pyside_tool.py b/sources/pyside-tools/pyside_tool.py index 7daacc22d..b369be8a2 100644 --- a/sources/pyside-tools/pyside_tool.py +++ b/sources/pyside-tools/pyside_tool.py @@ -227,5 +227,17 @@ def android_deploy(): pyside_script_wrapper("android_deploy.py") +def qsb(): + qt_tool_wrapper("qsb", sys.argv[1:]) + + +def balsam(): + qt_tool_wrapper("balsam", sys.argv[1:]) + + +def balsamui(): + qt_tool_wrapper("balsamui", sys.argv[1:]) + + if __name__ == "__main__": main() diff --git a/sources/pyside6/PySide6/Qt3DAnimation/CMakeLists.txt b/sources/pyside6/PySide6/Qt3DAnimation/CMakeLists.txt index 45a6792cf..d9169924e 100644 --- a/sources/pyside6/PySide6/Qt3DAnimation/CMakeLists.txt +++ b/sources/pyside6/PySide6/Qt3DAnimation/CMakeLists.txt @@ -19,6 +19,7 @@ ${Qt3DAnimation_GEN_DIR}/qt3danimation_qanimationcliploader_wrapper.cpp ${Qt3DAnimation_GEN_DIR}/qt3danimation_qanimationcontroller_wrapper.cpp ${Qt3DAnimation_GEN_DIR}/qt3danimation_qanimationgroup_wrapper.cpp ${Qt3DAnimation_GEN_DIR}/qt3danimation_qblendedclipanimator_wrapper.cpp +${Qt3DAnimation_GEN_DIR}/qt3danimation_qcallbackmapping_wrapper.cpp ${Qt3DAnimation_GEN_DIR}/qt3danimation_qchannel_wrapper.cpp ${Qt3DAnimation_GEN_DIR}/qt3danimation_qchannelcomponent_wrapper.cpp ${Qt3DAnimation_GEN_DIR}/qt3danimation_qchannelmapper_wrapper.cpp diff --git a/sources/pyside6/PySide6/Qt3DAnimation/typesystem_3danimation.xml b/sources/pyside6/PySide6/Qt3DAnimation/typesystem_3danimation.xml index 1e8c56b86..61af82d6d 100644 --- a/sources/pyside6/PySide6/Qt3DAnimation/typesystem_3danimation.xml +++ b/sources/pyside6/PySide6/Qt3DAnimation/typesystem_3danimation.xml @@ -37,6 +37,7 @@ <object-type name="QBlendedClipAnimator"/> <value-type name="QChannel"/> <value-type name="QChannelComponent"/> + <object-type name="QCallbackMapping"/> <object-type name="QChannelMapper" since="6.1"/> <object-type name="QChannelMapping"/> <object-type name="QClipAnimator"/> diff --git a/sources/pyside6/PySide6/Qt3DCore/CMakeLists.txt b/sources/pyside6/PySide6/Qt3DCore/CMakeLists.txt index 5493c6876..cd2699f11 100644 --- a/sources/pyside6/PySide6/Qt3DCore/CMakeLists.txt +++ b/sources/pyside6/PySide6/Qt3DCore/CMakeLists.txt @@ -19,6 +19,7 @@ ${Qt3DCore_GEN_DIR}/qt3dcore_qbackendnodemapper_wrapper.cpp ${Qt3DCore_GEN_DIR}/qt3dcore_qboundingvolume_wrapper.cpp ${Qt3DCore_GEN_DIR}/qt3dcore_qbuffer_wrapper.cpp ${Qt3DCore_GEN_DIR}/qt3dcore_qcomponent_wrapper.cpp +${Qt3DCore_GEN_DIR}/qt3dcore_qcoreaspect_wrapper.cpp ${Qt3DCore_GEN_DIR}/qt3dcore_qcoresettings_wrapper.cpp ${Qt3DCore_GEN_DIR}/qt3dcore_qentity_wrapper.cpp ${Qt3DCore_GEN_DIR}/qt3dcore_qgeometry_wrapper.cpp diff --git a/sources/pyside6/PySide6/Qt3DCore/typesystem_3dcore.xml b/sources/pyside6/PySide6/Qt3DCore/typesystem_3dcore.xml index e9532fac9..985c459b6 100644 --- a/sources/pyside6/PySide6/Qt3DCore/typesystem_3dcore.xml +++ b/sources/pyside6/PySide6/Qt3DCore/typesystem_3dcore.xml @@ -50,6 +50,7 @@ </modify-argument> </modify-function> </object-type> + <object-type name="QCoreAspect"/> <object-type name="QCoreSettings"/> <object-type name="QGeometry"/> <object-type name="QGeometryView"> diff --git a/sources/pyside6/PySide6/Qt3DRender/CMakeLists.txt b/sources/pyside6/PySide6/Qt3DRender/CMakeLists.txt index f5be5804b..72f859305 100644 --- a/sources/pyside6/PySide6/Qt3DRender/CMakeLists.txt +++ b/sources/pyside6/PySide6/Qt3DRender/CMakeLists.txt @@ -28,6 +28,7 @@ ${Qt3DRender_GEN_DIR}/qt3drender_qclipplane_wrapper.cpp ${Qt3DRender_GEN_DIR}/qt3drender_qcolormask_wrapper.cpp ${Qt3DRender_GEN_DIR}/qt3drender_qcomputecommand_wrapper.cpp ${Qt3DRender_GEN_DIR}/qt3drender_qcullface_wrapper.cpp +${Qt3DRender_GEN_DIR}/qt3drender_qdebugoverlay_wrapper.cpp ${Qt3DRender_GEN_DIR}/qt3drender_qdepthrange_wrapper.cpp ${Qt3DRender_GEN_DIR}/qt3drender_qdepthtest_wrapper.cpp ${Qt3DRender_GEN_DIR}/qt3drender_qdirectionallight_wrapper.cpp diff --git a/sources/pyside6/PySide6/Qt3DRender/typesystem_3drender.xml b/sources/pyside6/PySide6/Qt3DRender/typesystem_3drender.xml index 6a49d1373..66fd70927 100644 --- a/sources/pyside6/PySide6/Qt3DRender/typesystem_3drender.xml +++ b/sources/pyside6/PySide6/Qt3DRender/typesystem_3drender.xml @@ -68,6 +68,7 @@ <object-type name="QCullFace"> <enum-type name="CullingMode"/> </object-type> + <object-type name="QDebugOverlay"/> <object-type name="QDepthRange"/> <object-type name="QDepthTest"> <enum-type name="DepthFunction"/> diff --git a/sources/pyside6/PySide6/QtAsyncio/__init__.py b/sources/pyside6/PySide6/QtAsyncio/__init__.py index 4baa8134e..60d1846d1 100644 --- a/sources/pyside6/PySide6/QtAsyncio/__init__.py +++ b/sources/pyside6/PySide6/QtAsyncio/__init__.py @@ -28,8 +28,8 @@ def run(coro: typing.Optional[typing.Coroutine] = None, # subsequent removal in Python 3.15. At that point, part of the current # logic of the QAsyncioEventLoopPolicy constructor will have to be moved # here and/or to a loop factory class (to be provided as an argument to - # asyncio.run()), namely setting up the QCoreApplication and the SIGINT - # handler. + # asyncio.run()). In particular, this concerns the logic of setting up the + # QCoreApplication and the SIGINT handler. # # More details: # https://discuss.python.org/t/removing-the-asyncio-policy-system-asyncio-set-event-loop-policy-in-python-3-15/37553 # noqa: E501 diff --git a/sources/pyside6/PySide6/QtAsyncio/events.py b/sources/pyside6/PySide6/QtAsyncio/events.py index cec26ee72..b87d7a309 100644 --- a/sources/pyside6/PySide6/QtAsyncio/events.py +++ b/sources/pyside6/PySide6/QtAsyncio/events.py @@ -26,6 +26,19 @@ __all__ = [ class QAsyncioExecutorWrapper(QObject): + """ + Executors in asyncio allow running synchronous code in a separate thread or + process without blocking the event loop or interrupting the asynchronous + program flow. Callables are scheduled for execution by calling submit() or + map() on an executor object. + + Executors require a bit of extra work for QtAsyncio, as we can't use + naked Python threads; instead, we must make sure that the thread created + by executor.submit() has an event loop. This is achieved by not submitting + the callable directly, but a small wrapper that attaches a QEventLoop to + the executor thread, and then creates a zero-delay singleshot timer to push + the actual callable for the executor into this new event loop. + """ def __init__(self, func: typing.Callable, *args: typing.Tuple) -> None: super().__init__() @@ -37,6 +50,7 @@ class QAsyncioExecutorWrapper(QObject): def _cb(self): try: + # Call the synchronous callable that we submitted with submit() or map(). self._result = self._func(*self._args) except BaseException as e: self._exception = e @@ -57,6 +71,17 @@ class QAsyncioExecutorWrapper(QObject): class QAsyncioEventLoopPolicy(asyncio.AbstractEventLoopPolicy): + """ + Event loop policies are expected to be deprecated with Python 3.13, with + subsequent removal in Python 3.15. At that point, part of the current + logic of the QAsyncioEventLoopPolicy constructor will have to be moved + to QtAsyncio.run() and/or to a loop factory class (to be provided as an + argument to asyncio.run()). In particular, this concerns the logic of + setting up the QCoreApplication and the SIGINT handler. + + More details: + https://discuss.python.org/t/removing-the-asyncio-policy-system-asyncio-set-event-loop-policy-in-python-3-15/37553 + """ def __init__(self, application: typing.Optional[QCoreApplication] = None, quit_qapp: bool = True, @@ -68,7 +93,14 @@ class QAsyncioEventLoopPolicy(asyncio.AbstractEventLoopPolicy): else: application = QCoreApplication.instance() self._application: QCoreApplication = application # type: ignore[assignment] + + # Configure whether the QCoreApplication at the core of QtAsyncio + # should be shut down when asyncio finishes. A special case where one + # would want to disable this is test suites that want to reuse a single + # QCoreApplication instance across all unit tests, which would fail if + # this instance is shut down every time. self._quit_qapp = quit_qapp + self._event_loop: typing.Optional[asyncio.AbstractEventLoop] = None if handle_sigint: @@ -99,6 +131,14 @@ class QAsyncioEventLoop(asyncio.BaseEventLoop, QObject): """ class ShutDownThread(QThread): + """ + Used to shut down the default executor when calling + shutdown_default_executor(). As the executor is a ThreadPoolExecutor, + it must be shut down in a separate thread as all the threads from the + thread pool must join, which we want to do without blocking the event + loop. + """ + def __init__(self, future: futures.QAsyncioFuture, loop: "QAsyncioEventLoop") -> None: super().__init__() self._future = future @@ -123,22 +163,48 @@ class QAsyncioEventLoop(asyncio.BaseEventLoop, QObject): QObject.__init__(self) self._application: QCoreApplication = application + + # Configure whether the QCoreApplication at the core of QtAsyncio + # should be shut down when asyncio finishes. A special case where one + # would want to disable this is test suites that want to reuse a single + # QCoreApplication instance across all unit tests, which would fail if + # this instance is shut down every time. self._quit_qapp = quit_qapp + self._thread = QThread.currentThread() self._closed = False + # These two flags are used to determine whether the loop was stopped + # from inside the loop (i.e., coroutine or callback called stop()) or + # from outside the loop (i.e., the QApplication is being shut down, for + # example, by the user closing the window or by calling + # QApplication.quit()). The different cases can trigger slightly + # different behaviors (see the comments where the flags are used). + # There are two variables for this as in a third case the loop is still + # running and both flags are False. self._quit_from_inside = False self._quit_from_outside = False + # A set of all asynchronous generators that are currently running. self._asyncgens: typing.Set[collections.abc.AsyncGenerator] = set() # Starting with Python 3.11, this must be an instance of # ThreadPoolExecutor. self._default_executor = concurrent.futures.ThreadPoolExecutor() + # The exception handler, if set with set_exception_handler(). The + # exception handler is currently called in two places: One, if an + # asynchonrous generator raises an exception when closed, and two, if + # an exception is raised during the execution of a task. Currently, the + # default exception handler just prints the exception to the console. self._exception_handler: typing.Optional[typing.Callable] = self.default_exception_handler + + # The task factory, if set with set_task_factory(). Otherwise, a new + # task is created with the QAsyncioTask constructor. self._task_factory: typing.Optional[typing.Callable] = None + + # The future that is currently being awaited with run_until_complete(). self._future_to_complete: typing.Optional[futures.QAsyncioFuture] = None self._debug = bool(os.getenv("PYTHONASYNCIODEBUG", False)) @@ -148,6 +214,10 @@ class QAsyncioEventLoop(asyncio.BaseEventLoop, QObject): # Running and stopping the loop def _run_until_complete_cb(self, future: futures.QAsyncioFuture) -> None: + """ + A callback that stops the loop when the future is done, used when + running the loop with run_until_complete(). + """ if not future.cancelled(): if isinstance(future.exception(), (SystemExit, KeyboardInterrupt)): return @@ -188,7 +258,12 @@ class QAsyncioEventLoop(asyncio.BaseEventLoop, QObject): asyncio.events._set_running_loop(None) def _about_to_quit_cb(self): + """ A callback for the aboutToQuit signal of the QCoreApplication. """ if not self._quit_from_inside: + # If the aboutToQuit signal is emitted, the user is closing the + # application window or calling QApplication.quit(). In this case, + # we want to close the event loop, and we consider this a quit from + # outside the loop. self._quit_from_outside = True self.close() @@ -197,8 +272,15 @@ class QAsyncioEventLoop(asyncio.BaseEventLoop, QObject): if self._future_to_complete.done(): self._future_to_complete = None else: + # Do not stop the loop if there is a future still being awaited + # with run_until_complete(). return + self._quit_from_inside = True + + # The user might want to keep the QApplication running after the event + # event loop finishes, which they can control with the quit_qapp + # argument. if self._quit_qapp: self._application.quit() @@ -298,6 +380,7 @@ class QAsyncioEventLoop(asyncio.BaseEventLoop, QObject): callback: typing.Callable, *args: typing.Any, context: typing.Optional[contextvars.Context] = None, is_threadsafe: typing.Optional[bool] = False) -> asyncio.TimerHandle: + """ All call_at() and call_later() methods map to this method. """ if not isinstance(when, (int, float)): raise TypeError("when must be an int or float") return QAsyncioTimerHandle(when, callback, args, self, context, is_threadsafe=is_threadsafe) @@ -482,6 +565,13 @@ class QAsyncioEventLoop(asyncio.BaseEventLoop, QObject): raise RuntimeError("Event loop is closed") if executor is None: executor = self._default_executor + + # Executors require a bit of extra work for QtAsyncio, as we can't use + # naked Python threads; instead, we must make sure that the thread + # created by executor.submit() has an event loop. This is achieved by + # not submitting the callable directly, but a small wrapper that + # attaches a QEventLoop to the executor thread, and then pushes the + # actual callable for the executor into this new event loop. wrapper = QAsyncioExecutorWrapper(func, *args) return asyncio.futures.wrap_future( executor.submit(wrapper.do), loop=self @@ -541,6 +631,12 @@ class QAsyncioEventLoop(asyncio.BaseEventLoop, QObject): class QAsyncioHandle(): + """ + The handle enqueues a callback to be executed by the event loop, and allows + for this callback to be cancelled before it is executed. This callback will + typically execute the step function for a task. This makes the handle one + of the main components of asyncio. + """ class HandleState(enum.Enum): PENDING = enum.auto() CANCELLED = enum.auto() @@ -561,6 +657,9 @@ class QAsyncioHandle(): self._start() def _schedule_event(self, timeout: int, func: typing.Callable) -> None: + # Do not schedule events from asyncio when the app is quit from outside + # the event loop, as this would cause events to be enqueued after the + # event loop was destroyed. if not self._loop.is_closed() and not self._loop._quit_from_outside: if self._is_threadsafe: QTimer.singleShot(timeout, self._loop, func) @@ -572,6 +671,10 @@ class QAsyncioHandle(): @Slot() def _cb(self) -> None: + """ + A slot, enqueued into the event loop, that wraps around the actual + callback, typically the step function of a task. + """ if self._state == QAsyncioHandle.HandleState.PENDING: if self._context is not None: self._context.run(self._callback, *self._args) @@ -581,7 +684,9 @@ class QAsyncioHandle(): def cancel(self) -> None: if self._state == QAsyncioHandle.HandleState.PENDING: - # The old timer that was created in _start will still trigger but _cb won't do anything. + # The old timer that was created in _start will still trigger but + # _cb won't do anything, therefore the callback is effectively + # cancelled. self._state = QAsyncioHandle.HandleState.CANCELLED def cancelled(self) -> bool: @@ -595,13 +700,16 @@ class QAsyncioTimerHandle(QAsyncioHandle, asyncio.TimerHandle): QAsyncioHandle.__init__(self, callback, args, loop, context, is_threadsafe) self._when = when - self._timeout = int(max(self._when - self._loop.time(), 0) * 1000) + time = self._loop.time() + self._timeout = round(max(self._when - time, 0) * 1000) QAsyncioHandle._start(self) - # Override this so that timer.start() is only called once at the end - # of the constructor for both QtHandle and QtTimerHandle. def _start(self) -> None: + """ + Overridden so that timer.start() is only called once at the end of the + constructor for both QtHandle and QtTimerHandle. + """ pass def when(self) -> float: diff --git a/sources/pyside6/PySide6/QtAsyncio/futures.py b/sources/pyside6/PySide6/QtAsyncio/futures.py index 611bd5634..cbb005fc9 100644 --- a/sources/pyside6/PySide6/QtAsyncio/futures.py +++ b/sources/pyside6/PySide6/QtAsyncio/futures.py @@ -36,10 +36,11 @@ class QAsyncioFuture(): self._result: typing.Any = None self._exception: typing.Optional[BaseException] = None - self._callbacks: typing.List[typing.Callable] = list() - self._cancel_message: typing.Optional[str] = None + # List of callbacks that are called when the future is done. + self._callbacks: typing.List[typing.Callable] = list() + def __await__(self): if not self.done(): self._asyncio_future_blocking = True @@ -51,6 +52,7 @@ class QAsyncioFuture(): __iter__ = __await__ def _schedule_callbacks(self, context: typing.Optional[contextvars.Context] = None): + """ A future can optionally have callbacks that are called when the future is done. """ for cb in self._callbacks: self._loop.call_soon( cb, self, context=context if context else self._context) diff --git a/sources/pyside6/PySide6/QtAsyncio/tasks.py b/sources/pyside6/PySide6/QtAsyncio/tasks.py index bc3d41a73..7edc15093 100644 --- a/sources/pyside6/PySide6/QtAsyncio/tasks.py +++ b/sources/pyside6/PySide6/QtAsyncio/tasks.py @@ -20,16 +20,22 @@ class QAsyncioTask(futures.QAsyncioFuture): context: typing.Optional[contextvars.Context] = None) -> None: super().__init__(loop=loop, context=context) - self._coro = coro + self._coro = coro # The coroutine for which this task was created. self._name = name if name else "QtTask" + # The task creates a handle for its coroutine. The handle enqueues the + # task's step function as its callback in the event loop. self._handle = self._loop.call_soon(self._step, context=self._context) - self._cancellation_requests = 0 - + # The task step function executes the coroutine until it finishes, + # raises an exception or returns a future. If a future was returned, + # the task will await its completion (or exception). self._future_to_await: typing.Optional[asyncio.Future] = None + + self._cancelled = False self._cancel_message: typing.Optional[str] = None + # https://docs.python.org/3/library/asyncio-extending.html#task-lifetime-support asyncio._register_task(self) # type: ignore[arg-type] def __repr__(self) -> str: @@ -58,24 +64,39 @@ class QAsyncioTask(futures.QAsyncioFuture): def _step(self, exception_or_future: typing.Union[ BaseException, futures.QAsyncioFuture, None] = None) -> None: + """ + The step function is the heart of a task. It is scheduled in the event + loop repeatedly, executing the coroutine "step" by "step" (i.e., + iterating through the asynchronous generator) until it finishes with an + exception or successfully. Each step can optionally receive an + exception or a future as a result from a previous step to handle. + """ + if self.done(): return result = None self._future_to_await = None + if asyncio.futures.isfuture(exception_or_future): + try: + exception_or_future.result() + except BaseException as e: + exception_or_future = e + try: asyncio._enter_task(self._loop, self) # type: ignore[arg-type] - if exception_or_future is None: - result = self._coro.send(None) - elif asyncio.futures.isfuture(exception_or_future): - try: - exception_or_future.result() - except BaseException as e: - result = self._coro.throw(e) - else: - result = self._coro.send(None) - elif isinstance(exception_or_future, BaseException): + + # It is at this point that the coroutine is resumed for the current + # step (i.e. asynchronous generator iteration). It will now be + # executed until it yields (and potentially returns a future), + # raises an exception, is cancelled, or finishes successfully. + + if isinstance(exception_or_future, BaseException): + # If the coroutine doesn't handle this exception, it propagates + # to the caller. result = self._coro.throw(exception_or_future) + else: + result = self._coro.send(None) except StopIteration as e: self._state = futures.QAsyncioFuture.FutureState.DONE_WITH_RESULT self._result = e.value @@ -87,16 +108,32 @@ class QAsyncioTask(futures.QAsyncioFuture): self._exception = e else: if asyncio.futures.isfuture(result): + # If the coroutine yields a future, the task will await its + # completion, and at that point the step function will be + # called again. result.add_done_callback( self._step, context=self._context) # type: ignore[arg-type] self._future_to_await = result + if self._cancelled: + # If the task was cancelled, then a new future should be + # cancelled as well. Otherwise, in some scenarios like + # a loop inside the task and with bad timing, if the new + # future is not cancelled, the task would continue running + # in this loop despite having been cancelled. This bad + # timing can occur especially if the first future finishes + # very quickly. + self._future_to_await.cancel(self._cancel_message) elif result is None: + # If no future was yielded, we schedule the step function again + # without any arguments. self._loop.call_soon(self._step, context=self._context) else: + # This is not supposed to happen. exception = RuntimeError(f"Bad task result: {result}") self._loop.call_soon(self._step, exception, context=self._context) finally: asyncio._leave_task(self._loop, self) # type: ignore[arg-type] + if self._exception: self._loop.call_exception_handler({ "message": (str(self._exception) if self._exception @@ -108,8 +145,11 @@ class QAsyncioTask(futures.QAsyncioFuture): if asyncio.futures.isfuture(exception_or_future) else None) }) + if self.done(): self._schedule_callbacks() + + # https://docs.python.org/3/library/asyncio-extending.html#task-lifetime-support asyncio._unregister_task(self) # type: ignore[arg-type] def get_stack(self, *, limit=None) -> typing.List[typing.Any]: @@ -135,7 +175,10 @@ class QAsyncioTask(futures.QAsyncioFuture): self._cancel_message = msg self._handle.cancel() if self._future_to_await is not None: + # A task that is awaiting a future must also cancel this future in + # order for the cancellation to be successful. self._future_to_await.cancel(msg) + self._cancelled = True return True def uncancel(self) -> None: diff --git a/sources/pyside6/PySide6/QtCore/glue/core_snippets.cpp b/sources/pyside6/PySide6/QtCore/glue/core_snippets.cpp index 4266e868c..f6acf9d60 100644 --- a/sources/pyside6/PySide6/QtCore/glue/core_snippets.cpp +++ b/sources/pyside6/PySide6/QtCore/glue/core_snippets.cpp @@ -340,9 +340,7 @@ PyObject *invokeMetaMethodWithReturn(const InvokeMetaMethodFuncWithReturn &f, a6.toGenericArgument(), a7.toGenericArgument(), a8.toGenericArgument(), a9.toGenericArgument()); PyEval_RestoreThread(_save); // Py_END_ALLOW_THREADS - if (!callResult) { - PyErr_SetString(PyExc_RuntimeError, "QMetaMethod invocation failed."); - return nullptr; - } + if (!callResult) + return PyErr_Format(PyExc_RuntimeError, "QMetaMethod invocation failed."); return convertGenericReturnArgument(r.data(), r.metaType()); } diff --git a/sources/pyside6/PySide6/QtCore/typesystem_core_common.xml b/sources/pyside6/PySide6/QtCore/typesystem_core_common.xml index 0a08928f5..e915bc91b 100644 --- a/sources/pyside6/PySide6/QtCore/typesystem_core_common.xml +++ b/sources/pyside6/PySide6/QtCore/typesystem_core_common.xml @@ -1677,8 +1677,9 @@ <add-function signature="operator==(const QItemSelection&)" return-type="bool"/> <add-function signature="operator!=(const QItemSelection&)" return-type="bool"/> <!-- For some reason, the empty selection is not seen. Maybe related to the new [default] - tag in Qt6? --> - <declare-function signature="QItemSelection()" return-type="QItemSelection" /> + tag in Qt6? + PYSIDE-2756: The return-type attribute is unnecessary --> + <declare-function signature="QItemSelection()"/> <!-- The __add__ function creates a result list, instead of using the inherited type. Fixed by adding with the correct type. --> <add-function signature="operator+(QItemSelection)" return-type="QItemSelection"> @@ -1759,9 +1760,6 @@ </modify-function> <!-- End of Invalidate-after-use fix --> <modify-function signature="parent()const"> - <modify-argument index="this"> - <parent index="return" action="add"/> - </modify-argument> <modify-argument index="return"> <define-ownership class="target" owner="default"/> </modify-argument> @@ -2552,6 +2550,11 @@ </modify-argument> <inject-code file="../glue/qtcore.cpp" snippet="qtranslator-load"/> </modify-function> + <modify-function signature="translate(const char*,const char*, const char*,int)const"> + <modify-argument index="1" pyi-type="str"/> + <modify-argument index="2" pyi-type="str"/> + <modify-argument index="3" pyi-type="Optional[str]"/> + </modify-function> </object-type> <object-type name="QWaitCondition"> <configuration condition="QT_CONFIG(thread)"/> diff --git a/sources/pyside6/PySide6/QtGui/CMakeLists.txt b/sources/pyside6/PySide6/QtGui/CMakeLists.txt index 7cd7871f3..99e0789d1 100644 --- a/sources/pyside6/PySide6/QtGui/CMakeLists.txt +++ b/sources/pyside6/PySide6/QtGui/CMakeLists.txt @@ -88,6 +88,7 @@ ${QtGui_GEN_DIR}/qaccessibleactioninterface_wrapper.cpp ${QtGui_GEN_DIR}/qaccessibleeditabletextinterface_wrapper.cpp ${QtGui_GEN_DIR}/qaccessibleevent_wrapper.cpp ${QtGui_GEN_DIR}/qaccessibleobject_wrapper.cpp +${QtGui_GEN_DIR}/qaccessibleselectioninterface_wrapper.cpp ${QtGui_GEN_DIR}/qaccessiblestatechangeevent_wrapper.cpp ${QtGui_GEN_DIR}/qaccessibletablecellinterface_wrapper.cpp ${QtGui_GEN_DIR}/qaccessibletablemodelchangeevent_wrapper.cpp diff --git a/sources/pyside6/PySide6/QtGui/typesystem_gui_common.xml b/sources/pyside6/PySide6/QtGui/typesystem_gui_common.xml index dd17952f0..feb2dc11b 100644 --- a/sources/pyside6/PySide6/QtGui/typesystem_gui_common.xml +++ b/sources/pyside6/PySide6/QtGui/typesystem_gui_common.xml @@ -122,6 +122,7 @@ <object-type name="QAccessibleEditableTextInterface"/> <object-type name="QAccessibleInterface"/> <object-type name="QAccessibleObject" qt-register-metatype="base"/> + <object-type name="QAccessibleSelectionInterface" since="6.7"/> <object-type name="QAccessibleTableCellInterface"/> <object-type name="QAccessibleTextInterface"/> <object-type name="QAccessibleValueInterface"/> @@ -2572,7 +2573,7 @@ </extra-includes> </primitive-type> - <object-type name="QWindow" delete-in-main-thread="true"> + <object-type name="QWindow" delete-in-main-thread="true" polymorphic-base="true"> <enum-type name="AncestorMode"/> <enum-type name="Visibility"/> <modify-function signature="raise()" rename="raise_"/> diff --git a/sources/pyside6/PySide6/QtHelp/CMakeLists.txt b/sources/pyside6/PySide6/QtHelp/CMakeLists.txt index 85bda0c10..7bf46dcef 100644 --- a/sources/pyside6/PySide6/QtHelp/CMakeLists.txt +++ b/sources/pyside6/PySide6/QtHelp/CMakeLists.txt @@ -8,6 +8,7 @@ ${QtHelp_GEN_DIR}/qcompressedhelpinfo_wrapper.cpp ${QtHelp_GEN_DIR}/qhelpcontentitem_wrapper.cpp ${QtHelp_GEN_DIR}/qhelpcontentmodel_wrapper.cpp ${QtHelp_GEN_DIR}/qhelpcontentwidget_wrapper.cpp +${QtHelp_GEN_DIR}/qhelpglobal_wrapper.cpp ${QtHelp_GEN_DIR}/qhelpengine_wrapper.cpp ${QtHelp_GEN_DIR}/qhelpenginecore_wrapper.cpp ${QtHelp_GEN_DIR}/qhelpfilterdata_wrapper.cpp diff --git a/sources/pyside6/PySide6/QtHelp/typesystem_help.xml b/sources/pyside6/PySide6/QtHelp/typesystem_help.xml index cb55baca0..5fa30e034 100644 --- a/sources/pyside6/PySide6/QtHelp/typesystem_help.xml +++ b/sources/pyside6/PySide6/QtHelp/typesystem_help.xml @@ -18,6 +18,7 @@ </value-type> <object-type name="QHelpContentModel" polymorphic-id-expression="qobject_cast<QHelpContentModel*>(%B)"/> <object-type name="QHelpContentWidget"/> + <value-type name="QHelpGlobal"/> <object-type name="QHelpEngine"/> <object-type name="QHelpEngineCore"/> <value-type name="QHelpFilterData"/> diff --git a/sources/pyside6/PySide6/QtMultimedia/CMakeLists.txt b/sources/pyside6/PySide6/QtMultimedia/CMakeLists.txt index 57ebdbb40..e40c5a2ed 100644 --- a/sources/pyside6/PySide6/QtMultimedia/CMakeLists.txt +++ b/sources/pyside6/PySide6/QtMultimedia/CMakeLists.txt @@ -31,8 +31,10 @@ ${QtMultimedia_GEN_DIR}/qscreencapture_wrapper.cpp ${QtMultimedia_GEN_DIR}/qsoundeffect_wrapper.cpp ${QtMultimedia_GEN_DIR}/qtvideo_wrapper.cpp ${QtMultimedia_GEN_DIR}/qvideoframe_wrapper.cpp +${QtMultimedia_GEN_DIR}/qvideoframe_paintoptions_wrapper.cpp ${QtMultimedia_GEN_DIR}/qvideoframeformat_wrapper.cpp ${QtMultimedia_GEN_DIR}/qvideosink_wrapper.cpp +${QtMultimedia_GEN_DIR}/qwavedecoder_wrapper.cpp ${QtMultimedia_GEN_DIR}/qwindowcapture_wrapper.cpp # module is always needed diff --git a/sources/pyside6/PySide6/QtMultimedia/typesystem_multimedia.xml b/sources/pyside6/PySide6/QtMultimedia/typesystem_multimedia.xml index d37eb15fd..2791f695a 100644 --- a/sources/pyside6/PySide6/QtMultimedia/typesystem_multimedia.xml +++ b/sources/pyside6/PySide6/QtMultimedia/typesystem_multimedia.xml @@ -154,6 +154,9 @@ <inject-code file="../glue/qtmultimedia.cpp" snippet="qvideoframe-bits"/> </modify-function> <modify-function signature="bits(int)const" remove="all"/> + <value-type name="PaintOptions"> + <enum-type name="PaintFlag" flags="PaintFlags"/> + </value-type> </value-type> <value-type name="QVideoFrameFormat" since="6.1"> <enum-type name="ColorSpace" since="6.4"/> @@ -164,6 +167,11 @@ <enum-type name="YCbCrColorSpace"/> </value-type> + <object-type name="QWaveDecoder"> + <!-- No implementation --> + <modify-function signature="setIODevice(QIODevice*)" remove="all"/> + </object-type> + <object-type name="QWindowCapture" since="6.6"> <enum-type name="Error"/> </object-type> diff --git a/sources/pyside6/PySide6/QtNetwork/CMakeLists.txt b/sources/pyside6/PySide6/QtNetwork/CMakeLists.txt index e7c8586e4..529e2e86b 100644 --- a/sources/pyside6/PySide6/QtNetwork/CMakeLists.txt +++ b/sources/pyside6/PySide6/QtNetwork/CMakeLists.txt @@ -18,6 +18,7 @@ ${QtNetwork_GEN_DIR}/qdnstextrecord_wrapper.cpp ${QtNetwork_GEN_DIR}/qhostaddress_wrapper.cpp ${QtNetwork_GEN_DIR}/qhostinfo_wrapper.cpp ${QtNetwork_GEN_DIR}/qhstspolicy_wrapper.cpp +${QtNetwork_GEN_DIR}/qhttp1configuration_wrapper.cpp ${QtNetwork_GEN_DIR}/qhttpheaders_wrapper.cpp ${QtNetwork_GEN_DIR}/qhttpmultipart_wrapper.cpp ${QtNetwork_GEN_DIR}/qhttppart_wrapper.cpp @@ -84,7 +85,8 @@ if("dtls" IN_LIST QtNetwork_disabled_features) else() list(APPEND QtNetwork_SRC ${QtNetwork_GEN_DIR}/qdtls_wrapper.cpp - ${QtNetwork_GEN_DIR}/qdtlsclientverifier_wrapper.cpp) + ${QtNetwork_GEN_DIR}/qdtlsclientverifier_wrapper.cpp + ${QtNetwork_GEN_DIR}/qdtlsclientverifier_generatorparameters_wrapper.cpp) message(STATUS "Qt${QT_MAJOR_VERSION}Network: Adding DTLS classes") endif() diff --git a/sources/pyside6/PySide6/QtNetwork/typesystem_network.xml b/sources/pyside6/PySide6/QtNetwork/typesystem_network.xml index 4c1efc8b2..4dc7c9b0a 100644 --- a/sources/pyside6/PySide6/QtNetwork/typesystem_network.xml +++ b/sources/pyside6/PySide6/QtNetwork/typesystem_network.xml @@ -76,11 +76,15 @@ </object-type> <object-type name="QDtlsClientVerifier"> <configuration condition="QT_CONFIG(dtls)"/> + <value-type name="GeneratorParameters"> + <configuration condition="QT_CONFIG(dtls)"/> + </value-type> </object-type> <value-type name="QHstsPolicy"> <enum-type name="PolicyFlag" flags="PolicyFlags"/> </value-type> + <value-type name="QHttp1Configuration"/> <value-type name="QHttpHeaders" since="6.7"> <enum-type name="WellKnownHeader"/> </value-type> @@ -175,6 +179,11 @@ <modify-function signature="put(const QNetworkRequest &,QIODevice*)" allow-thread="yes"/> <modify-function signature="put(const QNetworkRequest &,const QByteArray &)" allow-thread="yes"/> <modify-function signature="sendCustomRequest(const QNetworkRequest &,const QByteArray &,QIODevice*)" allow-thread="yes" since="4.7"/> + <modify-function signature="setCache(QAbstractNetworkCache*)"> + <modify-argument index="1"> + <define-ownership class="target" owner="c++"/> + </modify-argument> + </modify-function> <modify-function signature="setCookieJar(QNetworkCookieJar*)"> <modify-argument index="1"> <define-ownership class="target" owner="c++"/> diff --git a/sources/pyside6/PySide6/QtNfc/CMakeLists.txt b/sources/pyside6/PySide6/QtNfc/CMakeLists.txt index 7ecb6e73a..b94249a55 100644 --- a/sources/pyside6/PySide6/QtNfc/CMakeLists.txt +++ b/sources/pyside6/PySide6/QtNfc/CMakeLists.txt @@ -14,6 +14,7 @@ set(QtNfc_SRC ${QtNfc_GEN_DIR}/qndefnfcurirecord_wrapper.cpp ${QtNfc_GEN_DIR}/qnearfieldmanager_wrapper.cpp ${QtNfc_GEN_DIR}/qnearfieldtarget_wrapper.cpp + ${QtNfc_GEN_DIR}/qnearfieldtarget_requestid_wrapper.cpp # module is always needed ${QtNfc_GEN_DIR}/qtnfc_module_wrapper.cpp) diff --git a/sources/pyside6/PySide6/QtNfc/typesystem_nfc.xml b/sources/pyside6/PySide6/QtNfc/typesystem_nfc.xml index 51f5fa9bc..b548227f0 100644 --- a/sources/pyside6/PySide6/QtNfc/typesystem_nfc.xml +++ b/sources/pyside6/PySide6/QtNfc/typesystem_nfc.xml @@ -28,6 +28,7 @@ <enum-type name="AccessMethod" flags="AccessMethods"/> <enum-type name="Error"/> <enum-type name="Type"/> + <value-type name="RequestId"/> </object-type> <!-- QtNetwork is pulled in via QtNfcDepends. --> <suppress-warning text="^Scoped enum 'Q(Ocsp)|(Dtls).*' does not have a type entry.*$"/> diff --git a/sources/pyside6/PySide6/QtOpenGL/typesystem_opengl.xml b/sources/pyside6/PySide6/QtOpenGL/typesystem_opengl.xml index bd031cc49..efbd16056 100644 --- a/sources/pyside6/PySide6/QtOpenGL/typesystem_opengl.xml +++ b/sources/pyside6/PySide6/QtOpenGL/typesystem_opengl.xml @@ -625,7 +625,7 @@ </object-type> <value-type name="QOpenGLFramebufferObjectFormat"/> <object-type name="QAbstractOpenGLFunctions"/> - <value-type name="QOpenGLPixelTransferOptions"/> + <value-type name="QOpenGLPixelTransferOptions"/> <object-type name="QOpenGLShader"> <enum-type name="ShaderTypeBit" flags="ShaderType"/> </object-type> diff --git a/sources/pyside6/PySide6/QtPositioning/CMakeLists.txt b/sources/pyside6/PySide6/QtPositioning/CMakeLists.txt index 874b8ada0..cad6dcb74 100644 --- a/sources/pyside6/PySide6/QtPositioning/CMakeLists.txt +++ b/sources/pyside6/PySide6/QtPositioning/CMakeLists.txt @@ -23,6 +23,7 @@ ${QtPositioning_GEN_DIR}/qgeosatelliteinfo_wrapper.cpp ${QtPositioning_GEN_DIR}/qgeosatelliteinfosource_wrapper.cpp ${QtPositioning_GEN_DIR}/qgeoshape_wrapper.cpp ${QtPositioning_GEN_DIR}/qnmeapositioninfosource_wrapper.cpp +${QtPositioning_GEN_DIR}/qnmeasatelliteinfosource_wrapper.cpp # module is always needed ${QtPositioning_GEN_DIR}/qtpositioning_module_wrapper.cpp ) diff --git a/sources/pyside6/PySide6/QtPositioning/typesystem_positioning.xml b/sources/pyside6/PySide6/QtPositioning/typesystem_positioning.xml index e7f9fd254..8dac3f00d 100644 --- a/sources/pyside6/PySide6/QtPositioning/typesystem_positioning.xml +++ b/sources/pyside6/PySide6/QtPositioning/typesystem_positioning.xml @@ -44,4 +44,8 @@ <object-type name="QNmeaPositionInfoSource"> <enum-type name="UpdateMode"/> </object-type> + <object-type name="QNmeaSatelliteInfoSource"> + <enum-type name="UpdateMode"/> + <enum-type name="SatelliteInfoParseStatus"/> + </object-type> </typesystem> diff --git a/sources/pyside6/PySide6/QtQml/pysideqmlvolatilebool.cpp b/sources/pyside6/PySide6/QtQml/pysideqmlvolatilebool.cpp index 6e403ab72..ca3dfebed 100644 --- a/sources/pyside6/PySide6/QtQml/pysideqmlvolatilebool.cpp +++ b/sources/pyside6/PySide6/QtQml/pysideqmlvolatilebool.cpp @@ -49,7 +49,10 @@ static void QtQml_VolatileBoolObject_dealloc(PyObject *self) static PyObject * QtQml_VolatileBoolObject_get(QtQml_VolatileBoolObject *self) { - return *self->flag ? Py_True : Py_False; + if (*self->flag) { + Py_RETURN_TRUE; + } + Py_RETURN_FALSE; } static PyObject * @@ -63,10 +66,8 @@ QtQml_VolatileBoolObject_set(QtQml_VolatileBoolObject *self, PyObject *args) } ok = PyObject_IsTrue(value); - if (ok < 0) { - PyErr_SetString(PyExc_TypeError, "Not a boolean value."); - return nullptr; - } + if (ok < 0) + return PyErr_Format(PyExc_TypeError, "Not a boolean value."); *self->flag = ok > 0; diff --git a/sources/pyside6/PySide6/QtQml/typesystem_qml.xml b/sources/pyside6/PySide6/QtQml/typesystem_qml.xml index 6a739bbdf..3392ef379 100644 --- a/sources/pyside6/PySide6/QtQml/typesystem_qml.xml +++ b/sources/pyside6/PySide6/QtQml/typesystem_qml.xml @@ -124,9 +124,12 @@ <function signature="qmlProtectModule(const char*,int)" doc-file="qqmlengine"/> <function signature="qmlRegisterModule(const char*,int,int)" doc-file="qqmlengine"/> <function signature="qmlTypeId(const char*,int,int,const char*)" doc-file="qqmlengine"/> - <function signature="qmlRegisterType(const QUrl &,const char *,int,int,const char *)"/> - <function signature="qmlRegisterSingletonType(const QUrl &,const char *,int,int,const char *)"/> - <function signature="qmlRegisterUncreatableMetaObject(const QMetaObject&,const char*,int,int, const char*,const QString&)"/> + <function signature="qmlRegisterType(const QUrl &,const char *,int,int,const char *)" + doc-file="qqmlengine"/> + <function signature="qmlRegisterSingletonType(const QUrl &,const char *,int,int,const char *)" + doc-file="qqmlengine"/> + <function signature="qmlRegisterUncreatableMetaObject(const QMetaObject&,const char*,int,int, const char*,const QString&)" + doc-file="qqmlengine"/> <enum-type identified-by-value="QML_HAS_ATTACHED_PROPERTIES"> <extra-includes> diff --git a/sources/pyside6/PySide6/QtQuick/CMakeLists.txt b/sources/pyside6/PySide6/QtQuick/CMakeLists.txt index a42b655db..775230242 100644 --- a/sources/pyside6/PySide6/QtQuick/CMakeLists.txt +++ b/sources/pyside6/PySide6/QtQuick/CMakeLists.txt @@ -37,6 +37,7 @@ ${QtQuick_GEN_DIR}/qquickrhiitem_wrapper.cpp ${QtQuick_GEN_DIR}/qquicktextdocument_wrapper.cpp ${QtQuick_GEN_DIR}/qquickview_wrapper.cpp ${QtQuick_GEN_DIR}/qquickwindow_wrapper.cpp +${QtQuick_GEN_DIR}/qquickwindow_graphicsstateinfo_wrapper.cpp ${QtQuick_GEN_DIR}/qsgbasicgeometrynode_wrapper.cpp ${QtQuick_GEN_DIR}/qsgclipnode_wrapper.cpp ${QtQuick_GEN_DIR}/qsgdynamictexture_wrapper.cpp @@ -48,16 +49,21 @@ ${QtQuick_GEN_DIR}/qsggeometry_point2d_wrapper.cpp ${QtQuick_GEN_DIR}/qsggeometry_texturedpoint2d_wrapper.cpp ${QtQuick_GEN_DIR}/qsggeometry_wrapper.cpp ${QtQuick_GEN_DIR}/qsggeometrynode_wrapper.cpp +${QtQuick_GEN_DIR}/qsgimagenode_wrapper.cpp ${QtQuick_GEN_DIR}/qsgmaterial_wrapper.cpp ${QtQuick_GEN_DIR}/qsgmaterialshader_wrapper.cpp +${QtQuick_GEN_DIR}/qsgmaterialshader_graphicspipelinestate_wrapper.cpp ${QtQuick_GEN_DIR}/qsgmaterialtype_wrapper.cpp +${QtQuick_GEN_DIR}/qsgninepatchnode_wrapper.cpp ${QtQuick_GEN_DIR}/qsgnode_wrapper.cpp +${QtQuick_GEN_DIR}/qsgnodevisitor_wrapper.cpp ${QtQuick_GEN_DIR}/qsgopacitynode_wrapper.cpp ${QtQuick_GEN_DIR}/qsgopaquetexturematerial_wrapper.cpp #${QtQuick_GEN_DIR}/qsgsimplematerial_wrapper.cpp #${QtQuick_GEN_DIR}/qsgsimplematerialshader_wrapper.cpp ${QtQuick_GEN_DIR}/qsgrectanglenode_wrapper.cpp ${QtQuick_GEN_DIR}/qsgrendernode_wrapper.cpp +${QtQuick_GEN_DIR}/qsgrootnode_wrapper.cpp ${QtQuick_GEN_DIR}/qsgsimplerectnode_wrapper.cpp ${QtQuick_GEN_DIR}/qsgsimpletexturenode_wrapper.cpp ${QtQuick_GEN_DIR}/qsgrendererinterface_wrapper.cpp diff --git a/sources/pyside6/PySide6/QtQuick/typesystem_quick.xml b/sources/pyside6/PySide6/QtQuick/typesystem_quick.xml index 0bfb744c7..61aed6005 100644 --- a/sources/pyside6/PySide6/QtQuick/typesystem_quick.xml +++ b/sources/pyside6/PySide6/QtQuick/typesystem_quick.xml @@ -53,7 +53,7 @@ <object-type name="QQuickImageResponse"/> <object-type name="QQuickTransform"/> - <object-type name="QQuickItem" delete-in-main-thread="true"> + <object-type name="QQuickItem" delete-in-main-thread="true" polymorphic-base="true"> <value-type name="UpdatePaintNodeData"/> <enum-type name="Flag" flags="Flags"/> <enum-type name="ItemChange"/> @@ -112,6 +112,7 @@ <enum-type name="RenderStage"/> <enum-type name="SceneGraphError"/> <enum-type name="TextRenderType"/> + <value-type name="GraphicsStateInfo"/> </object-type> <object-type name="QSGBasicGeometryNode"> @@ -167,6 +168,10 @@ </modify-function> </object-type> + <object-type name="QSGImageNode"> + <enum-type name="TextureCoordinatesTransformFlag" flags="TextureCoordinatesTransformMode"/> + </object-type> + <object-type name="QSGMaterial"> <enum-type name="Flag" flags="Flags"/> </object-type> @@ -176,16 +181,25 @@ <value-type name="RenderState"> <enum-type name="DirtyState" flags="DirtyStates"/> </value-type> + <value-type name="GraphicsPipelineState"> + <enum-type name="BlendFactor"/> + <enum-type name="ColorMaskComponent" flags="ColorMask"/> + <enum-type name="CullMode"/> + <enum-type name="PolygonMode"/> + </value-type> <modify-function signature="updateSampledImage(QSGMaterialShader::RenderState&,int,QSGTexture**,QSGMaterial*,QSGMaterial*)" remove="all"/> <!-- Private QRhi class --> <modify-function signature="setShader(QSGMaterialShader::Stage,QShader)" remove="all"/> </object-type> <object-type name="QSGMaterialType"/> + <object-type name="QSGNinePatchNode"/> <object-type name="QSGNode"> <enum-type name="DirtyStateBit" flags="DirtyState"/> <enum-type name="Flag" flags="Flags"/> <enum-type name="NodeType"/> </object-type> + <object-type name="QSGNodeVisitor"/> + <object-type name="QSGOpacityNode"/> <object-type name="QSGOpaqueTextureMaterial"/> <object-type name="QSGSimpleRectNode"/> @@ -210,6 +224,7 @@ <enum-type name="RenderingFlag" flags="RenderingFlags"/> <object-type name="RenderState"/> </object-type> + <object-type name="QSGRootNode"/> <object-type name="QSGTexture"> <enum-type name="AnisotropyLevel"/> <enum-type name="Filtering"/> diff --git a/sources/pyside6/PySide6/QtQuickControls2/CMakeLists.txt b/sources/pyside6/PySide6/QtQuickControls2/CMakeLists.txt index 72c046a57..9951d2e1e 100644 --- a/sources/pyside6/PySide6/QtQuickControls2/CMakeLists.txt +++ b/sources/pyside6/PySide6/QtQuickControls2/CMakeLists.txt @@ -7,6 +7,7 @@ set (QtQuickControls2_DROPPED_ENTRIES) set(QtQuickControls2_SRC ${QtQuickControls2_GEN_DIR}/qquickstyle_wrapper.cpp +${QtQuickControls2_GEN_DIR}/qquickattachedpropertypropagator_wrapper.cpp # module is always needed ${QtQuickControls2_GEN_DIR}/qtquickcontrols2_module_wrapper.cpp ) diff --git a/sources/pyside6/PySide6/QtQuickControls2/typesystem_quickcontrols2.xml b/sources/pyside6/PySide6/QtQuickControls2/typesystem_quickcontrols2.xml index 401340522..72fc05226 100644 --- a/sources/pyside6/PySide6/QtQuickControls2/typesystem_quickcontrols2.xml +++ b/sources/pyside6/PySide6/QtQuickControls2/typesystem_quickcontrols2.xml @@ -8,5 +8,6 @@ <load-typesystem name="QtQuick/typesystem_quick.xml" generate="no"/> <object-type name="QQuickStyle"/> + <object-type name="QQuickAttachedPropertyPropagator"/> </typesystem> diff --git a/sources/pyside6/PySide6/QtRemoteObjects/CMakeLists.txt b/sources/pyside6/PySide6/QtRemoteObjects/CMakeLists.txt index 8d7126bb1..07835b2f6 100644 --- a/sources/pyside6/PySide6/QtRemoteObjects/CMakeLists.txt +++ b/sources/pyside6/PySide6/QtRemoteObjects/CMakeLists.txt @@ -5,6 +5,7 @@ project(QtRemoteObjects) set(QtRemoteObjects_SRC ${QtRemoteObjects_GEN_DIR}/qabstractitemmodelreplica_wrapper.cpp +${QtRemoteObjects_GEN_DIR}/qconnectionabstractserver_wrapper.cpp ${QtRemoteObjects_GEN_DIR}/qremoteobjectabstractpersistedstore_wrapper.cpp ${QtRemoteObjects_GEN_DIR}/qremoteobjectdynamicreplica_wrapper.cpp ${QtRemoteObjects_GEN_DIR}/qremoteobjecthost_wrapper.cpp @@ -18,6 +19,11 @@ ${QtRemoteObjects_GEN_DIR}/qremoteobjectreplica_wrapper.cpp ${QtRemoteObjects_GEN_DIR}/qtremoteobjects_wrapper.cpp ${QtRemoteObjects_GEN_DIR}/qremoteobjectsettingsstore_wrapper.cpp ${QtRemoteObjects_GEN_DIR}/qremoteobjectsourcelocationinfo_wrapper.cpp +${QtRemoteObjects_GEN_DIR}/qtroclientfactory_wrapper.cpp +${QtRemoteObjects_GEN_DIR}/qtroclientiodevice_wrapper.cpp +${QtRemoteObjects_GEN_DIR}/qtroiodevicebase_wrapper.cpp +${QtRemoteObjects_GEN_DIR}/qtroserverfactory_wrapper.cpp +${QtRemoteObjects_GEN_DIR}/qtroserveriodevice_wrapper.cpp # module is always needed ${QtRemoteObjects_GEN_DIR}/qtremoteobjects_module_wrapper.cpp @@ -29,7 +35,8 @@ set(QtRemoteObjects_include_dirs ${QtRemoteObjects_SOURCE_DIR} ${SHIBOKEN_INCLUDE_DIR} ${libpyside_SOURCE_DIR} ${SHIBOKEN_PYTHON_INCLUDE_DIR} - ${QtCore_GEN_DIR}) + ${QtCore_GEN_DIR} + ${QtNetwork_GEN_DIR}) set(QtRemoteObjects_libraries pyside6 ${Qt${QT_MAJOR_VERSION}RemoteObjects_LIBRARIES}) diff --git a/sources/pyside6/PySide6/QtRemoteObjects/typesystem_remoteobjects.xml b/sources/pyside6/PySide6/QtRemoteObjects/typesystem_remoteobjects.xml index 9e351d0e0..86e4d9093 100644 --- a/sources/pyside6/PySide6/QtRemoteObjects/typesystem_remoteobjects.xml +++ b/sources/pyside6/PySide6/QtRemoteObjects/typesystem_remoteobjects.xml @@ -7,6 +7,7 @@ namespace-begin="QT_BEGIN_NAMESPACE" namespace-end="QT_END_NAMESPACE"> <load-typesystem name="templates/core_common.xml" generate="no"/> <load-typesystem name="QtCore/typesystem_core.xml" generate="no"/> + <load-typesystem name="QtNetwork/typesystem_network.xml" generate="no"/> <rejection class="QRemoteObjectStringLiterals"/> <rejection class="*" function-name="getTypeNameAndMetaobjectFromClassInfo"/> @@ -16,6 +17,7 @@ <enum-type name="QRemoteObjectPacketTypeEnum"/> </namespace-type> <object-type name="QAbstractItemModelReplica"/> + <object-type name="QConnectionAbstractServer"/> <object-type name="QRemoteObjectAbstractPersistedStore"/> <object-type name="QRemoteObjectDynamicReplica"/> <object-type name="QRemoteObjectHost"/> @@ -37,6 +39,11 @@ </object-type> <object-type name="QRemoteObjectSettingsStore"/> <value-type name="QRemoteObjectSourceLocationInfo"/> + <object-type name="QtROClientFactory"/> + <object-type name="QtROClientIoDevice"/> + <object-type name="QtROIoDeviceBase"/> + <object-type name="QtROServerFactory"/> + <object-type name="QtROServerIoDevice"/> <suppress-warning text="^.*Typedef used on signal QRemoteObject.*$"/> <suppress-warning text="^QRemoteObjectPendingCallWatcher inherits from a non polymorphic type.*$"/> diff --git a/sources/pyside6/PySide6/QtSerialBus/CMakeLists.txt b/sources/pyside6/PySide6/QtSerialBus/CMakeLists.txt index f3bf0c805..310a8b0f0 100644 --- a/sources/pyside6/PySide6/QtSerialBus/CMakeLists.txt +++ b/sources/pyside6/PySide6/QtSerialBus/CMakeLists.txt @@ -8,25 +8,35 @@ set(QtSerialBus_DROPPED_ENTRIES ) set(QtSerialBus_SRC ${QtSerialBus_GEN_DIR}/qcanbus_wrapper.cpp - ${QtSerialBus_GEN_DIR}/qcanbusdevice_wrapper.cpp ${QtSerialBus_GEN_DIR}/qcanbusdevice_filter_wrapper.cpp + ${QtSerialBus_GEN_DIR}/qcanbusdevice_wrapper.cpp ${QtSerialBus_GEN_DIR}/qcanbusdeviceinfo_wrapper.cpp ${QtSerialBus_GEN_DIR}/qcanbusfactory_wrapper.cpp - ${QtSerialBus_GEN_DIR}/qcanbusframe_wrapper.cpp ${QtSerialBus_GEN_DIR}/qcanbusframe_timestamp_wrapper.cpp + ${QtSerialBus_GEN_DIR}/qcanbusframe_wrapper.cpp + ${QtSerialBus_GEN_DIR}/qcandbcfileparser_wrapper.cpp + ${QtSerialBus_GEN_DIR}/qcanframeprocessor_parseresult_wrapper.cpp + ${QtSerialBus_GEN_DIR}/qcanframeprocessor_wrapper.cpp + ${QtSerialBus_GEN_DIR}/qcanmessagedescription_wrapper.cpp + ${QtSerialBus_GEN_DIR}/qcansignaldescription_multiplexvaluerange_wrapper.cpp + ${QtSerialBus_GEN_DIR}/qcansignaldescription_wrapper.cpp + ${QtSerialBus_GEN_DIR}/qcanuniqueiddescription_wrapper.cpp ${QtSerialBus_GEN_DIR}/qmodbusclient_wrapper.cpp ${QtSerialBus_GEN_DIR}/qmodbusdataunit_wrapper.cpp ${QtSerialBus_GEN_DIR}/qmodbusdevice_wrapper.cpp ${QtSerialBus_GEN_DIR}/qmodbusdeviceidentification_wrapper.cpp + ${QtSerialBus_GEN_DIR}/qmodbusexceptionresponse_wrapper.cpp ${QtSerialBus_GEN_DIR}/qmodbuspdu_wrapper.cpp ${QtSerialBus_GEN_DIR}/qmodbusreply_wrapper.cpp ${QtSerialBus_GEN_DIR}/qmodbusrequest_wrapper.cpp + ${QtSerialBus_GEN_DIR}/qmodbusresponse_wrapper.cpp ${QtSerialBus_GEN_DIR}/qmodbusrtuserialclient_wrapper.cpp ${QtSerialBus_GEN_DIR}/qmodbusrtuserialserver_wrapper.cpp ${QtSerialBus_GEN_DIR}/qmodbusserver_wrapper.cpp ${QtSerialBus_GEN_DIR}/qmodbustcpclient_wrapper.cpp ${QtSerialBus_GEN_DIR}/qmodbustcpconnectionobserver_wrapper.cpp ${QtSerialBus_GEN_DIR}/qmodbustcpserver_wrapper.cpp + ${QtSerialBus_GEN_DIR}/qtcanbus_wrapper.cpp # module is always needed ${QtSerialBus_GEN_DIR}/qtserialbus_module_wrapper.cpp ) diff --git a/sources/pyside6/PySide6/QtSerialBus/typesystem_serialbus.xml b/sources/pyside6/PySide6/QtSerialBus/typesystem_serialbus.xml index e565e0663..fdd2b1483 100644 --- a/sources/pyside6/PySide6/QtSerialBus/typesystem_serialbus.xml +++ b/sources/pyside6/PySide6/QtSerialBus/typesystem_serialbus.xml @@ -10,6 +10,13 @@ <load-typesystem name="QtNetwork/typesystem_network.xml" generate="no"/> <load-typesystem name="QtSerialPort/typesystem_serialport.xml" generate="no"/> + <namespace-type name="QtCanBus"> + <enum-type name="DataSource"/> + <enum-type name="DataFormat"/> + <enum-type name="MultiplexState"/> + <enum-type name="UniqueId"/> + </namespace-type> + <object-type name="QCanBus"> <!-- Remove errorMessage argument, return tuple instead. --> <modify-function signature="availableDevices(QString,QString*)const"> @@ -62,6 +69,18 @@ <enum-type name="FrameError" flags="FrameErrors"/> <value-type name="TimeStamp"/> </value-type> + <object-type name="QCanDbcFileParser"> + <enum-type name="Error"/> + </object-type> + <object-type name="QCanFrameProcessor"> + <enum-type name="Error"/> + <value-type name="ParseResult"/> + </object-type> + <value-type name="QCanMessageDescription"/> + <value-type name="QCanSignalDescription"> + <value-type name="MultiplexValueRange"/> + </value-type> + <value-type name="QCanUniqueIdDescription"/> <object-type name="QModbusClient"/> <value-type name="QModbusDataUnit"> <enum-type name="RegisterType"/> @@ -82,6 +101,8 @@ <enum-type name="FunctionCode"/> <modify-field name="ExceptionByte" remove="true"/> <!-- Link error --> </object-type> + <object-type name="QModbusExceptionResponse"/> + <object-type name="QModbusResponse"/> <object-type name="QModbusReply"> <enum-type name="ReplyType"/> </object-type> diff --git a/sources/pyside6/PySide6/QtWebEngineCore/CMakeLists.txt b/sources/pyside6/PySide6/QtWebEngineCore/CMakeLists.txt index 2aa7d0b9e..0cdaf2f91 100644 --- a/sources/pyside6/PySide6/QtWebEngineCore/CMakeLists.txt +++ b/sources/pyside6/PySide6/QtWebEngineCore/CMakeLists.txt @@ -5,6 +5,8 @@ project(QtWebEngineCore) set(QtWebEngineCore_SRC ${QtWebEngineCore_GEN_DIR}/qwebenginecertificateerror_wrapper.cpp +${QtWebEngineCore_GEN_DIR}/qwebengineclientcertificateselection_wrapper.cpp +${QtWebEngineCore_GEN_DIR}/qwebengineclientcertificatestore_wrapper.cpp ${QtWebEngineCore_GEN_DIR}/qwebenginecontextmenurequest_wrapper.cpp ${QtWebEngineCore_GEN_DIR}/qwebenginecookiestore_wrapper.cpp ${QtWebEngineCore_GEN_DIR}/qwebenginecookiestore_filterrequest_wrapper.cpp @@ -13,10 +15,13 @@ ${QtWebEngineCore_GEN_DIR}/qwebenginedownloadrequest_wrapper.cpp ${QtWebEngineCore_GEN_DIR}/qwebenginefilesystemaccessrequest_wrapper.cpp ${QtWebEngineCore_GEN_DIR}/qwebenginefindtextresult_wrapper.cpp ${QtWebEngineCore_GEN_DIR}/qwebenginefullscreenrequest_wrapper.cpp +${QtWebEngineCore_GEN_DIR}/qwebengineglobalsettings_wrapper.cpp +${QtWebEngineCore_GEN_DIR}/qwebengineglobalsettings_dnsmode_wrapper.cpp ${QtWebEngineCore_GEN_DIR}/qwebenginehistory_wrapper.cpp ${QtWebEngineCore_GEN_DIR}/qwebenginehistoryitem_wrapper.cpp ${QtWebEngineCore_GEN_DIR}/qwebenginehistorymodel_wrapper.cpp ${QtWebEngineCore_GEN_DIR}/qwebenginehttprequest_wrapper.cpp +${QtWebEngineCore_GEN_DIR}/qwebenginenavigationrequest_wrapper.cpp ${QtWebEngineCore_GEN_DIR}/qwebengineloadinginfo_wrapper.cpp # FIXME ${QtWebEngineCore_GEN_DIR}/qwebenginemediasourcemodel_wrapper.cpp ${QtWebEngineCore_GEN_DIR}/qwebenginenewwindowrequest_wrapper.cpp diff --git a/sources/pyside6/PySide6/QtWebEngineCore/typesystem_webenginecore.xml b/sources/pyside6/PySide6/QtWebEngineCore/typesystem_webenginecore.xml index a72cfaefa..da9259ccc 100644 --- a/sources/pyside6/PySide6/QtWebEngineCore/typesystem_webenginecore.xml +++ b/sources/pyside6/PySide6/QtWebEngineCore/typesystem_webenginecore.xml @@ -17,6 +17,9 @@ <function signature="qWebEngineChromiumSecurityPatchVersion()"/> <function signature="qWebEngineVersion()"/> + <value-type name="QWebEngineClientCertificateSelection"/> + <object-type name="QWebEngineClientCertificateStore"/> + <object-type name="QWebEngineCookieStore"> <inject-code class="native" position="beginning" file="../glue/qtwebenginecore.cpp" snippet="qwebenginecookiestore-functor"/> @@ -61,6 +64,11 @@ <enum-type name="Roles"/> </object-type> + <object-type name="QWebEngineNavigationRequest"> + <enum-type name="NavigationType"/> + <enum-type name="NavigationRequestAction"/> + </object-type> + <object-type name="QWebEngineNotification"/> <object-type name="QWebEnginePage"> @@ -74,6 +82,16 @@ <enum-type name="FileSelectionMode"/> <enum-type name="JavaScriptConsoleMessageLevel"/> <enum-type name="RenderProcessTerminationStatus"/> + <add-function signature="javaScriptPromptPyOverride(QUrl@securityOrigin@,QString@msg@,QString@defaultValue@)" + return-type="std::pair<bool,QString>" python-override="true"/> + <modify-function signature="javaScriptPrompt(QUrl,QString,QString,QString*)"> + <inject-code class="shell" position="override" file="../glue/qtwebenginecore.cpp" + snippet="qwebenginepage-javascriptprompt-virtual-redirect"/> + <modify-argument index="return" pyi-type="Tuple[bool, str]"/> + <modify-argument index="4"><remove-default-expression/><remove-argument/></modify-argument> + <inject-code class="target" position="beginning" file="../glue/qtwebenginecore.cpp" + snippet="qwebenginepage-javascriptprompt-return"/> + </modify-function> <add-function signature="findText(const QString &,QWebEnginePage::FindFlags,PyObject*)"> <inject-code class="target" position="beginning" file="../glue/qtwebenginewidgets.cpp" snippet="qwebenginepage-findtext"/> </add-function> @@ -163,6 +181,10 @@ <enum-type name="PinEntryError"/> <enum-type name="RequestFailureReason"/> </object-type> + <namespace-type name="QWebEngineGlobalSettings"> + <enum-type name="SecureDnsMode"/> + <value-type name="DnsMode"/> + </namespace-type> <!-- QtQml is pulled in via QtWebEngineCoreDepends. --> <suppress-warning text="^Scoped enum 'QQml.*' does not have a type entry.*$"/> diff --git a/sources/pyside6/PySide6/QtWebEngineQuick/CMakeLists.txt b/sources/pyside6/PySide6/QtWebEngineQuick/CMakeLists.txt index 5aedc1147..d87dc55a4 100644 --- a/sources/pyside6/PySide6/QtWebEngineQuick/CMakeLists.txt +++ b/sources/pyside6/PySide6/QtWebEngineQuick/CMakeLists.txt @@ -14,17 +14,26 @@ set(QtWebEngineQuick_include_dirs ${QtWebEngineQuick_SOURCE_DIR} ${QtWebEngineQuick_BINARY_DIR} ${Qt${QT_MAJOR_VERSION}Core_INCLUDE_DIRS} + ${Qt${QT_MAJOR_VERSION}Gui_INCLUDE_DIRS} + ${Qt${QT_MAJOR_VERSION}Widgets_INCLUDE_DIRS} ${Qt${QT_MAJOR_VERSION}Network_INCLUDE_DIRS} + ${Qt${QT_MAJOR_VERSION}PrintSupport_INCLUDE_DIRS} ${Qt${QT_MAJOR_VERSION}Qml_INCLUDE_DIRS} + ${Qt${QT_MAJOR_VERSION}WebChannel_INCLUDE_DIRS} ${libpyside_SOURCE_DIR} ${QtCore_GEN_DIR} + ${QtGui_GEN_DIR} + ${QtWidgets_GEN_DIR} ${QtNetwork_GEN_DIR} - ${QtQml_GEN_DIR}) + ${QtWebEngineCore_GEN_DIR} + ${QtPrintSupport_GEN_DIR} + ${QtQml_GEN_DIR} + ${QtWebChannel_GEN_DIR}) set(QtWebEngineQuick_libraries pyside6 ${Qt${QT_MAJOR_VERSION}WebEngineQuick_LIBRARIES}) -set(QtWebEngineQuick_deps QtQml QtNetwork QtCore) +set(QtWebEngineQuick_deps QtQml QtWebEngineCore) create_pyside_module(NAME QtWebEngineQuick INCLUDE_DIRS QtWebEngineQuick_include_dirs diff --git a/sources/pyside6/PySide6/QtWebEngineQuick/typesystem_webenginequick.xml b/sources/pyside6/PySide6/QtWebEngineQuick/typesystem_webenginequick.xml index 108ba5fae..2e7d22334 100644 --- a/sources/pyside6/PySide6/QtWebEngineQuick/typesystem_webenginequick.xml +++ b/sources/pyside6/PySide6/QtWebEngineQuick/typesystem_webenginequick.xml @@ -6,6 +6,7 @@ <typesystem package="PySide6.QtWebEngineQuick" namespace-begin="QT_BEGIN_NAMESPACE" namespace-end="QT_END_NAMESPACE"> <load-typesystem name="QtQml/typesystem_qml.xml" generate="no"/> + <load-typesystem name="QtWebEngineCore/typesystem_webenginecore.xml" generate="no"/> <namespace-type name="QtWebEngineQuick"/> <!-- initialize() --> diff --git a/sources/pyside6/PySide6/QtWidgets/typesystem_widgets_common.xml b/sources/pyside6/PySide6/QtWidgets/typesystem_widgets_common.xml index 0669c18f6..b1188a514 100644 --- a/sources/pyside6/PySide6/QtWidgets/typesystem_widgets_common.xml +++ b/sources/pyside6/PySide6/QtWidgets/typesystem_widgets_common.xml @@ -1205,7 +1205,7 @@ <modify-function signature="getColor(const QColor&,QWidget*,const QString&,QFlags<QColorDialog::ColorDialogOption>)" allow-thread="yes"/> </object-type> - <object-type name="QLayout"> + <object-type name="QLayout" polymorphic-base="true"> <inject-code class="native" position="beginning" file="../glue/qtwidgets.cpp" snippet="qwidget-retrieveobjectname"/> <inject-code class="native" position="beginning" file="../glue/qtwidgets.cpp" snippet="qlayout-help-functions"/> @@ -1980,7 +1980,7 @@ <modify-function signature="removeItemWidget(QListWidgetItem*)" allow-thread="yes"/> </object-type> - <object-type name="QWidget" delete-in-main-thread="true"> + <object-type name="QWidget" delete-in-main-thread="true" polymorphic-base="true"> <!-- see QWindow::nativeEvent(), QAbstractNativeEventFilter::nativeEventFilter() --> <inject-code class="native" position="beginning" file="../glue/qtwidgets.cpp" snippet="qwidget-addaction-glue"/> <inject-code class="native" position="beginning"> @@ -2032,9 +2032,6 @@ </modify-function> <modify-function signature="parentWidget()const"> - <modify-argument index="this"> - <parent index="return" action="add"/> - </modify-argument> <modify-argument index="return"> <define-ownership class="target" owner="default"/> </modify-argument> diff --git a/sources/pyside6/PySide6/QtXml/CMakeLists.txt b/sources/pyside6/PySide6/QtXml/CMakeLists.txt index 82001cc60..b0e4630dd 100644 --- a/sources/pyside6/PySide6/QtXml/CMakeLists.txt +++ b/sources/pyside6/PySide6/QtXml/CMakeLists.txt @@ -9,6 +9,7 @@ ${QtXml_GEN_DIR}/qdomcdatasection_wrapper.cpp ${QtXml_GEN_DIR}/qdomcharacterdata_wrapper.cpp ${QtXml_GEN_DIR}/qdomcomment_wrapper.cpp ${QtXml_GEN_DIR}/qdomdocument_wrapper.cpp +${QtXml_GEN_DIR}/qdomdocument_parseresult_wrapper.cpp ${QtXml_GEN_DIR}/qdomdocumentfragment_wrapper.cpp ${QtXml_GEN_DIR}/qdomdocumenttype_wrapper.cpp ${QtXml_GEN_DIR}/qdomelement_wrapper.cpp diff --git a/sources/pyside6/PySide6/QtXml/typesystem_xml.xml b/sources/pyside6/PySide6/QtXml/typesystem_xml.xml index 1d6b20c28..3661a67bf 100644 --- a/sources/pyside6/PySide6/QtXml/typesystem_xml.xml +++ b/sources/pyside6/PySide6/QtXml/typesystem_xml.xml @@ -19,6 +19,9 @@ <value-type name="QDomDocument"> <enum-type name="ParseOption" flags="ParseOptions" since="6.5"/> <!-- will be replaced in inject code --> + + <value-type name="ParseResult"/> + <modify-function signature="setContent(const QByteArray&,bool,QString*,int*,int*)"> <modify-argument index="3"> <remove-argument/> diff --git a/sources/pyside6/PySide6/glue/qtcore.cpp b/sources/pyside6/PySide6/glue/qtcore.cpp index bc51d26d7..fc2fe7a58 100644 --- a/sources/pyside6/PySide6/glue/qtcore.cpp +++ b/sources/pyside6/PySide6/glue/qtcore.cpp @@ -84,11 +84,9 @@ static PyObject *convertToPrimitiveType(const QVariant &out, int metaTypeId) return PyFloat_FromDouble(out.toFloat()); case QMetaType::Bool: if (out.toBool()) { - Py_INCREF(Py_True); - return Py_True; + Py_RETURN_TRUE; } - Py_INCREF(Py_False); - return Py_False; + Py_RETURN_FALSE; default: break; } @@ -632,20 +630,16 @@ if (ret == nullptr) { // @snippet qbytearray-mgetitem if (PyIndex_Check(_key)) { const Py_ssize_t _i = PyNumber_AsSsize_t(_key, PyExc_IndexError); - if (_i < 0 || _i >= %CPPSELF.size()) { - PyErr_SetString(PyExc_IndexError, "index out of bounds"); - return nullptr; - } + if (_i < 0 || _i >= %CPPSELF.size()) + return PyErr_Format(PyExc_IndexError, "index out of bounds"); char res[2] = {%CPPSELF.at(_i), '\0'}; return PyBytes_FromStringAndSize(res, 1); } -if (PySlice_Check(_key) == 0) { - PyErr_Format(PyExc_TypeError, +if (PySlice_Check(_key) == 0) + return PyErr_Format(PyExc_TypeError, "list indices must be integers or slices, not %.200s", Py_TYPE(_key)->tp_name); - return nullptr; -} Py_ssize_t start, stop, step, slicelength; if (PySlice_GetIndicesEx(_key, %CPPSELF.size(), &start, &stop, &step, &slicelength) < 0) diff --git a/sources/pyside6/PySide6/glue/qtgui.cpp b/sources/pyside6/PySide6/glue/qtgui.cpp index 130de11bb..5c860a2bf 100644 --- a/sources/pyside6/PySide6/glue/qtgui.cpp +++ b/sources/pyside6/PySide6/glue/qtgui.cpp @@ -912,10 +912,8 @@ return %CPPSELF.rectCount(); // @snippet qregion-len // @snippet qregion-getitem -if (_i < 0 || _i >= %CPPSELF.rectCount()) { - PyErr_SetString(PyExc_IndexError, "index out of bounds"); - return nullptr; -} +if (_i < 0 || _i >= %CPPSELF.rectCount()) + return PyErr_Format(PyExc_IndexError, "index out of bounds"); const QRect cppResult = *(%CPPSELF.cbegin() + _i); return %CONVERTTOPYTHON[QRect](cppResult); diff --git a/sources/pyside6/PySide6/glue/qtuitools.cpp b/sources/pyside6/PySide6/glue/qtuitools.cpp index 0f4405944..1835ed096 100644 --- a/sources/pyside6/PySide6/glue/qtuitools.cpp +++ b/sources/pyside6/PySide6/glue/qtuitools.cpp @@ -45,7 +45,7 @@ static PyObject *QUiLoadedLoadUiFromDevice(QUiLoader *self, QIODevice *dev, QWid } if (!PyErr_Occurred()) - PyErr_SetString(PyExc_RuntimeError, "Unable to open/read ui device"); + PyErr_Format(PyExc_RuntimeError, "Unable to open/read ui device"); return nullptr; } diff --git a/sources/pyside6/PySide6/glue/qtwebenginecore.cpp b/sources/pyside6/PySide6/glue/qtwebenginecore.cpp index 50ef554f0..76a7c6d73 100644 --- a/sources/pyside6/PySide6/glue/qtwebenginecore.cpp +++ b/sources/pyside6/PySide6/glue/qtwebenginecore.cpp @@ -48,3 +48,17 @@ void QWebEngineNotificationFunctor::operator() // @snippet qwebengineprofile-setnotificationpresenter %CPPSELF.%FUNCTION_NAME(QWebEngineNotificationFunctor(%PYARG_1)); // @snippet qwebengineprofile-setnotificationpresenter + +// @snippet qwebenginepage-javascriptprompt-virtual-redirect +std::pair<bool, QString> resultPair = javaScriptPromptPyOverride(gil, pyOverride.object(), securityOrigin, msg, defaultValue); +result->assign(resultPair.second); +return resultPair.first; +// @snippet qwebenginepage-javascriptprompt-virtual-redirect + +// @snippet qwebenginepage-javascriptprompt-return +QString str; +%RETURN_TYPE retval_ = %CPPSELF.%FUNCTION_NAME(%1, %2, %3, &str); +%PYARG_0 = PyTuple_New(2); +PyTuple_SET_ITEM(%PYARG_0, 0, %CONVERTTOPYTHON[%RETURN_TYPE](retval_)); +PyTuple_SET_ITEM(%PYARG_0, 1, %CONVERTTOPYTHON[QString](str)); +// @snippet qwebenginepage-javascriptprompt-return diff --git a/sources/pyside6/PySide6/glue/qtwidgets.cpp b/sources/pyside6/PySide6/glue/qtwidgets.cpp index 1b3e94016..f886106cf 100644 --- a/sources/pyside6/PySide6/glue/qtwidgets.cpp +++ b/sources/pyside6/PySide6/glue/qtwidgets.cpp @@ -792,10 +792,8 @@ const char *styleOptionType(const QStyleOption *o) // @snippet qwizardpage-registerfield auto *signalInst = reinterpret_cast<PySideSignalInstance *>(%PYARG_4); const auto data = PySide::Signal::getEmitterData(signalInst); -if (data.methodIndex == -1) { - PyErr_SetString(PyExc_RuntimeError, "QWizardPage::registerField(): Unable to retrieve signal emitter."); - return nullptr; -} +if (data.methodIndex == -1) + return PyErr_Format(PyExc_RuntimeError, "QWizardPage::registerField(): Unable to retrieve signal emitter."); const auto method = data.emitter->metaObject()->method(data.methodIndex); const QByteArray signature = QByteArrayLiteral("2") + method.methodSignature(); %BEGIN_ALLOW_THREADS diff --git a/sources/pyside6/PySide6/qtdatavisualization_helper.h b/sources/pyside6/PySide6/qtdatavisualization_helper.h index 6884900ee..8fee4492d 100644 --- a/sources/pyside6/PySide6/qtdatavisualization_helper.h +++ b/sources/pyside6/PySide6/qtdatavisualization_helper.h @@ -6,8 +6,8 @@ #include <sbkpython.h> -#include <QtDataVisualization/QSurfaceDataProxy> -#include <QtCore/QList> +#include <QtDataVisualization/qsurfacedataproxy.h> +#include <QtCore/qlist.h> namespace QtDataVisualizationHelper { diff --git a/sources/pyside6/PySide6/qtgraphs_helper.h b/sources/pyside6/PySide6/qtgraphs_helper.h index 726f5fb37..e488fc7d3 100644 --- a/sources/pyside6/PySide6/qtgraphs_helper.h +++ b/sources/pyside6/PySide6/qtgraphs_helper.h @@ -6,8 +6,8 @@ #include <sbkpython.h> -#include <QtGraphs/QSurfaceDataProxy> -#include <QtCore/QList> +#include <QtGraphs/qsurfacedataproxy.h> +#include <QtCore/qlist.h> namespace QtGraphsHelper { diff --git a/sources/pyside6/doc/developer/add_tool.rst b/sources/pyside6/doc/developer/add_tool.rst index a894226c5..732e6b915 100644 --- a/sources/pyside6/doc/developer/add_tool.rst +++ b/sources/pyside6/doc/developer/add_tool.rst @@ -44,6 +44,8 @@ Add a Qt tool wrapper - Add the tool in ``sources/pyside-tools/pyside_tool.py``. - Add the tool in ``build_scripts/__init__.py`` to create the setuptools entry points i.e. this enable using the tool from the console as "pyside6-<tool_name>" -- Add an entry to ``sources/pyside6/doc/gettingstarted/package_details.rst``. +- Add an entry to ``sources/pyside6/doc/tools/index.rst`` and the detailed + documentation to ``sources/pyside6/doc/tools/<tool_name>.rst``. - Include the necessary Qt binaries explicitly on ``build_scripts/wheel_files.py`` -- Build with ``--standalone``, verify it is working. +- Add the necessary files to ``build_scripts/wheel_files.py``. +- Build with ``--standalone``, verify it is working. Also, check if the wheel bundles the tool. diff --git a/sources/pyside6/doc/developer/extras.rst b/sources/pyside6/doc/developer/extras.rst index 79fc72190..9788b539d 100644 --- a/sources/pyside6/doc/developer/extras.rst +++ b/sources/pyside6/doc/developer/extras.rst @@ -24,6 +24,19 @@ Build on the command line - Consider using ``build_scripts/qp5_tool.py``. +Build with address sanitizer (Linux) +==================================== + +ASAN needs to be told to not exit on memory leaks and its library +needs to be pre-loaded. Assuming the library is found +at ``/usr/lib/gcc/x86_64-linux-gnu/11``: + +.. code-block:: bash + + export ASAN_OPTIONS=detect_leaks=0 + export LD_PRELOAD=/usr/lib/gcc/x86_64-linux-gnu/11/libasan.so + python setup.py build [...] --sanitize-address + De-Virtualize the Python Files ============================== diff --git a/sources/pyside6/doc/extras/QtCore.ClassInfo.rst b/sources/pyside6/doc/extras/QtCore.ClassInfo.rst index aa080aa08..75445e1fc 100644 --- a/sources/pyside6/doc/extras/QtCore.ClassInfo.rst +++ b/sources/pyside6/doc/extras/QtCore.ClassInfo.rst @@ -1,11 +1,9 @@ .. currentmodule:: PySide6.QtCore -.. _ClassInfo: +.. py:decorator:: ClassInfo -ClassInfo -********* - -This class is used to associate extra information to the class, which is available -using QObject.metaObject(). Qt and PySide doesn't use this information. +This decorator is used to associate extra information to the class, which is available +using ``QObject.metaObject()``. This information is used by the +*Qt D-Bus* and *Qt Qml* modules. The extra information takes the form of a dictionary with key and value in a literal string. @@ -16,7 +14,7 @@ If the key needs to contain special characters (spaces, commas, '::', start with it is also possible to pass a python dictionary with arbitrary strings for both the key and value and enabling special characters in the key. -.. note:: This Class is a implementation of Q_CLASSINFO macro. +.. note:: This decorator is a implementation of the Q_CLASSINFO macro. Example diff --git a/sources/pyside6/doc/extras/QtQml.ListProperty.rst b/sources/pyside6/doc/extras/QtQml.ListProperty.rst new file mode 100644 index 000000000..eaa580c68 --- /dev/null +++ b/sources/pyside6/doc/extras/QtQml.ListProperty.rst @@ -0,0 +1,24 @@ +.. currentmodule:: PySide6.QtQml +.. py:class:: ListProperty + + The ``ListProperty`` class allows applications to expose list-like properties of + :class:`~PySide6.QtCore.QObject`-derived classes to QML. + The usage is shown in the :ref:`qml-object-and-list-property-types-example` + and the :ref:`qml-chapter5-listproperties` example. + + .. py:method:: __init__(type, append, count=None, at=None, clear=None, removeLast=None, doc="", notify=None, designable=True, scriptable=True, stored=True, user=False, constant=False, final=False) + + :param type type: Element type + :param callable append: A function to append an item + :param callable count: A function returning the list count + :param callable at: A function returning the item at an index + :param callable clear: A function to clear the list + :param removeLast: A function to remove the last item + :param str doc: Doc string + :param Signal notify: A signal emitted when a change occurs + :param bool designable: Not used in QML + :param bool scriptable: Not used in QML + :param bool stored: Whether the property is stored + :param bool user: Not used in QML + :param bool constant: Whether the property is constant + :param bool final: Whether the property is final diff --git a/sources/pyside6/doc/index.rst b/sources/pyside6/doc/index.rst index 83582835b..1bb28f9c1 100644 --- a/sources/pyside6/doc/index.rst +++ b/sources/pyside6/doc/index.rst @@ -99,7 +99,7 @@ Documentation PySide API reference. +++ - .. button-ref:: api + .. button-ref:: pyside-api :color: primary :outline: :expand: diff --git a/sources/pyside6/doc/tools/index.rst b/sources/pyside6/doc/tools/index.rst index dd51da65e..b421a428f 100644 --- a/sources/pyside6/doc/tools/index.rst +++ b/sources/pyside6/doc/tools/index.rst @@ -176,3 +176,36 @@ Deployment to deploy PySide6 application as an Android app targeting different Android platforms - aarch64, armv7a, i686, x86_64. + +Shader Tools +~~~~~~~~~~~~ + +.. grid:: 2 + :gutter: 3 3 4 5 + + .. grid-item-card:: ``pyside6-qsb`` + :link: pyside6-qsb + :link-type: ref + + a command-line tool provided by the Qt Shader Tools modules to + generate and inspect .qsb files. + +Qt Quick 3D +~~~~~~~~~~~ + +.. grid:: 2 + :gutter: 3 3 4 5 + + .. grid-item-card:: ``pyside6-balsam`` + :link: pyside6-balsam + :link-type: ref + + a command line tool that takes assets created in digital content + creation tools like Maya, 3ds Max or Blender and converts them into an + efficient runtime format for use with Qt Quick 3D. + + .. grid-item-card:: ``pyside6-balsamui`` + :link: pyside6-balsamui + :link-type: ref + + a graphical user interface for the ``pyside6-balsam`` tool. diff --git a/sources/pyside6/doc/tools/pyside6-balsam.rst b/sources/pyside6/doc/tools/pyside6-balsam.rst new file mode 100644 index 000000000..c6677f6a3 --- /dev/null +++ b/sources/pyside6/doc/tools/pyside6-balsam.rst @@ -0,0 +1,59 @@ +.. _pyside6-balsam: + +pyside6-balsam +============== + +``pyside6-qsb`` is a tool that wraps the `balsam <Balsam Asset Import Tool>`_ +tool provided with Qt Quick 3D. The Balsam tool is a command line application +that is part of Qt Quick 3D's asset conditioning pipeline. The purpose is to +take assets created in digital content creation tools like `Maya`_, `3ds Max`_ +or `Blender`_ and converts them into an efficient runtime format for use with Qt +Quick 3D. It is not possible, nor does it make sense to reference the +interchange formats directly in applications because a large amount of +resources are needed to parse and condition the content of the asset before it +is usable for real-time rendering. Instead, the interchange formats can be +converted via the Balsam tool into QML Components and resources like geometry +and textures. + + +For more information on how to use this tool, read Qt's documentation +here: `Balsam Asset Import Tool`_. + +Usage +----- + +.. code-block:: bash + + pyside6-balsam [options] sourceFileName + +To convert a 3D asset contained in the file ``testModel.fbx`` with +``pyside6-balsam`` the following command would be used: + +.. code-block:: bash + + pyside6-balsam testModel.fbx + +This would generate the following files: + +* meshes/testModel.mesh +* TestModel.qml + +Which can then be used in a Qt Quick 3D project by using that QML Component: + +.. code-block:: xml + + import QtQuick3D 1.0 + + Scene { + Model { + source: "TestModel.qml" + } + } + +For other modes of operation, refer to the `Balsam Asset Import Tool`_. + +.. _`Balsam Asset Import Tool`: https://doc.qt.io/qt-6/qtquick3d-tool-balsam.html +.. _Maya: https://www.autodesk.com/products/maya/overview +.. _3ds Max: https://www.autodesk.com/products/3ds-max/overview +.. _Blender: https://www.blender.org/ + diff --git a/sources/pyside6/doc/tools/pyside6-balsamui.rst b/sources/pyside6/doc/tools/pyside6-balsamui.rst new file mode 100644 index 000000000..f34cb6045 --- /dev/null +++ b/sources/pyside6/doc/tools/pyside6-balsamui.rst @@ -0,0 +1,22 @@ +.. _pyside6-balsamui: + +pyside6-balsamui +================ + +``pyside6-balsamui`` is graphical user interface frontend to the command line +tool :ref:`pyside6-balsam`. The purpose of the tool is to take assets created +in digital content creation tools like `Maya`_, `3ds Max`_ or `Blender`_ and +converts them into an efficient runtime format for use with Qt Quick 3D. + +For more information on the further capabilities of the tool, read Qt's +documentation here: `Balsam Asset Import Tool`_. + +.. image:: pyside6-balsamui_screenshot.webp + :width: 500 + :alt: pyside6-balsamui screenshot + +.. _`Balsam Asset Import Tool`: https://doc.qt.io/qt-6/qtquick3d-tool-balsam.html +.. _Maya: https://www.autodesk.com/products/maya/overview +.. _3ds Max: https://www.autodesk.com/products/3ds-max/overview +.. _Blender: https://www.blender.org/ + diff --git a/sources/pyside6/doc/tools/pyside6-balsamui_screenshot.webp b/sources/pyside6/doc/tools/pyside6-balsamui_screenshot.webp Binary files differnew file mode 100644 index 000000000..5c194fdb6 --- /dev/null +++ b/sources/pyside6/doc/tools/pyside6-balsamui_screenshot.webp diff --git a/sources/pyside6/doc/tools/pyside6-qsb.rst b/sources/pyside6/doc/tools/pyside6-qsb.rst new file mode 100644 index 000000000..f6f1847d4 --- /dev/null +++ b/sources/pyside6/doc/tools/pyside6-qsb.rst @@ -0,0 +1,39 @@ +.. _pyside6-qsb: + +pyside6-qsb +=========== + +``pyside6-qsb`` is a tool that wraps the `qsb <QSB Manual>`_ tool. qsb is a +command line tool provided by the `Qt Shader Tools`_ module. It integrates +third-party libraries such as `glslang`_ and `SPIRV-Cross`_, optionally invokes +external tools, such as ``fxc`` or ``spirv-opt``, and generates .qsb files. +Additionally, it can be used to inspect the contents of a .qsb package. + +For more information on how to use this tool, read Qt's documentation +here: `QSB Manual`_. + +Usage +----- + +To create a qsb file from a shader file, e.g., ``shader.frag``, use the +following command: + +.. code-block:: bash + + pyside6-qsb -o shader.frag.qsb shader.frag + +To inspect the file produced, i.e., ``shader.frag.qsb``, use the following +command: + +.. code-block:: bash + + pyside6-qsb -d shader.frag.qsb + +This will print the reflection metadata (in JSON form) and the included shaders. + +For other modes of operation, refer to the `QSB Manual`_. + +.. _`glslang`: https://github.com/KhronosGroup/glslang +.. _`spirv-cross`: https://github.com/KhronosGroup/SPIRV-Cross +.. _`QSB Manual`: https://doc.qt.io/qt-6/qtshadertools-qsb.html +.. _`Qt Shader Tools`: https://doc.qt.io/qt-6/qtshadertools-index.html diff --git a/sources/pyside6/doc/tutorials/basictutorial/qrcfiles.rst b/sources/pyside6/doc/tutorials/basictutorial/qrcfiles.rst index 21f4e1e2b..858293beb 100644 --- a/sources/pyside6/doc/tutorials/basictutorial/qrcfiles.rst +++ b/sources/pyside6/doc/tutorials/basictutorial/qrcfiles.rst @@ -46,7 +46,6 @@ In the following example, notice how the resources are listed in ``icons.qrc`` :: - </ui> <!DOCTYPE RCC><RCC version="1.0"> <qresource> <file>icons/play.png</file> diff --git a/sources/pyside6/libpyside/pyside.cpp b/sources/pyside6/libpyside/pyside.cpp index d5e815a42..9e12e3cd7 100644 --- a/sources/pyside6/libpyside/pyside.cpp +++ b/sources/pyside6/libpyside/pyside.cpp @@ -43,6 +43,7 @@ #include <QtCore/QMutex> #include <QtCore/QStack> #include <QtCore/QThread> +#include <QtCore/private/qobject_p.h> #include <algorithm> #include <cstring> @@ -63,6 +64,20 @@ QT_END_NAMESPACE Q_LOGGING_CATEGORY(lcPySide, "qt.pyside.libpyside", QtCriticalMsg) +static QObjectData *qt_object_private(const QObject *o) +{ + class FriendlyQObject : public QObject { + public: + using QObject::d_ptr; + }; + return static_cast<const FriendlyQObject *>(o)->d_ptr.data(); +} + +static bool hasDynamicMetaObject(const QObject *o) +{ + return qt_object_private(o)->metaObject != nullptr; +} + namespace PySide { @@ -675,6 +690,32 @@ static void invalidatePtr(any_t *object) static const char invalidatePropertyName[] = "_PySideInvalidatePtr"; +// PYSIDE-2749: Skip over internal QML classes and classes +// with dynamic meta objects when looking for the best matching +// type to avoid unnessarily triggering the lazy load mechanism +// for classes that do not have a binding from things like eventFilter(). +static inline bool isInternalObject(const char *name) +{ + return std::strstr(name, "QMLTYPE") != nullptr || std::strstr(name, "QQmlPrivate") != nullptr; +} + +static const QMetaObject *metaObjectCandidate(const QObject *o) +{ + auto *metaObject = o->metaObject(); + // Skip QML helper types and Python objects + if (hasDynamicMetaObject(o)) { + if (auto *super = metaObject->superClass()) + metaObject = super; + } + for (auto *candidate = metaObject; candidate != nullptr; candidate = candidate->superClass()) { + if (!isInternalObject(candidate->className())) { + metaObject = candidate; + break; + } + } + return metaObject; +} + // PYSIDE-1214, when creating new wrappers for classes inheriting QObject but // not exposed to Python, try to find the best-matching (most-derived) Qt // class by walking up the meta objects. @@ -682,19 +723,13 @@ static const char *typeName(const QObject *cppSelf) { const char *typeName = typeid(*cppSelf).name(); if (!Shiboken::Conversions::getConverter(typeName)) { - for (auto metaObject = cppSelf->metaObject(); metaObject; metaObject = metaObject->superClass()) { + auto *metaObject = metaObjectCandidate(cppSelf); + for (; metaObject != nullptr; metaObject = metaObject->superClass()) { const char *name = metaObject->className(); if (Shiboken::Conversions::getConverter(name)) { typeName = name; break; } - // PYSIDE-2404: Did not find the name. Load the lazy classes - // which have this name and try again. - Shiboken::Module::loadLazyClassesWithName(name); - if (Shiboken::Conversions::getConverter(name)) { - typeName = name; - break; - } } } return typeName; @@ -738,7 +773,7 @@ PyObject *getWrapperForQObject(QObject *cppSelf, PyTypeObject *sbk_type) } } - pyOut = Shiboken::Object::newObject(sbk_type, cppSelf, false, false, typeName(cppSelf)); + pyOut = Shiboken::Object::newObjectWithHeuristics(sbk_type, cppSelf, false, typeName(cppSelf)); return pyOut; } diff --git a/sources/pyside6/libpyside/pysideclassinfo.cpp b/sources/pyside6/libpyside/pysideclassinfo.cpp index 9ab5a7ad0..698cb1c76 100644 --- a/sources/pyside6/libpyside/pysideclassinfo.cpp +++ b/sources/pyside6/libpyside/pysideclassinfo.cpp @@ -51,16 +51,12 @@ PyObject *ClassInfoPrivate::tp_call(PyObject *self, PyObject *args, PyObject * / auto *pData = DecoratorPrivate::get<ClassInfoPrivate>(self); - if (pData->m_alreadyWrapped) { - PyErr_SetString(PyExc_TypeError, "This instance of ClassInfo() was already used to wrap an object"); - return nullptr; - } + if (pData->m_alreadyWrapped) + return PyErr_Format(PyExc_TypeError, "This instance of ClassInfo() was already used to wrap an object"); PyTypeObject *klassType = reinterpret_cast<PyTypeObject *>(klass); - if (!PySide::ClassInfo::setClassInfo(klassType, pData->m_data)) { - PyErr_SetString(PyExc_TypeError, "This decorator can only be used on classes that are subclasses of QObject"); - return nullptr; - } + if (!PySide::ClassInfo::setClassInfo(klassType, pData->m_data)) + return PyErr_Format(PyExc_TypeError, "This decorator can only be used on classes that are subclasses of QObject"); pData->m_alreadyWrapped = true; diff --git a/sources/pyside6/libpyside/pysidesignal.cpp b/sources/pyside6/libpyside/pysidesignal.cpp index c93cbadfb..11e07cb04 100644 --- a/sources/pyside6/libpyside/pysidesignal.cpp +++ b/sources/pyside6/libpyside/pysidesignal.cpp @@ -484,14 +484,10 @@ static PyObject *signalInstanceConnect(PyObject *self, PyObject *args, PyObject return nullptr; PySideSignalInstance *source = reinterpret_cast<PySideSignalInstance *>(self); - if (!source->d) { - PyErr_Format(PyExc_RuntimeError, "cannot connect uninitialized SignalInstance"); - return nullptr; - } - if (source->deleted) { - PyErr_Format(PyExc_RuntimeError, "Signal source has been deleted"); - return nullptr; - } + if (!source->d) + return PyErr_Format(PyExc_RuntimeError, "cannot connect uninitialized SignalInstance"); + if (source->deleted) + return PyErr_Format(PyExc_RuntimeError, "Signal source has been deleted"); Shiboken::AutoDecRef pyArgs(PyList_New(0)); @@ -564,10 +560,8 @@ static PyObject *signalInstanceConnect(PyObject *self, PyObject *args, PyObject Shiboken::AutoDecRef tupleArgs(PyList_AsTuple(pyArgs)); Shiboken::AutoDecRef pyMethod(PyObject_GetAttr(source->d->source, PySide::PySideName::qtConnect())); - if (pyMethod.isNull()) { // PYSIDE-79: check if pyMethod exists. - PyErr_SetString(PyExc_RuntimeError, "method 'connect' vanished!"); - return nullptr; - } + if (pyMethod.isNull()) // PYSIDE-79: check if pyMethod exists. + return PyErr_Format(PyExc_RuntimeError, "method 'connect' vanished!"); PyObject *result = PyObject_CallObject(pyMethod, tupleArgs); if (connection_Check(result)) return result; @@ -587,17 +581,13 @@ static int argCountInSignature(const char *signature) static PyObject *signalInstanceEmit(PyObject *self, PyObject *args) { PySideSignalInstance *source = reinterpret_cast<PySideSignalInstance *>(self); - if (!source->d) { - PyErr_Format(PyExc_RuntimeError, "cannot emit uninitialized SignalInstance"); - return nullptr; - } + if (!source->d) + return PyErr_Format(PyExc_RuntimeError, "cannot emit uninitialized SignalInstance"); // PYSIDE-2201: Check if the object has vanished meanwhile. // Tried to revive it without exception, but this gives problems. - if (source->deleted) { - PyErr_Format(PyExc_RuntimeError, "The SignalInstance object was already deleted"); - return nullptr; - } + if (source->deleted) + return PyErr_Format(PyExc_RuntimeError, "The SignalInstance object was already deleted"); Shiboken::AutoDecRef pyArgs(PyList_New(0)); int numArgsGiven = PySequence_Fast_GET_SIZE(args); @@ -657,17 +647,16 @@ static PyObject *signalInstanceGetItem(PyObject *self, PyObject *key) message += '"' + data->d->signature + '"'; } - PyErr_SetString(PyExc_IndexError, message.constData()); - return nullptr; + return PyErr_Format(PyExc_IndexError, message.constData()); } static inline void warnDisconnectFailed(PyObject *aSlot, const QByteArray &signature) { if (PyErr_Occurred() != nullptr) { // avoid "%S" invoking str() when an error is set. - PyErr_WarnFormat(PyExc_RuntimeError, 0, "Failed to disconnect (%s) from signal \"%s\".", + PyErr_WarnFormat(PyExc_RuntimeWarning, 0, "Failed to disconnect (%s) from signal \"%s\".", Py_TYPE(aSlot)->tp_name, signature.constData()); } else { - PyErr_WarnFormat(PyExc_RuntimeError, 0, "Failed to disconnect (%S) from signal \"%s\".", + PyErr_WarnFormat(PyExc_RuntimeWarning, 0, "Failed to disconnect (%S) from signal \"%s\".", aSlot, signature.constData()); } } @@ -675,10 +664,9 @@ static inline void warnDisconnectFailed(PyObject *aSlot, const QByteArray &signa static PyObject *signalInstanceDisconnect(PyObject *self, PyObject *args) { auto source = reinterpret_cast<PySideSignalInstance *>(self); - if (!source->d) { - PyErr_Format(PyExc_RuntimeError, "cannot disconnect uninitialized SignalInstance"); - return nullptr; - } + if (!source->d) + return PyErr_Format(PyExc_RuntimeError, "cannot disconnect uninitialized SignalInstance"); + Shiboken::AutoDecRef pyArgs(PyList_New(0)); PyObject *slot = Py_None; @@ -725,8 +713,7 @@ static PyObject *signalInstanceDisconnect(PyObject *self, PyObject *args) } warnDisconnectFailed(slot, source->d->signature); - Py_INCREF(Py_False); - return Py_False; + Py_RETURN_FALSE; } // PYSIDE-68: Supply the missing __get__ function @@ -761,10 +748,8 @@ static PyObject *signalCall(PyObject *self, PyObject *args, PyObject *kw) // The only way calling a signal can succeed (the Python equivalent of C++'s operator() ) // is when a method with the same name as the signal is attached to an object. // An example is QProcess::error() (don't check the docs, but the source code of qprocess.h). - if (!signal->homonymousMethod) { - PyErr_SetString(PyExc_TypeError, "native Qt signal is not callable"); - return nullptr; - } + if (!signal->homonymousMethod) + return PyErr_Format(PyExc_TypeError, "native Qt signal is not callable"); // Check if there exists a method with the same name as the signal, which is also a static // method in C++ land. diff --git a/sources/pyside6/libpysideqml/pysideqmllistproperty.cpp b/sources/pyside6/libpysideqml/pysideqmllistproperty.cpp index 97d6ce91b..75bb5af96 100644 --- a/sources/pyside6/libpysideqml/pysideqmllistproperty.cpp +++ b/sources/pyside6/libpysideqml/pysideqmllistproperty.cpp @@ -169,7 +169,7 @@ qsizetype propListCount(QQmlListProperty<QObject> *propList) return 0; } - int cppResult = 0; + qsizetype cppResult = 0; auto *converter = Shiboken::Conversions::PrimitiveTypeConverter<qsizetype>(); if (auto *pythonToCpp = Shiboken::Conversions::isPythonToCppConvertible(converter, retVal)) pythonToCpp(retVal, &cppResult); diff --git a/sources/pyside6/libpysideqml/pysideqmlregistertype.cpp b/sources/pyside6/libpysideqml/pysideqmlregistertype.cpp index 618d621bd..4ccd459d5 100644 --- a/sources/pyside6/libpysideqml/pysideqmlregistertype.cpp +++ b/sources/pyside6/libpysideqml/pysideqmlregistertype.cpp @@ -28,6 +28,7 @@ #include <QtQml/QJSValue> #include <QtQml/QQmlListProperty> #include <private/qqmlmetatype_p.h> +#include <private/qmetaobjectbuilder_p.h> #include <memory> @@ -191,19 +192,15 @@ namespace PySide::Qml { // Modern (6.7) type registration using RegisterTypeAndRevisions // and information set to QMetaClassInfo. -static int qmlRegisterType(PyObject *pyObj, PyObject *pyClassInfoObj, - const ImportData &importData) +static int qmlRegisterType(PyObject *pyObj, + const ImportData &importData, + const QMetaObject *metaObject, + const QMetaObject *classInfoMetaObject = nullptr) { - using namespace Shiboken; - PyTypeObject *pyObjType = reinterpret_cast<PyTypeObject *>(pyObj); - if (!isQObjectDerived(pyObjType, true)) - return -1; - const QMetaObject *metaObject = PySide::retrieveMetaObject(pyObjType); - Q_ASSERT(metaObject); - const QMetaObject *classInfoMetaObject = pyObj == pyClassInfoObj - ? metaObject : PySide::retrieveMetaObject(pyClassInfoObj); + if (classInfoMetaObject == nullptr) + classInfoMetaObject = metaObject; // Register as simple QObject rather than Qt Quick item. // Incref the type object, don't worry about decref'ing it because @@ -270,18 +267,44 @@ static int qmlRegisterType(PyObject *pyObj, PyObject *pyClassInfoObj, return qmlTypeId; } +static int qmlRegisterType(PyObject *pyObj, PyObject *pyClassInfoObj, + const ImportData &importData) +{ + PyTypeObject *pyObjType = reinterpret_cast<PyTypeObject *>(pyObj); + if (!isQObjectDerived(pyObjType, true)) + return -1; + + const QMetaObject *metaObject = PySide::retrieveMetaObject(pyObjType); + Q_ASSERT(metaObject); + const QMetaObject *classInfoMetaObject = pyObj == pyClassInfoObj + ? metaObject : PySide::retrieveMetaObject(pyClassInfoObj); + return qmlRegisterType(pyObj, importData, metaObject, classInfoMetaObject); +} + // Legacy (pre 6.7) compatibility helper for the free register functions. int qmlRegisterType(PyObject *pyObj, const char *uri, int versionMajor, int versionMinor, const char *qmlName, const char *noCreationReason, bool creatable) { auto *type = checkTypeObject(pyObj, "qmlRegisterType()"); - if (type == nullptr || !PySide::isQObjectDerived(type, true) - || !setClassInfo(type, qmlElementKey, qmlName)) + if (type == nullptr || !PySide::isQObjectDerived(type, true)) return -1; + + const QMetaObject *metaObject = PySide::retrieveMetaObject(type); + Q_ASSERT(metaObject); + + // PYSIDE-2709: Use a separate QMetaObject for the class information + // as modifying metaObject breaks inheritance. + QMetaObjectBuilder classInfobuilder(&QObject::staticMetaObject); + classInfobuilder.addClassInfo(qmlElementKey, qmlName); if (!creatable) - setUncreatableClassInfo(type, noCreationReason); - return qmlRegisterType(pyObj, pyObj, {uri, versionMajor, versionMinor}); + setUncreatableClassInfo(&classInfobuilder, noCreationReason); + auto *classInfoMetaObject = classInfobuilder.toMetaObject(); + + const int qmlTypeId = qmlRegisterType(pyObj, {uri, versionMajor, versionMinor}, + metaObject, classInfoMetaObject); + free(classInfoMetaObject); + return qmlTypeId; } // Singleton helpers @@ -369,7 +392,7 @@ QObject *SingletonQObjectCreationBase::handleReturnValue(PyObject *retVal) using Shiboken::Conversions::isPythonToCppPointerConvertible; // Make sure the callback returns something we can convert, else the entire application will crash. if (retVal == nullptr) { - PyErr_SetString(PyExc_TypeError, "Callback returns 0 value."); + PyErr_Format(PyExc_TypeError, "Callback returns 0 value."); return nullptr; } if (isPythonToCppPointerConvertible(qObjectType(), retVal) == nullptr) { diff --git a/sources/pyside6/libpysideqml/pysideqmluncreatable.cpp b/sources/pyside6/libpysideqml/pysideqmluncreatable.cpp index 55b15ba5b..7c0f6b8ff 100644 --- a/sources/pyside6/libpysideqml/pysideqmluncreatable.cpp +++ b/sources/pyside6/libpysideqml/pysideqmluncreatable.cpp @@ -10,6 +10,7 @@ #include <sbkcppstring.h> #include <QtCore/qbytearray.h> +#include <private/qmetaobjectbuilder_p.h> using namespace Qt::StringLiterals; @@ -109,3 +110,9 @@ void setUncreatableClassInfo(PyTypeObject *type, const QByteArray &reason) {"QML.Creatable"_ba, "false"_ba}, {"QML.UncreatableReason"_ba, reason} }); } + +void setUncreatableClassInfo(QMetaObjectBuilder *builder, const QByteArray &reason) +{ + builder->addClassInfo("QML.Creatable", "false"); + builder->addClassInfo("QML.UncreatableReason", reason); +} diff --git a/sources/pyside6/libpysideqml/pysideqmluncreatable.h b/sources/pyside6/libpysideqml/pysideqmluncreatable.h index 772ad4ccb..8a8adb3c8 100644 --- a/sources/pyside6/libpysideqml/pysideqmluncreatable.h +++ b/sources/pyside6/libpysideqml/pysideqmluncreatable.h @@ -8,6 +8,8 @@ #include <QtCore/QByteArray> +QT_FORWARD_DECLARE_CLASS(QMetaObjectBuilder) + // The QmlUncreatable decorator modifies QmlElement to register an uncreatable // type. Due to the (reverse) execution order of decorators, it needs to follow // QmlElement. @@ -19,5 +21,6 @@ extern "C" void initQmlUncreatable(PyObject *module); void setUncreatableClassInfo(PyTypeObject *type, const QByteArray &reason); +void setUncreatableClassInfo(QMetaObjectBuilder *builder, const QByteArray &reason); #endif // PYSIDEQMLUNCREATABLE_H diff --git a/sources/pyside6/tests/QtAsyncio/qasyncio_test_cancel_taskgroup.py b/sources/pyside6/tests/QtAsyncio/qasyncio_test_cancel_taskgroup.py new file mode 100644 index 000000000..aa8ce4718 --- /dev/null +++ b/sources/pyside6/tests/QtAsyncio/qasyncio_test_cancel_taskgroup.py @@ -0,0 +1,57 @@ +# Copyright (C) 2024 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +'''Test cases for QtAsyncio''' + +import asyncio +import unittest + +import PySide6.QtAsyncio as QtAsyncio + + +class QAsyncioTestCaseCancelTaskGroup(unittest.TestCase): + def setUp(self) -> None: + super().setUp() + # We only reach the end of the loop if the task is not cancelled. + self.loop_end_reached = False + + async def raise_error(self): + raise RuntimeError + + async def loop_short(self): + self._loop_end_reached = False + for _ in range(1000): + await asyncio.sleep(1e-3) + self._loop_end_reached = True + + async def loop_shorter(self): + self._loop_end_reached = False + for _ in range(1000): + await asyncio.sleep(1e-4) + self._loop_end_reached = True + + async def loop_the_shortest(self): + self._loop_end_reached = False + for _ in range(1000): + await asyncio.to_thread(lambda: None) + self._loop_end_reached = True + + async def main(self, coro): + async with asyncio.TaskGroup() as tg: + tg.create_task(coro()) + tg.create_task(self.raise_error()) + + def test_cancel_taskgroup(self): + coros = [self.loop_short, self.loop_shorter, self.loop_the_shortest] + + for coro in coros: + try: + QtAsyncio.run(self.main(coro), keep_running=False) + except ExceptionGroup as e: + self.assertEqual(len(e.exceptions), 1) + self.assertIsInstance(e.exceptions[0], RuntimeError) + self.assertFalse(self._loop_end_reached) + + +if __name__ == '__main__': + unittest.main() diff --git a/sources/pyside6/tests/QtCore/bug_927.py b/sources/pyside6/tests/QtCore/bug_927.py index 1ecea61b2..c15a7014b 100644 --- a/sources/pyside6/tests/QtCore/bug_927.py +++ b/sources/pyside6/tests/QtCore/bug_927.py @@ -24,7 +24,7 @@ class thread_function(): class Task(QRunnable): def run(self): - QThread.sleep(2) # Sleep 2 seconds + QThread.msleep(100) class QThreadPoolTest(unittest.TestCase): @@ -34,15 +34,15 @@ class QThreadPoolTest(unittest.TestCase): for i in range(3): task = Task() QThreadPool.globalInstance().start(task) - time.sleep(1) # Sleep 1 second + time.sleep(0.05) - QThreadPool.globalInstance().waitForDone() + self.assertTrue(QThreadPool.globalInstance().waitForDone()) def testCallable(self): global thread_function_called tp = QThreadPool.globalInstance() tp.start(thread_function) - tp.waitForDone() + self.assertTrue(tp.waitForDone()) self.assertTrue(thread_function_called) diff --git a/sources/pyside6/tests/QtCore/qobject_event_filter_test.py b/sources/pyside6/tests/QtCore/qobject_event_filter_test.py index 1cf07fbab..ab7a1b6ad 100644 --- a/sources/pyside6/tests/QtCore/qobject_event_filter_test.py +++ b/sources/pyside6/tests/QtCore/qobject_event_filter_test.py @@ -63,6 +63,17 @@ class FilteredObject(QObject): self.app.quit() +class PolymorphicIdFilterObject(QObject): + """PYSIDE-2675: Check whether QChildEvent.added() is accessible via PolymorphicId""" + def __init__(self, parent=None): + super().__init__(parent) + self.added = False + + def event(self, event): + self.added = event.added() + return False + + class TestQObjectEventFilterPython(UsesQApplication): '''QObject.eventFilter - Reimplemented in python Filters 5 TimerEvents and then bypasses the other events to the @@ -93,6 +104,11 @@ class TestQObjectEventFilterPython(UsesQApplication): self.assertEqual(filtered.times_called, 5) self.assertEqual(self.obj_filter.events_handled, 5) + def testPolymorphicId(self): + testObject = PolymorphicIdFilterObject() + t2 = QObject(testObject) # noqa: F841 + self.assertTrue(testObject.added) + @unittest.skipUnless(hasattr(sys, "getrefcount"), f"{sys.implementation.name} has no refcount") def testInstallEventFilterRefCountAfterDelete(self): '''Bug 910 - installEventFilter() increments reference count on target object diff --git a/sources/pyside6/tests/QtGui/qbrush_test.py b/sources/pyside6/tests/QtGui/qbrush_test.py index 800e6f072..69262328b 100644 --- a/sources/pyside6/tests/QtGui/qbrush_test.py +++ b/sources/pyside6/tests/QtGui/qbrush_test.py @@ -13,7 +13,7 @@ from init_paths import init_test_paths init_test_paths(False) from PySide6.QtCore import Qt -from PySide6.QtGui import QColor, QBrush +from PySide6.QtGui import QColor, QBrush, QConicalGradient from helper.usesqapplication import UsesQApplication @@ -30,6 +30,14 @@ class Constructor(UsesQApplication): obj = QBrush(Qt.blue) self.assertEqual(obj.color(), Qt.blue) + def testGradient(self): + """Test type discovery on class hierarchies with non-virtual + destructors by specifying a polymorphic-id-expression without + polymorphic-name-function.""" + gradient = QConicalGradient() + brush = QBrush(gradient) + self.assertEqual(type(brush.gradient()), type(gradient)) + if __name__ == '__main__': unittest.main() diff --git a/sources/pyside6/tests/QtQml/CMakeLists.txt b/sources/pyside6/tests/QtQml/CMakeLists.txt index 720f0ef99..30bf7e786 100644 --- a/sources/pyside6/tests/QtQml/CMakeLists.txt +++ b/sources/pyside6/tests/QtQml/CMakeLists.txt @@ -17,6 +17,7 @@ PYSIDE_TEST(bug_997.py) PYSIDE_TEST(bug_1029.py) PYSIDE_TEST(groupedproperty.py) PYSIDE_TEST(listproperty.py) +PYSIDE_TEST(qmlregistertype_test.py) PYSIDE_TEST(qqmlapplicationengine_test.py) PYSIDE_TEST(qqmlnetwork_test.py) PYSIDE_TEST(qqmlcomponent_test.py) diff --git a/sources/pyside6/tests/QtQml/listproperty.py b/sources/pyside6/tests/QtQml/listproperty.py index 8916aefe5..884600d29 100644 --- a/sources/pyside6/tests/QtQml/listproperty.py +++ b/sources/pyside6/tests/QtQml/listproperty.py @@ -7,11 +7,25 @@ import unittest from pathlib import Path sys.path.append(os.fspath(Path(__file__).resolve().parents[1])) -from init_paths import init_test_paths +from init_paths import init_test_paths # noqa: E402 init_test_paths(False) -from PySide6.QtCore import QObject -from PySide6.QtQml import ListProperty +from helper.usesqapplication import UsesQApplication # noqa: E402, F401 + +from PySide6.QtCore import QObject, QUrl, Property, qInstallMessageHandler # noqa: E402 +from PySide6.QtQml import ListProperty, QmlElement # noqa: E402 +from PySide6.QtQuick import QQuickView # noqa: E402 + + +QML_IMPORT_NAME = "test.ListPropertyTest" +QML_IMPORT_MAJOR_VERSION = 1 + +output_messages = [] + + +def message_handler(mode, context, message): + global output_messages + output_messages.append(f"{message}") class InheritsQObject(QObject): @@ -22,7 +36,46 @@ def dummyFunc(): pass -class TestListProperty(unittest.TestCase): +@QmlElement +class Person(QObject): + def __init__(self, parent=None): + super().__init__(parent=None) + self._name = '' + self._friends = [] + + def appendFriend(self, friend): + self._friends.append(friend) + + def friendCount(self): + return len(self._friends) + + def friend(self, index): + return self._friends[index] + + def removeLastItem(self): + if len(self._friends) > 0: + self._friends.pop() + + def replace(self, index, friend): + if 0 <= index < len(self._friends): + self._friends[index] = friend + + def clear(self): + self._friends.clear() + + @Property(str, final=True) + def name(self): + return self._name + + @name.setter + def name(self, value): + self._name = value + + friends = ListProperty(QObject, append=appendFriend, count=friendCount, at=friend, + removeLast=removeLastItem, replace=replace, clear=clear) + + +class TestListProperty(UsesQApplication): def testIt(self): # Verify that type checking works properly @@ -31,7 +84,7 @@ class TestListProperty(unittest.TestCase): try: ListProperty(QObject) ListProperty(InheritsQObject) - except: + except Exception: type_check_error = True self.assertFalse(type_check_error) @@ -47,21 +100,37 @@ class TestListProperty(unittest.TestCase): method_check_error = False try: - ListProperty(QObject, append=None, at=None, count=None, replace=None, clear=None, removeLast=None) # Explicitly setting None + ListProperty(QObject, append=None, at=None, count=None, replace=None, clear=None, + removeLast=None) # Explicitly setting None ListProperty(QObject, append=dummyFunc) ListProperty(QObject, count=dummyFunc, at=dummyFunc) - except: + except Exception: method_check_error = True self.assertFalse(method_check_error) try: - ListPropery(QObject, append=QObject()) - except: + ListProperty(QObject, append=QObject()) + except Exception: method_check_error = True self.assertTrue(method_check_error) + def testListPropParameters(self): + global output_messages + qInstallMessageHandler(message_handler) + view = QQuickView() + file = Path(__file__).resolve().parent / 'listproperty.qml' + self.assertTrue(file.is_file()) + view.setSource(QUrl.fromLocalFile(file)) + view.show() + self.assertEqual(output_messages[0], "List length: 3") + self.assertEqual(output_messages[1], "First element: Alice") + self.assertEqual(output_messages[2], "Removing last item: Charlie") + self.assertEqual(output_messages[3], "Replacing last item: Bob") + self.assertEqual(output_messages[4], "Replaced last item: David") + self.assertEqual(output_messages[5], "List length after clearing: 0") + if __name__ == '__main__': unittest.main() diff --git a/sources/pyside6/tests/QtQml/listproperty.qml b/sources/pyside6/tests/QtQml/listproperty.qml new file mode 100644 index 000000000..7b71e30ba --- /dev/null +++ b/sources/pyside6/tests/QtQml/listproperty.qml @@ -0,0 +1,50 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import QtQuick 2.0 +import test.ListPropertyTest + +Rectangle { + width: 360 + height: 360 + + Person { + id: person + friends: [ + Person{ + name: "Alice" + }, + Person{ + name: "Bob" + }, + Person{ + name: "Charlie" + } + ] + } + + Person{ + id: david + name: "David" + } + + Component.onCompleted: { + // Access the length of the list + console.log("List length: " + person.friends.length); + + // Access the first element of the list + console.log("First element: " + person.friends[0].name); + + // Remove the last item of the list + console.log("Removing last item: " + person.friends.pop().name); + + // Repalce the last item of the list + console.log("Replacing last item: " + person.friends[person.friends.length - 1].name); + person.friends[person.friends.length - 1] = david; + console.log("Replaced last item: " + person.friends[person.friends.length - 1].name); + + // Clear the list + person.friends = []; + console.log("List length after clearing: " + person.friends.length); + } +} diff --git a/sources/pyside6/tests/QtQml/qmlregistertype_test.py b/sources/pyside6/tests/QtQml/qmlregistertype_test.py new file mode 100644 index 000000000..0042d6fd3 --- /dev/null +++ b/sources/pyside6/tests/QtQml/qmlregistertype_test.py @@ -0,0 +1,53 @@ +# Copyright (C) 2024 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +import os +import sys +import unittest + +from pathlib import Path +sys.path.append(os.fspath(Path(__file__).resolve().parents[1])) +from init_paths import init_test_paths +init_test_paths(False) + +from helper.usesqapplication import UsesQApplication + + +from PySide6.QtCore import QCoreApplication, QObject # noqa: F401 +from PySide6.QtQml import QQmlApplicationEngine, qmlRegisterType + + +class BaseClass(QObject): + def __init__(self, p=None): + super().__init__(p) + + +class ChildClass(BaseClass): + def __init__(self, p=None): + super().__init__(p) + + +class TestQmlRegisterType(UsesQApplication): + """Test the legacy QML register functions.""" + + def test(self): + qmlRegisterType(BaseClass, 'test', 1, 0, 'BaseClass') + qmlRegisterType(ChildClass, 'test', 1, 0, 'ChildClass') + # PYSIDE-2709: qmlRegisterType() would set additional class info + # on the meta objects for registration which caused another meta + # object to be created, breaking inheritance. + child = ChildClass() + base = BaseClass() + self.assertTrue(child.metaObject().inherits(base.metaObject())) + + engine = QQmlApplicationEngine() + file = Path(__file__).resolve().parent / 'qmlregistertype_test.qml' + + engine.load(file) + rootObjects = engine.rootObjects() + self.assertTrue(rootObjects) + self.assertTrue(type(rootObjects[0]), ChildClass) + + +if __name__ == '__main__': + unittest.main() diff --git a/sources/pyside6/tests/QtQml/qmlregistertype_test.qml b/sources/pyside6/tests/QtQml/qmlregistertype_test.qml new file mode 100644 index 000000000..108bb84b1 --- /dev/null +++ b/sources/pyside6/tests/QtQml/qmlregistertype_test.qml @@ -0,0 +1,7 @@ +// Copyright (C) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +import test + +ChildClass { +} diff --git a/sources/shiboken6/ApiExtractor/abstractmetabuilder.cpp b/sources/shiboken6/ApiExtractor/abstractmetabuilder.cpp index 1acfe1375..29566a272 100644 --- a/sources/shiboken6/ApiExtractor/abstractmetabuilder.cpp +++ b/sources/shiboken6/ApiExtractor/abstractmetabuilder.cpp @@ -1451,12 +1451,12 @@ void AbstractMetaBuilderPrivate::traverseFunctions(const ScopeModelItem& scopeIt } } - if (metaFunction->functionType() == AbstractMetaFunction::ConstructorFunction) { - if (metaFunction->isPrivate()) - metaClass->setHasPrivateConstructor(true); - else - metaClass->setHasNonPrivateConstructor(true); + if (metaFunction->functionType() == AbstractMetaFunction::ConstructorFunction + && metaFunction->isPrivate()) { + metaClass->setHasPrivateConstructor(true); } + if (metaFunction->isConstructor() && !metaFunction->isPrivate()) // Including Copy CT + metaClass->setHasNonPrivateConstructor(true); if (!metaFunction->isDestructor() && !(metaFunction->isPrivate() && metaFunction->functionType() == AbstractMetaFunction::ConstructorFunction)) { @@ -1574,7 +1574,8 @@ bool AbstractMetaBuilderPrivate::setupInheritance(const AbstractMetaClassPtr &me &info, &baseContainerType); if (templ) { setupInheritance(templ); - inheritTemplate(metaClass, templ, info); + if (!inheritTemplate(metaClass, templ, info)) + return false; metaClass->typeEntry()->setBaseContainerType(templ->typeEntry()); return true; } @@ -3143,54 +3144,81 @@ AbstractMetaClassPtr return result; } + +static std::optional<AbstractMetaType> + inheritTemplateParameter(const AbstractMetaClassPtr &subclass, + const AbstractMetaClassCPtr &templateClass, + const TypeInfo &info, QString *errorMessage) +{ + QString typeName = info.qualifiedName().join("::"_L1); + TypeDatabase *typeDb = TypeDatabase::instance(); + TypeEntryPtr t; + // Check for a non-type template integer parameter, that is, for a base + // "template <int R, int C> Matrix<R, C>" and subclass + // "typedef Matrix<2,3> Matrix2x3;". If so, create dummy entries of + // EnumValueTypeEntry for the integer values encountered on the fly. + if (isNumber(typeName)) { + t = typeDb->findType(typeName); + if (!t) { + auto parent = typeSystemTypeEntry(subclass->typeEntry()); + t = TypeDatabase::instance()->addConstantValueTypeEntry(typeName, parent); + } + } else { + QStringList possibleNames; + possibleNames << subclass->qualifiedCppName() + "::"_L1 + typeName; + possibleNames << templateClass->qualifiedCppName() + "::"_L1 + typeName; + if (subclass->enclosingClass()) + possibleNames << subclass->enclosingClass()->qualifiedCppName() + "::"_L1 + typeName; + possibleNames << typeName; + + for (const QString &possibleName : std::as_const(possibleNames)) { + t = typeDb->findType(possibleName); + if (t) + break; + } + } + + if (!t) { + *errorMessage = msgIgnoringTemplateParameter(typeName, + "The corresponding type was not found in the typesystem."); + return std::nullopt; + } + + if (t->isContainer()) { + *errorMessage = msgIgnoringTemplateParameter(typeName, + "Template inheritance from nested containers is not supported"); + return std::nullopt; + } + AbstractMetaType result(t); + result.setConstant(info.isConstant()); + result.setReferenceType(info.referenceType()); + result.setIndirectionsV(info.indirectionsV()); + result.decideUsagePattern(); + return result; +} + bool AbstractMetaBuilderPrivate::inheritTemplate(const AbstractMetaClassPtr &subclass, const AbstractMetaClassCPtr &templateClass, const TypeInfo &info) { AbstractMetaTypeList templateTypes; + QString errorMessage; for (const TypeInfo &i : info.instantiations()) { - QString typeName = i.qualifiedName().join(u"::"_s); - TypeDatabase *typeDb = TypeDatabase::instance(); - TypeEntryPtr t; - // Check for a non-type template integer parameter, that is, for a base - // "template <int R, int C> Matrix<R, C>" and subclass - // "typedef Matrix<2,3> Matrix2x3;". If so, create dummy entries of - // EnumValueTypeEntry for the integer values encountered on the fly. - if (isNumber(typeName)) { - t = typeDb->findType(typeName); - if (!t) { - auto parent = typeSystemTypeEntry(subclass->typeEntry()); - t = TypeDatabase::instance()->addConstantValueTypeEntry(typeName, parent); - } + const auto typeO = inheritTemplateParameter(subclass, templateClass, i, &errorMessage); + if (typeO.has_value()) { + templateTypes.append(typeO.value()); } else { - QStringList possibleNames; - possibleNames << subclass->qualifiedCppName() + u"::"_s + typeName; - possibleNames << templateClass->qualifiedCppName() + u"::"_s + typeName; - if (subclass->enclosingClass()) - possibleNames << subclass->enclosingClass()->qualifiedCppName() + u"::"_s + typeName; - possibleNames << typeName; - - for (const QString &possibleName : std::as_const(possibleNames)) { - t = typeDb->findType(possibleName); - if (t) - break; - } - } - - if (t) { - AbstractMetaType temporaryType(t); - temporaryType.setConstant(i.isConstant()); - temporaryType.setReferenceType(i.referenceType()); - temporaryType.setIndirectionsV(i.indirectionsV()); - temporaryType.decideUsagePattern(); - templateTypes << temporaryType; - } else { - qCWarning(lcShiboken).noquote().nospace() - << "Ignoring template parameter " << typeName << " from " - << info.toString() << ". The corresponding type was not found in the typesystem."; + errorMessage = msgInheritTemplateIssue(subclass, info, errorMessage); + qCWarning(lcShiboken, "%s", qPrintable(errorMessage)); } } + if (templateTypes.isEmpty()) { + errorMessage = msgInheritTemplateIssue(subclass, info, + "No template parameters could be inherited"_L1); + qCWarning(lcShiboken, "%s", qPrintable(errorMessage)); + return false; + } return inheritTemplate(subclass, templateClass, templateTypes); } @@ -3540,16 +3568,22 @@ static QList<std::shared_ptr<MetaClass> > if (!result.isValid() && graph.nodeCount()) { QTemporaryFile tempFile(QDir::tempPath() + u"/cyclic_depXXXXXX.dot"_s); tempFile.setAutoRemove(false); - tempFile.open(); - graph.dumpDot(tempFile.fileName(), - [] (const AbstractMetaClassCPtr &c) { return c->name(); }); + const bool ok = tempFile.open(); + if (ok) { + graph.dumpDot(tempFile.fileName(), + [] (const AbstractMetaClassCPtr &c) { return c->name(); }); + } QString message; QTextStream str(&message); str << "Cyclic dependency of classes found:"; for (const auto &c : result.cyclic) str << ' ' << c->name(); - str << ". Graph can be found at \"" << QDir::toNativeSeparators(tempFile.fileName()) << '"'; + str << '.'; + if (ok) { + str << " Graph can be found at \"" + << QDir::toNativeSeparators(tempFile.fileName()) << '"'; + } qCWarning(lcShiboken, "%s", qPrintable(message)); } diff --git a/sources/shiboken6/ApiExtractor/abstractmetafunction.cpp b/sources/shiboken6/ApiExtractor/abstractmetafunction.cpp index f8faba103..11a02f154 100644 --- a/sources/shiboken6/ApiExtractor/abstractmetafunction.cpp +++ b/sources/shiboken6/ApiExtractor/abstractmetafunction.cpp @@ -592,6 +592,11 @@ bool AbstractMetaFunction::isUserAdded() const return d->m_addedFunction && !d->m_addedFunction->isDeclaration(); } +bool AbstractMetaFunction::isUserAddedPythonOverride() const +{ + return d->m_addedFunction && d->m_addedFunction->isPythonOverride(); +} + bool AbstractMetaFunction::isUserDeclared() const { return d->m_addedFunction && d->m_addedFunction->isDeclaration(); diff --git a/sources/shiboken6/ApiExtractor/abstractmetafunction.h b/sources/shiboken6/ApiExtractor/abstractmetafunction.h index bdf5127d1..e252e439d 100644 --- a/sources/shiboken6/ApiExtractor/abstractmetafunction.h +++ b/sources/shiboken6/ApiExtractor/abstractmetafunction.h @@ -300,6 +300,7 @@ public: /// Returns true if the AbstractMetaFunction was added by the user via the type system description. bool isUserAdded() const; + bool isUserAddedPythonOverride() const; /// Returns true if the AbstractMetaFunction was declared by the user via /// the type system description. bool isUserDeclared() const; diff --git a/sources/shiboken6/ApiExtractor/abstractmetalang.cpp b/sources/shiboken6/ApiExtractor/abstractmetalang.cpp index 7cc036cbc..fb49cc9d0 100644 --- a/sources/shiboken6/ApiExtractor/abstractmetalang.cpp +++ b/sources/shiboken6/ApiExtractor/abstractmetalang.cpp @@ -102,6 +102,7 @@ public: AbstractMetaClassCPtr m_templateBaseClass; AbstractMetaFunctionCList m_functions; + AbstractMetaFunctionCList m_userAddedPythonOverrides; AbstractMetaFieldList m_fields; AbstractMetaEnumList m_enums; QList<QPropertySpec> m_propertySpecs; @@ -323,6 +324,11 @@ const AbstractMetaFunctionCList &AbstractMetaClass::functions() const return d->m_functions; } +const AbstractMetaFunctionCList &AbstractMetaClass::userAddedPythonOverrides() const +{ + return d->m_userAddedPythonOverrides; +} + void AbstractMetaClassPrivate::sortFunctions() { std::sort(m_functions.begin(), m_functions.end(), function_sorter); @@ -390,7 +396,13 @@ void AbstractMetaClass::addFunction(const AbstractMetaClassPtr &klass, // to function properly. Such as function modifications nonConstF->setImplementingClass(klass); - klass->d->addFunction(function); + if (function->isUserAddedPythonOverride()) { + nonConstF->setConstant(false); + nonConstF->setCppAttribute(FunctionAttribute::Static); + klass->d->m_userAddedPythonOverrides.append(function); + } else { + klass->d->addFunction(function); + } } bool AbstractMetaClass::hasSignal(const AbstractMetaFunction *other) const @@ -1452,6 +1464,12 @@ void AbstractMetaClass::fixFunctions(const AbstractMetaClassPtr &klass) } for (const auto &superClassC : d->m_baseClasses) { + for (const auto &pof : superClassC->userAddedPythonOverrides()) { + auto *clonedPof = pof->copy(); + clonedPof->setOwnerClass(klass); + d->m_userAddedPythonOverrides.append(AbstractMetaFunctionCPtr{clonedPof}); + } + auto superClass = std::const_pointer_cast<AbstractMetaClass>(superClassC); AbstractMetaClass::fixFunctions(superClass); // Since we always traverse the complete hierarchy we are only diff --git a/sources/shiboken6/ApiExtractor/abstractmetalang.h b/sources/shiboken6/ApiExtractor/abstractmetalang.h index f7ae7b69f..3dc876690 100644 --- a/sources/shiboken6/ApiExtractor/abstractmetalang.h +++ b/sources/shiboken6/ApiExtractor/abstractmetalang.h @@ -66,6 +66,7 @@ public: ~AbstractMetaClass(); const AbstractMetaFunctionCList &functions() const; + const AbstractMetaFunctionCList &userAddedPythonOverrides() const; void setFunctions(const AbstractMetaFunctionCList &functions); static void addFunction(const AbstractMetaClassPtr &klass, const AbstractMetaFunctionCPtr &function); diff --git a/sources/shiboken6/ApiExtractor/abstractmetatype.cpp b/sources/shiboken6/ApiExtractor/abstractmetatype.cpp index dcfc74bbb..3ec07509d 100644 --- a/sources/shiboken6/ApiExtractor/abstractmetatype.cpp +++ b/sources/shiboken6/ApiExtractor/abstractmetatype.cpp @@ -543,6 +543,7 @@ void AbstractMetaType::decideUsagePattern() pattern = ObjectPattern; } setTypeUsagePattern(pattern); + Q_ASSERT(pattern != ContainerPattern || !d->m_instantiations.isEmpty()); } bool AbstractMetaTypeData::hasTemplateChildren() const diff --git a/sources/shiboken6/ApiExtractor/addedfunction.h b/sources/shiboken6/ApiExtractor/addedfunction.h index 06986a47b..b8d189b7a 100644 --- a/sources/shiboken6/ApiExtractor/addedfunction.h +++ b/sources/shiboken6/ApiExtractor/addedfunction.h @@ -79,6 +79,9 @@ struct AddedFunction bool isDeclaration() const { return m_isDeclaration; } // <declare-function> void setDeclaration(bool value) { m_isDeclaration = value; } + bool isPythonOverride() const { return m_isPythonOverride; } + void setPythonOverride(bool o) { m_isPythonOverride = o; } + const FunctionModificationList &modifications() const { return m_modifications; } FunctionModificationList &modifications() { return m_modifications; } @@ -101,6 +104,7 @@ private: bool m_isClassMethod = false; bool m_isStatic = false; bool m_isDeclaration = false; + bool m_isPythonOverride = false; }; QDebug operator<<(QDebug d, const AddedFunction::Argument &a); diff --git a/sources/shiboken6/ApiExtractor/apiextractor.cpp b/sources/shiboken6/ApiExtractor/apiextractor.cpp index ea45f22ba..83ee4437e 100644 --- a/sources/shiboken6/ApiExtractor/apiextractor.cpp +++ b/sources/shiboken6/ApiExtractor/apiextractor.cpp @@ -669,6 +669,8 @@ ApiExtractorPrivate::collectInstantiatedContainersAndSmartPointers(Instantiation return; for (const auto &func : metaClass->functions()) collectInstantiatedContainersAndSmartPointers(context, func); + for (const auto &func : metaClass->userAddedPythonOverrides()) + collectInstantiatedContainersAndSmartPointers(context, func); for (const AbstractMetaField &field : metaClass->fields()) addInstantiatedContainersAndSmartPointers(context, field.type(), field.name()); diff --git a/sources/shiboken6/ApiExtractor/messages.cpp b/sources/shiboken6/ApiExtractor/messages.cpp index f9f46f520..b1f0b240e 100644 --- a/sources/shiboken6/ApiExtractor/messages.cpp +++ b/sources/shiboken6/ApiExtractor/messages.cpp @@ -481,6 +481,21 @@ QString msgCannotFindTypeEntryForSmartPointer(const QString &t, const QString &s + u"\" for instantiation of \""_s +smartPointerType + u"\"."_s; } +QString msgInheritTemplateIssue(const AbstractMetaClassPtr &subclass, + const TypeInfo &info, + const QString &what) +{ + return "While inheriting template "_L1 + subclass->name() + + " from "_L1 + info.toString() + ": "_L1 + what; +} + +QString msgIgnoringTemplateParameter(const QString &typeName, + const char *why) +{ + return "Ignoring template parameter "_L1 + typeName + + ": "_L1 + QLatin1StringView(why); +} + QString msgInvalidSmartPointerType(const TypeInfo &i) { return u"Invalid smart pointer type \""_s +i.toString() + u"\"."_s; diff --git a/sources/shiboken6/ApiExtractor/messages.h b/sources/shiboken6/ApiExtractor/messages.h index 2899cbdfa..e3f582b49 100644 --- a/sources/shiboken6/ApiExtractor/messages.h +++ b/sources/shiboken6/ApiExtractor/messages.h @@ -126,6 +126,10 @@ QString msgUnableToTranslateType(const TypeInfo &typeInfo, QString msgCannotFindTypeEntry(const QString &t); QString msgCannotFindTypeEntryForSmartPointer(const QString &t, const QString &smartPointerType); +QString msgInheritTemplateIssue(const AbstractMetaClassPtr &subclass, + const TypeInfo &info, const QString &what); +QString msgIgnoringTemplateParameter(const QString &typeName, + const char *why); QString msgInvalidSmartPointerType(const TypeInfo &i); QString msgCannotFindSmartPointerInstantion(const TypeInfo &i); diff --git a/sources/shiboken6/ApiExtractor/tests/testconversionruletag.cpp b/sources/shiboken6/ApiExtractor/tests/testconversionruletag.cpp index 731ce161c..b5efd92a6 100644 --- a/sources/shiboken6/ApiExtractor/tests/testconversionruletag.cpp +++ b/sources/shiboken6/ApiExtractor/tests/testconversionruletag.cpp @@ -23,7 +23,7 @@ void TestConversionRuleTag::testConversionRuleTagWithFile() // temp file used later constexpr auto conversionData = "Hi! I'm a conversion rule."_L1; QTemporaryFile file; - file.open(); + QVERIFY(file.open()); QCOMPARE(file.write(conversionData.constData()), conversionData.size()); file.close(); diff --git a/sources/shiboken6/ApiExtractor/typesystem_enums.h b/sources/shiboken6/ApiExtractor/typesystem_enums.h index 81304e6c2..9ecbb08a1 100644 --- a/sources/shiboken6/ApiExtractor/typesystem_enums.h +++ b/sources/shiboken6/ApiExtractor/typesystem_enums.h @@ -35,6 +35,7 @@ enum CodeSnipPosition { CodeSnipPositionBeginning, CodeSnipPositionEnd, CodeSnipPositionDeclaration, + CodeSnipPositionPyOverride, CodeSnipPositionAny }; diff --git a/sources/shiboken6/ApiExtractor/typesystem_typedefs.h b/sources/shiboken6/ApiExtractor/typesystem_typedefs.h index 59bb452a3..5a4e12ff2 100644 --- a/sources/shiboken6/ApiExtractor/typesystem_typedefs.h +++ b/sources/shiboken6/ApiExtractor/typesystem_typedefs.h @@ -68,6 +68,7 @@ using TypedefEntryCPtr = std::shared_ptr<const TypedefEntry>; using TypeSystemTypeEntryCPtr = std::shared_ptr<const TypeSystemTypeEntry>; using ValueTypeEntryCPtr = std::shared_ptr<const ValueTypeEntry>; +using ComplexTypeEntryCList = QList<ComplexTypeEntryCPtr>; using ContainerTypeEntryCList = QList<ContainerTypeEntryCPtr>; using NamespaceTypeEntryList = QList<NamespaceTypeEntryPtr>; using PrimitiveTypeEntryCList = QList<PrimitiveTypeEntryCPtr>; diff --git a/sources/shiboken6/ApiExtractor/typesystemparser.cpp b/sources/shiboken6/ApiExtractor/typesystemparser.cpp index 4e666151d..2b686e997 100644 --- a/sources/shiboken6/ApiExtractor/typesystemparser.cpp +++ b/sources/shiboken6/ApiExtractor/typesystemparser.cpp @@ -93,6 +93,7 @@ constexpr auto positionAttribute = "position"_L1; constexpr auto preferredConversionAttribute = "preferred-conversion"_L1; constexpr auto preferredTargetLangTypeAttribute = "preferred-target-lang-type"_L1; constexpr auto pythonEnumTypeAttribute = "python-type"_L1; +constexpr auto pythonOverrideAttribute = "python-override"_L1; constexpr auto cppEnumTypeAttribute = "cpp-type"_L1; constexpr auto qtMetaObjectFunctionsAttribute = "qt-metaobject"_L1; constexpr auto qtMetaTypeAttribute = "qt-register-metatype"_L1; @@ -331,7 +332,8 @@ ENUM_LOOKUP_BEGIN(TypeSystem::CodeSnipPosition, Qt::CaseInsensitive, { {u"beginning", TypeSystem::CodeSnipPositionBeginning}, {u"end", TypeSystem::CodeSnipPositionEnd}, - {u"declaration", TypeSystem::CodeSnipPositionDeclaration} + {u"declaration", TypeSystem::CodeSnipPositionDeclaration}, + {u"override", TypeSystem::CodeSnipPositionPyOverride} }; ENUM_LOOKUP_LINEAR_SEARCH @@ -2600,6 +2602,7 @@ bool TypeSystemParser::parseAddFunction(const ConditionalStreamReader &, QString returnType; bool staticFunction = false; bool classMethod = false; + bool pythonOverride = false; QString access; for (auto i = attributes->size() - 1; i >= 0; --i) { const auto name = attributes->at(i).qualifiedName(); @@ -2615,6 +2618,9 @@ bool TypeSystemParser::parseAddFunction(const ConditionalStreamReader &, classmethodAttribute, false); } else if (name == accessAttribute) { access = attributes->takeAt(i).value().toString(); + } else if (name == pythonOverrideAttribute) { + pythonOverride = convertBoolean(attributes->takeAt(i).value(), + pythonOverrideAttribute, false); } } @@ -2638,6 +2644,7 @@ bool TypeSystemParser::parseAddFunction(const ConditionalStreamReader &, func->setStatic(staticFunction); func->setClassMethod(classMethod); + func->setPythonOverride(pythonOverride); func->setTargetLangPackage(m_defaultPackage); // Create signature for matching modifications diff --git a/sources/shiboken6/doc/shibokenmodule.rst b/sources/shiboken6/doc/shibokenmodule.rst index b31efbcd1..3bc4fa6ba 100644 --- a/sources/shiboken6/doc/shibokenmodule.rst +++ b/sources/shiboken6/doc/shibokenmodule.rst @@ -116,6 +116,20 @@ To import the module: This method should be used **only** for debug purposes by developers. + .. function:: dumpTypeGraph(file_name) + + Dumps the inheritance graph of the types existing in libshiboken + to ``.dot`` file for use with `Graphviz <https://graphviz.org/>`_. + +.. function:: dumpWrapperMap() + + Dumps the map of wrappers existing in libshiboken to standard error. + +.. function:: dumpConverters() + + Dumps the map of named converters existing in libshiboken to standard + error. + .. py:class:: VoidPtr(address, size = -1, writeable = 0) :param address: (PyBuffer, SbkObject, int, VoidPtr) diff --git a/sources/shiboken6/doc/typediscovery.rst b/sources/shiboken6/doc/typediscovery.rst new file mode 100644 index 000000000..76d3adf7b --- /dev/null +++ b/sources/shiboken6/doc/typediscovery.rst @@ -0,0 +1,145 @@ +.. _typediscovery: + +************** +Type Discovery +************** + +When converting objects which are part of a class hierarchy from a pointer to a +base class, it is expected to get the Python type of the actual, most derived +type, as opposed to C++ which requires a cast for this: + +.. code-block:: python + + def event(self, event): + if event.type() == QEvent.Type.MousePress: + self.do_things(event.position()) + ... + + +.. code-block:: c++ + + bool event(QEvent *event) override + { + if (event->type() == QEvent::MousePress) { + auto *mouseEvent = static_cast<QMouseEvent *>(event); + doThings(mouseEvent->position()); + ... + } + +The process of determining the type of the event is called `type discovery`. + +Shiboken generates code to automatically detect the type. First, it tries to +find a converter for the name obtained by ``typeid(*pointer).name()``. This +should normally work as this name is registered by the binding. If that fails, +it starts walking a type inheritance graph built up in libshiboken to find the +most derived class by using a cast function (``dynamic_cast<>`` by default) to +check. + +For normal class hierarchies with virtual destructors, no special handling +is required since ``typeid()`` usually detects the proper class name. + +Multiple inheritance +==================== + +In case of multiple inheritance in C++, the conversion to the derived class is +not done in case it is not a single-line direct inheritance. For example, in +Qt, the class ``QWidget`` inherits both ``QObject`` (base of the ``QObject`` +hierarchy) and ``QPaintDevice``. + +When calling a function returning a ``QPaintDevice *``, for example +``QPainter.device()``, a Python type representing ``QPaintDevice`` is returned +instead of the underlying widget type. This restriction exists because the +underlying pointer in C++ is a pointer to a ``QPaintDevice *`` and differs from +the pointer to the ``QWidget``. + +Hierarchies of classes with non-virtual destructors +=================================================== + +There are some hierarchies of value-ish C++ classes that do not have virtual +destructors. This makes type discovery based on ``typeid()`` and +``dynamic_cast<>`` impossible. + +Examples in Qt are the ``QStyleOption``-derived or the ``QGradient`` +-derived classes. + +For such classes, some attributes need to be specified on the type entries: + +Primarily, a :ref:`polymorphic-id-expression` attribute +must be specified to be used as a check replacing ``dynamic_cast<>``. + +In addition, a :ref:`polymorphic-name-function` attribute can be specified. +This replaces the type name guess obtained by ``typeid()`` and is mainly a hint +to speed things up by skipping the checks for each type in the inheritance +graph. + +A :ref:`polymorphic-base` attribute identifies the base class of a hierarchy. +It should be given in case the base class inherits from another class to +prevent the logic from going below the base class. + +Using type discovery attributes for class hierarchies with virtual destructors +============================================================================== + +It is possible to use :ref:`polymorphic-id-expression` and +:ref:`polymorphic-name-function` for normal class hierarchies with virtual +destructors as well since they basically replace ``typeid()`` and +``dynamic_cast<>``. This makes sense if expressions can be specified that are +faster than the checks on virtual tables. + +Specifying :ref:`polymorphic-base` can also make sense for generating special +cast functions in case of multiple inheritance. For example, in Qt, +``QWindow``, ``QLayout``, ``QWidget`` are base classes of hierarchies. Since +they all inherit from ``QObject``, indicating the base classes prevents +the logic from using ``QObject`` as a base class. + +.. _typediscovery-attributes: + +Type discovery attributes reference +=================================== + +The following attributes related to type discovery may be be specified on the +:ref:`object-type` or :ref:`value-type` elements: + +.. _polymorphic-id-expression: + +polymorphic-id-expression ++++++++++++++++++++++++++ + +The **polymorphic-id-expression** attribute specifies an expression checking +whether a base class pointer is of the matching type. For example, in a +``virtual eventHandler(BaseEvent *e)`` function, this is used to construct a +Python wrapper matching the derived class (for example, a ``MouseEvent`` or +similar). The attribute value may contain placeholders: + +%1 + Fully qualified class name + +%B + Fully qualified name of the base class (found by base class + search or as indicated by **polymorphic-base**). + +To check for a class inheriting ``BaseEvent``, specify: + +.. code-block:: xml + + <object-type name="MouseEvent" + polymorphic-id-expression="%B->type() == BaseEvent::MouseEvent"/> + +.. _polymorphic-name-function: + +polymorphic-name-function ++++++++++++++++++++++++++ + +The **polymorphic-name-function** attribute specifies the name of a function +returning the type name of a derived class on the base class type entry. +Normally, ``typeid(ptr).name()`` is used for this. + +The function is expected to return ``const char *``. + +.. _polymorphic-base: + +polymorphic-base +++++++++++++++++ + +The boolean **polymorphic-base** attribute indicates whether the class is the +base class of a class hierarchy. It is used for the *%B* placeholder in +**polymorphic-id-expression** and for cast operations in multiple inheritance. diff --git a/sources/shiboken6/doc/typesystem.rst b/sources/shiboken6/doc/typesystem.rst index e1e4fdda2..26f929801 100644 --- a/sources/shiboken6/doc/typesystem.rst +++ b/sources/shiboken6/doc/typesystem.rst @@ -65,3 +65,4 @@ Extra options and Python caveats typesystem_solving_compilation.rst typesystem_specialfunctions.rst + typediscovery.rst diff --git a/sources/shiboken6/doc/typesystem_codeinjection.rst b/sources/shiboken6/doc/typesystem_codeinjection.rst index d0a5a0390..03d5f4b16 100644 --- a/sources/shiboken6/doc/typesystem_codeinjection.rst +++ b/sources/shiboken6/doc/typesystem_codeinjection.rst @@ -74,6 +74,9 @@ function. | |shell |declaration|Used only for virtual functions. This code is injected at the | | | | |top. | | | +-----------+--------------------------------------------------------------+ +| | |override |Used only for virtual functions. The code is injected before | +| | | |the code calling the Python override. | +| | +-----------+--------------------------------------------------------------+ | | |beginning |Used only for virtual functions. The code is injected when the| | | | |function does not has a Python implementation, then the code | | | | |is inserted before c++ call | diff --git a/sources/shiboken6/doc/typesystem_converters.rst b/sources/shiboken6/doc/typesystem_converters.rst index 7bdabc49c..ab6fba930 100644 --- a/sources/shiboken6/doc/typesystem_converters.rst +++ b/sources/shiboken6/doc/typesystem_converters.rst @@ -233,61 +233,3 @@ Variables & Functions **%CHECKTYPE[CPPTYPE]** Replaced by a |project| type checking function for a Python variable. The C++ type is indicated by ``CPPTYPE``. - - -.. _oldconverters: - -Converting The Old Converters -============================= - -If you use |project| for your bindings, and has defined some type conversions -using the ``Shiboken::Converter`` template, then you must update your converters -to the new scheme. - -Previously your conversion rules were declared in one line, like this: - - -.. code-block:: xml - - <primitive-type name="Complex" target-lang-api-name="PyComplex"> - <include file-name="complex.h" location="global"/> - <conversion-rule file="complex_conversions.h"/> - </primitive-type> - - -And implemented in a separate C++ file, like this: - - -.. code-block:: c++ - - namespace Shiboken { - template<> struct Converter<Complex> - { - static inline bool checkType(PyObject* pyObj) { - return PyComplex_Check(pyObj); - } - static inline bool isConvertible(PyObject* pyObj) { - return PyComplex_Check(pyObj); - } - static inline PyObject* toPython(void* cppobj) { - return toPython(*reinterpret_cast<Complex*>(cppobj)); - } - static inline PyObject* toPython(const Complex& cpx) { - return PyComplex_FromDoubles(cpx.real(), cpx.imag()); - } - static inline Complex toCpp(PyObject* pyobj) { - double real = PyComplex_RealAsDouble(pyobj); - double imag = PyComplex_ImagAsDouble(pyobj); - return Complex(real, imag); - } - }; - } - - -In this case, the parts of the implementation that will be used in the new -conversion-rule are the ones in the two last method -``static inline PyObject* toPython(const Complex& cpx)`` and -``static inline Complex toCpp(PyObject* pyobj)``. The ``isConvertible`` method -is gone, and the ``checkType`` is now an attribute of the :ref:`add-conversion <add-conversion>` -tag. Refer back to the first example in this page and you will be able to -correlate the above template with the new scheme of conversion rule definition. diff --git a/sources/shiboken6/doc/typesystem_manipulating_objects.rst b/sources/shiboken6/doc/typesystem_manipulating_objects.rst index 89e3879b4..e024cdf00 100644 --- a/sources/shiboken6/doc/typesystem_manipulating_objects.rst +++ b/sources/shiboken6/doc/typesystem_manipulating_objects.rst @@ -282,6 +282,7 @@ logic. This can be done using the :ref:`inject-code` node. access="public | protected" overload-number="number" static="yes | no" classmethod="yes | no" + python-override ="yes | no" since="..."/> </object-type> @@ -320,6 +321,10 @@ within the `signature` field See :ref:`sequence-protocol` for adding the respective functions. +The *optional* attribute ``python-override`` indicates a special type +of added function, a python-override that will be generated into +the native wrapper (see :ref:`modifying-virtual-functions`). + .. _declare-function: declare-function @@ -500,3 +505,52 @@ configuration (see also option :ref:`drop-type-entries`) intended for building several configurations from one generated source tree, but still requires listing the correct source files in the ``CMakeLists.txt`` file. + +.. _modifying-virtual-functions: + +Modifying virtual functions +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Some C++ virtual functions are unsuitable for Python bindings: + +.. code-block:: c + + virtual void getInt(int *result) const; + +In that case, you would modify it to return the integer instead (or a tuple +in case of several out-parameters): + +.. code-block:: c + + virtual int getInt() const; + +For the binding itself, use the common argument modifications (removing +arguments, modifying return types with injected code snippets) to modify the +signature. + +To make it possible to reimplement the function in Python with the modified +signature, add a ``python-override`` function with that signature, using an +arbitrary name for disambiguation: + +.. code-block:: xml + + <add-function signature="getIntPyOverride()" + return-type="int" python-override="true"/> + +This causes a static function performing the call into Python for the override +to be generated into the native wrapper. + +In the existing virtual function, inject a code snippet at the ``shell`` / +``override`` position which calls the newly added function. The first 2 +arguments are the `Global interpreter lock handle` (``Shiboken::GilState``) and +the Python method determined by the override check (``PyObject *``). The +snippet then converts the arguments and return values and returns after that: + +.. code-block:: xml + + <modify-function signature="getInt(int*)const"> + <inject-code class="shell" position="override"> + *result = getIntPyOverride(gil, pyOverride.object()); + return; + </inject-code> + </modify-function> diff --git a/sources/shiboken6/doc/typesystem_specifying_types.rst b/sources/shiboken6/doc/typesystem_specifying_types.rst index 66e68ae2b..f65b79bb4 100644 --- a/sources/shiboken6/doc/typesystem_specifying_types.rst +++ b/sources/shiboken6/doc/typesystem_specifying_types.rst @@ -536,37 +536,8 @@ type system has this attribute set, the heuristics will be applied to all classes. In shiboken 7, it will be mandatory to set the attribute. -The *optional* **polymorphic-id-expression** attribute specifies an -expression checking whether a base class pointer is of the matching -type. For example, in a ``virtual eventHandler(BaseEvent *e)`` -function, this is used to construct a Python wrapper matching -the derived class (for example, a ``MouseEvent`` or similar). -The attribute value may contain placeholders: - -%1 - Fully qualified class name - -%B - Fully qualified name of the base class (found by base class - search or as indicated by **polymorphic-base**). - -To check for a class inheriting ``BaseEvent``, specify: - -.. code-block:: xml - - <object-type name="MouseEvent" - polymorphic-id-expression="%B->type() == BaseEvent::MouseEvent"/> - -The *optional* **polymorphic-name-function** specifies the name of a -function returning the type name of a derived class on the base class -type entry. Normally, ``typeid(ptr).name()`` is used for this. -However, this fails if the type hierarchy does not have virtual functions. -In this case, a function is required which typically decides depending -on some type enumeration. - -The *optional* **polymorphic-base** attribute indicates -whether the class is the base class of a class hierarchy -(used for the *%B* placeholder in **polymorphic-id-expression**). +For the *optional* **polymorphic-id-expression**, **polymorphic-name-function** +and **polymorphic-base** attributes, see :ref:`typediscovery-attributes`. interface-type ^^^^^^^^^^^^^^ diff --git a/sources/shiboken6/generator/qtdoc/qtdocgenerator.cpp b/sources/shiboken6/generator/qtdoc/qtdocgenerator.cpp index 1103cef29..2797ff254 100644 --- a/sources/shiboken6/generator/qtdoc/qtdocgenerator.cpp +++ b/sources/shiboken6/generator/qtdoc/qtdocgenerator.cpp @@ -147,8 +147,13 @@ static bool shouldSkip(const AbstractMetaFunctionCPtr &func) static bool functionSort(const AbstractMetaFunctionCPtr &func1, const AbstractMetaFunctionCPtr &func2) { const bool ctor1 = func1->isConstructor(); - const bool ctor2 = func2->isConstructor(); - return ctor1 != ctor2 ? ctor1 : func1->name() < func2->name(); + if (ctor1 != func2->isConstructor()) + return ctor1; + const QString &name1 = func1->name(); + const QString &name2 = func2->name(); + if (name1 != name2) + return name1 < name2; + return func1->arguments().size() < func2->arguments().size(); } static inline QVersionNumber versionOf(const TypeEntryCPtr &te) @@ -1421,7 +1426,7 @@ GeneratorDocumentation std::remove_copy_if(allFunctions.cbegin(), allFunctions.cend(), std::back_inserter(result.allFunctions), shouldSkip); - std::sort(result.allFunctions.begin(), result.allFunctions.end(), functionSort); + std::stable_sort(result.allFunctions.begin(), result.allFunctions.end(), functionSort); for (const auto &func : std::as_const(result.allFunctions)) { if (func->isStatic()) diff --git a/sources/shiboken6/generator/shiboken/cppgenerator.cpp b/sources/shiboken6/generator/shiboken/cppgenerator.cpp index 05afde69f..48e7f4fe5 100644 --- a/sources/shiboken6/generator/shiboken/cppgenerator.cpp +++ b/sources/shiboken6/generator/shiboken/cppgenerator.cpp @@ -60,6 +60,9 @@ static const char shibokenErrorsOccurred[] = "Shiboken::Errors::occurred() != nu static constexpr auto virtualMethodStaticReturnVar = "result"_L1; +static constexpr auto sbkObjectTypeF = "SbkObject_TypeF()"_L1; +static const char initInheritanceFunction[] = "initInheritance"; + static QString mangleName(QString name) { if (name == u"None" || name == u"False" || name == u"True" || name == u"from") @@ -624,6 +627,9 @@ void CppGenerator::generateClass(TextStream &s, const GeneratorContext &classCon writeDestructorNative(s, classContext); } + for (const auto &f : metaClass->userAddedPythonOverrides()) + writeUserAddedPythonOverride(s, f); + StringStream smd(TextStream::Language::Cpp); StringStream md(TextStream::Language::Cpp); StringStream signatureStream(TextStream::Language::Cpp); @@ -1129,6 +1135,29 @@ static inline void writeVirtualMethodStaticReturnVar(TextStream &s, const Abstra << virtualMethodStaticReturnVar << ";\n"; } +static void writeFuncNameVar(TextStream &s, const AbstractMetaFunctionCPtr &func, + const QString &funcName) +{ + // PYSIDE-1019: Add info about properties + int propFlag = 0; + if (func->isPropertyReader()) + propFlag |= 1; + if (func->isPropertyWriter()) + propFlag |= 2; + if (propFlag && func->isStatic()) + propFlag |= 4; + QString propStr; + if (propFlag != 90) + propStr = QString::number(propFlag) + u':'; + + if (propFlag != 0) + s << "// This method belongs to a property.\n"; + s << "static const char *funcName = \""; + if (propFlag != 0) + s << propFlag << ':'; + s << funcName << "\";\n"; +} + void CppGenerator::writeVirtualMethodNative(TextStream &s, const AbstractMetaFunctionCPtr &func, int cacheIndex) const @@ -1191,23 +1220,9 @@ void CppGenerator::writeVirtualMethodNative(TextStream &s, s << "if (" << shibokenErrorsOccurred << ")\n" << indent << returnStatement.statement << '\n' << outdent; - // PYSIDE-1019: Add info about properties - int propFlag = 0; - if (func->isPropertyReader()) - propFlag |= 1; - if (func->isPropertyWriter()) - propFlag |= 2; - if (propFlag && func->isStatic()) - propFlag |= 4; - QString propStr; - if (propFlag) - propStr = QString::number(propFlag) + u':'; - s << "static PyObject *nameCache[2] = {};\n"; - if (propFlag) - s << "// This method belongs to a property.\n"; - s << "static const char *funcName = \"" << propStr << funcName << "\";\n" - << "Shiboken::AutoDecRef " << PYTHON_OVERRIDE_VAR + writeFuncNameVar(s, func, funcName); + s << "Shiboken::AutoDecRef " << PYTHON_OVERRIDE_VAR << "(Shiboken::BindingManager::instance().getOverride(this, nameCache, funcName));\n" << "if (" << PYTHON_OVERRIDE_VAR << ".isNull()) {\n" << indent; if (useOverrideCaching(func->ownerClass())) @@ -1216,6 +1231,11 @@ void CppGenerator::writeVirtualMethodNative(TextStream &s, returnStatement.statement, true); s << outdent << "}\n\n"; //WS + if (!snips.isEmpty()) { + writeCodeSnips(s, snips, TypeSystem::CodeSnipPositionPyOverride, + TypeSystem::ShellCode, func, false, lastArg); + } + writeVirtualMethodPythonOverride(s, func, snips, returnStatement); } @@ -1425,6 +1445,28 @@ void CppGenerator::writeVirtualMethodPythonOverride(TextStream &s, s << outdent << "}\n\n"; } +void CppGenerator::writeUserAddedPythonOverride(TextStream &s, + const AbstractMetaFunctionCPtr &func) const +{ + TypeEntryCPtr retType = func->type().typeEntry(); + const QString funcName = func->isOperatorOverload() + ? pythonOperatorFunctionName(func) : func->definitionNames().constFirst(); + + const CodeSnipList snips = func->hasInjectedCode() + ? func->injectedCodeSnips() : CodeSnipList(); + + QString prefix = wrapperName(func->ownerClass()) + u"::"_s; + s << '\n' << functionSignature(func, prefix, QString(), Generator::SkipDefaultValues | + Generator::OriginalTypeDescription) + << "\n{\n" << indent << sbkUnusedVariableCast("gil"); + + writeFuncNameVar(s, func, funcName); + + const auto returnStatement = virtualMethodReturn(api(), func, + func->modifications()); + writeVirtualMethodPythonOverride(s, func, snips, returnStatement); +} + void CppGenerator::writeMetaObjectMethod(TextStream &s, const GeneratorContext &classContext) const { @@ -1548,6 +1590,33 @@ void CppGenerator::writeEnumConverterFunctions(TextStream &s, const AbstractMeta s << '\n'; } +static void writePointerToPythonConverter(TextStream &c, + const AbstractMetaClassCPtr &metaClass, + const QString &typeName, + const QString &cpythonType) +{ + c << "auto *pyOut = reinterpret_cast<PyObject *>(Shiboken::BindingManager::instance().retrieveWrapper(cppIn));\n" + << "if (pyOut) {\n" << indent + << "Py_INCREF(pyOut);\nreturn pyOut;\n" << outdent + << "}\n"; + + const QString nameFunc = metaClass->typeEntry()->polymorphicNameFunction(); + if (nameFunc.isEmpty() && !metaClass->hasVirtualDestructor()) { + c << "return Shiboken::Object::newObjectWithHeuristics(" + << cpythonType << ", const_cast<void *>(cppIn), false);\n"; + return; + } + + c << "auto *tCppIn = reinterpret_cast<const " << typeName << R"( *>(cppIn); +const char *typeName = )"; + if (nameFunc.isEmpty()) + c << "typeid(*tCppIn).name();\n"; + else + c << nameFunc << "(tCppIn);\n"; + c << "return Shiboken::Object::newObjectForPointer(" + << cpythonType << ", const_cast<void *>(cppIn), false, typeName);\n"; +} + void CppGenerator::writeConverterFunctions(TextStream &s, const AbstractMetaClassCPtr &metaClass, const GeneratorContext &classContext) const { @@ -1593,30 +1662,7 @@ void CppGenerator::writeConverterFunctions(TextStream &s, const AbstractMetaClas c << "return PySide::getWrapperForQObject(reinterpret_cast<" << typeName << " *>(const_cast<void *>(cppIn)), " << cpythonType << ");\n"; } else { - c << "auto *pyOut = reinterpret_cast<PyObject *>(Shiboken::BindingManager::instance().retrieveWrapper(cppIn));\n" - << "if (pyOut) {\n" << indent - << "Py_INCREF(pyOut);\nreturn pyOut;\n" << outdent - << "}\n" - << "bool changedTypeName = false;\n" - << "auto *tCppIn = reinterpret_cast<const " << typeName << R"( *>(cppIn); -const char *typeName = )"; - - const QString nameFunc = metaClass->typeEntry()->polymorphicNameFunction(); - if (nameFunc.isEmpty()) - c << "typeid(*tCppIn).name();\n"; - else - c << nameFunc << "(tCppIn);\n"; - c << R"(auto *sbkType = Shiboken::ObjectType::typeForTypeName(typeName); -if (sbkType != nullptr && Shiboken::ObjectType::hasSpecialCastFunction(sbkType)) { - typeName = Shiboken::typeNameOf(typeid(*tCppIn).name()); - changedTypeName = true; -} -)" - << "PyObject *result = Shiboken::Object::newObject(" << cpythonType - << R"(, const_cast<void *>(cppIn), false, /* exactType */ changedTypeName, typeName); -if (changedTypeName) - delete [] typeName; -return result;)"; + writePointerToPythonConverter(c, metaClass, typeName, cpythonType); } std::swap(targetTypeName, sourceTypeName); writeCppToPythonFunction(s, c.toString(), sourceTypeName, targetTypeName); @@ -4219,6 +4265,12 @@ QString CppGenerator::writeContainerConverterInitialization(TextStream &s, return converter; } +QString CppGenerator::typeInitStruct(const TypeEntryCPtr &te) +{ + return cppApiVariableName(te->targetLangPackage()) + u'[' + + getTypeIndexVariableName(te) + u']'; +} + void CppGenerator::writeExtendedConverterInitialization(TextStream &s, const TypeEntryCPtr &externalType, const AbstractMetaClassCList &conversions) @@ -4226,15 +4278,13 @@ void CppGenerator::writeExtendedConverterInitialization(TextStream &s, s << "// Extended implicit conversions for " << externalType->qualifiedTargetLangName() << ".\n"; for (const auto &sourceClass : conversions) { - const QString converterVar = cppApiVariableName(externalType->targetLangPackage()) + u'[' - + getTypeIndexVariableName(externalType) + u']'; QString sourceTypeName = fixedCppTypeName(sourceClass->typeEntry()); QString targetTypeName = fixedCppTypeName(externalType); QString toCpp = pythonToCppFunctionName(sourceTypeName, targetTypeName); QString isConv = convertibleToCppFunctionName(sourceTypeName, targetTypeName); if (!externalType->isPrimitive()) s << cpythonTypeNameExt(externalType) << ";\n"; - writeAddPythonToCppConversion(s, converterVar, toCpp, isConv); + writeAddPythonToCppConversion(s, typeInitStruct(externalType), toCpp, isConv); } } @@ -4317,7 +4367,6 @@ void CppGenerator::writeClassDefinition(TextStream &s, QString tp_hash; QString tp_call; const QString className = chopType(cpythonTypeName(metaClass)); - QString baseClassName; AbstractMetaFunctionCList ctors; const auto &allCtors = metaClass->queryFunctions(FunctionQueryOption::AnyConstructor); for (const auto &f : allCtors) { @@ -4327,9 +4376,6 @@ void CppGenerator::writeClassDefinition(TextStream &s, } } - if (!metaClass->baseClass()) - baseClassName = u"SbkObject_TypeF()"_s; - bool onlyPrivCtor = !metaClass->hasNonPrivateConstructor(); const bool isQApp = usePySideExtensions() @@ -4663,7 +4709,7 @@ void CppGenerator::writeTpTraverseFunction(TextStream &s, const AbstractMetaClas s << "static int " << baseName << "_traverse(PyObject *self, visitproc visit, void *arg)\n{\n" << indent << "auto traverseProc = " - << pyTypeGetSlot("traverseproc", "SbkObject_TypeF()", "Py_tp_traverse") << ";\n" + << pyTypeGetSlot("traverseproc", sbkObjectTypeF, "Py_tp_traverse") << ";\n" << "return traverseProc(self, visit, arg);\n" << outdent << "}\n"; } @@ -4673,7 +4719,7 @@ void CppGenerator::writeTpClearFunction(TextStream &s, const AbstractMetaClassCP QString baseName = cpythonBaseName(metaClass); s << "static int " << baseName << "_clear(PyObject *self)\n{\n" << indent << "auto clearProc = " - << pyTypeGetSlot("inquiry", "SbkObject_TypeF()", "Py_tp_clear") << ";\n" + << pyTypeGetSlot("inquiry", sbkObjectTypeF, "Py_tp_clear") << ";\n" << "return clearProc(self);\n" << outdent << "}\n"; } @@ -5076,10 +5122,15 @@ QString CppGenerator::signatureParameter(const AbstractMetaArgument &arg) const const AbstractMetaFunctionCList conversions = api().implicitConversions(metaType); for (const auto &f : conversions) { - if (f->isConstructor() && !f->arguments().isEmpty()) - signatures << f->arguments().constFirst().type().pythonSignature(); - else if (f->isConversionOperator()) + if (f->isConstructor() && !f->arguments().isEmpty()) { + // PYSIDE-2712: modified types from converting constructors are not always correct + // candidates if they are modified by the type system reference + if (!f->arguments().constFirst().isTypeModified()) { + signatures << f->arguments().constFirst().type().pythonSignature(); + } + } else if (f->isConversionOperator()) { signatures << f->ownerClass()->fullName(); + } } const qsizetype size = signatures.size(); @@ -5402,6 +5453,61 @@ QString CppGenerator::destructorClassName(const AbstractMetaClassCPtr &metaClass return metaClass->qualifiedCppName(); } +// Return the base type entries for introduceWrapperType() +static ComplexTypeEntryCList pyBaseTypeEntries(const AbstractMetaClassCPtr &metaClass) +{ + ComplexTypeEntryCList result; + if (metaClass->isNamespace()) { + if (auto extended = metaClass->extendedNamespace()) + result.append(extended->typeEntry()); + return result; + } + + const auto &baseClasses = metaClass->typeSystemBaseClasses(); + for (auto base : baseClasses) { + for (; base != nullptr; base = base->baseClass()) { // Find a type that is not disabled. + const auto ct = base->typeEntry()->codeGeneration(); + if (ct == TypeEntry::GenerateCode || ct == TypeEntry::GenerateForSubclass) + break; + } + result.append(base->typeEntry()); + } + return result; +} + +// Return the base type strings for introduceWrapperType() +QStringList CppGenerator::pyBaseTypes(const AbstractMetaClassCPtr &metaClass) +{ + const auto &baseEntries = pyBaseTypeEntries(metaClass); + QStringList result; + for (const auto &baseEntry : baseEntries) + result.append("reinterpret_cast<PyObject *>("_L1 + cpythonTypeNameExt(baseEntry) + u')'); + if (result.isEmpty()) // no base classes -> SbkObjectType. + result.append(sbkObjectTypeF); + return result; +} + +void CppGenerator::writeInitInheritance(TextStream &s) const +{ + s << "static void " << initInheritanceFunction << "()\n{\n" << indent + << "auto &bm = Shiboken::BindingManager::instance();\n" + << sbkUnusedVariableCast("bm"); + for (const auto &cls : api().classes()){ + auto te = cls->typeEntry(); + if (shouldGenerate(te)) { + const auto &baseEntries = pyBaseTypeEntries(cls); + if (!baseEntries.isEmpty()) { + const QString childTypeInitStruct = typeInitStruct(cls->typeEntry()); + for (const auto &baseEntry : baseEntries) { + s << "bm.addClassInheritance(&" << typeInitStruct(baseEntry) << ",\n" + << Pad(' ', 23) << '&' << childTypeInitStruct << ");\n"; + } + } + } + } + s << outdent << "}\n\n"; +} + void CppGenerator::writeClassRegister(TextStream &s, const AbstractMetaClassCPtr &metaClass, const GeneratorContext &classContext, @@ -5428,18 +5534,15 @@ void CppGenerator::writeClassRegister(TextStream &s, // Multiple inheritance QString pyTypeBasesVariable = chopType(pyTypeName) + u"_Type_bases"_s; - const auto &baseClasses = metaClass->typeSystemBaseClasses(); - if (metaClass->baseClassNames().size() > 1) { - s << "PyObject *" << pyTypeBasesVariable - << " = PyTuple_Pack(" << baseClasses.size() << ',' << '\n' << indent; - for (qsizetype i = 0, size = baseClasses.size(); i < size; ++i) { - if (i) - s << ",\n"; - s << "reinterpret_cast<PyObject *>(" - << cpythonTypeNameExt(baseClasses.at(i)->typeEntry()) << ')'; - } - s << ");\n\n" << outdent; + const QStringList pyBases = pyBaseTypes(metaClass); + s << "Shiboken::AutoDecRef " << pyTypeBasesVariable << "(PyTuple_Pack(" + << pyBases.size() << ",\n" << indent; + for (qsizetype i = 0, size = pyBases.size(); i < size; ++i) { + if (i) + s << ",\n"; + s << pyBases.at(i); } + s << "));\n\n" << outdent; // Create type and insert it in the module or enclosing class. const QString typePtr = u"_"_s + chopType(pyTypeName) @@ -5474,27 +5577,8 @@ void CppGenerator::writeClassRegister(TextStream &s, s << "&Shiboken::callCppDestructor< " << globalScopePrefix(classContext) << dtorClassName << " >,\n"; - // 6:baseType: Find a type that is not disabled. - auto base = metaClass->isNamespace() - ? metaClass->extendedNamespace() : metaClass->baseClass(); - if (!metaClass->isNamespace()) { - for (; base != nullptr; base = base->baseClass()) { - const auto ct = base->typeEntry()->codeGeneration(); - if (ct == TypeEntry::GenerateCode || ct == TypeEntry::GenerateForSubclass) - break; - } - } - if (base) { - s << cpythonTypeNameExt(base->typeEntry()) << ",\n"; - } else { - s << "nullptr,\n"; - } - // 7:baseTypes - if (metaClass->baseClassNames().size() > 1) - s << pyTypeBasesVariable << ',' << '\n'; - else - s << "0,\n"; + s << pyTypeBasesVariable << ".object()," << '\n'; // 8:wrapperflags QByteArrayList wrapperFlags; @@ -5750,12 +5834,12 @@ void CppGenerator::writeTypeDiscoveryFunction(TextStream &s, if (!polymorphicExpr.isEmpty()) { replacePolymorphicIdPlaceHolders(metaClass, &polymorphicExpr); - s << " if (" << polymorphicExpr << ")\n" << indent + s << "if (" << polymorphicExpr << ")\n" << indent << "return cptr;\n" << outdent; } else if (metaClass->isPolymorphic()) { const auto &ancestors = metaClass->allTypeSystemAncestors(); for (const auto &ancestor : ancestors) { - if (ancestor->baseClass()) + if (ancestor->baseClass() && !ancestor->typeEntry()->isPolymorphicBase()) continue; if (ancestor->isPolymorphic()) { s << "if (instanceType == Shiboken::SbkType< " << m_gsp @@ -5975,18 +6059,14 @@ void CppGenerator::writeInitFunc(TextStream &declStr, TextStream &callStr, } else if (hasParent) { const QString &enclosingName = enclosingEntry->name(); const auto parts = QStringView{enclosingName}.split(u"::", Qt::SkipEmptyParts); + const QString namePathPrefix = enclosingEntry->name().replace("::"_L1, "."_L1); callStr << "Shiboken::Module::AddTypeCreationFunction(" - << "module, \"" << pythonName << "\", " << functionName << ", \""; - for (qsizetype i = 0; i < parts.size(); ++i) { - if (i > 0) - callStr << "\", \""; - callStr << parts.at(i); - } - callStr << "\");\n"; + << "module, \"" << parts[0] << "\", " + << functionName << ", \"" << namePathPrefix << '.' << pythonName << "\");\n"; } else { callStr << "Shiboken::Module::AddTypeCreationFunction(" << "module, \"" << pythonName << "\", " - << "init_" << initFunctionName << ");\n"; + << functionName << ");\n"; } } @@ -6288,6 +6368,8 @@ bool CppGenerator::finishGeneration() // PYSIDE-510: Create a signatures string for the introspection feature. writeSignatureStrings(s, signatureStream.toString(), moduleName(), "global functions"); + writeInitInheritance(s); + // Write module init function const QString globalModuleVar = pythonModuleObjectName(); s << "extern \"C\" LIBSHIBOKEN_EXPORT PyObject *PyInit_" @@ -6425,7 +6507,8 @@ bool CppGenerator::finishGeneration() } } - s << "\nif (" << shibokenErrorsOccurred << ") {\n" << indent + s << '\n' << initInheritanceFunction << "();\n" + << "\nif (" << shibokenErrorsOccurred << ") {\n" << indent << "PyErr_Print();\n" << "Py_FatalError(\"can't initialize module " << moduleName() << "\");\n" << outdent << "}\n"; diff --git a/sources/shiboken6/generator/shiboken/cppgenerator.h b/sources/shiboken6/generator/shiboken/cppgenerator.h index 7e87fd9f3..a31c2ca14 100644 --- a/sources/shiboken6/generator/shiboken/cppgenerator.h +++ b/sources/shiboken6/generator/shiboken/cppgenerator.h @@ -87,6 +87,8 @@ private: const AbstractMetaFunctionCPtr &func, const CodeSnipList &snips, const VirtualMethodReturn &returnStatement) const; + void writeUserAddedPythonOverride(TextStream &s, + const AbstractMetaFunctionCPtr &func) const; void writeVirtualMethodCppCall(TextStream &s, const AbstractMetaFunctionCPtr &func, const QString &funcName, const QList<CodeSnip> &snips, const AbstractMetaArgument *lastArg, const TypeEntryCPtr &retType, @@ -393,10 +395,12 @@ private: static void writeSignatureStrings(TextStream &s, const QString &signatures, const QString &arrayName, const char *comment); + void writeInitInheritance(TextStream &s) const; void writeClassRegister(TextStream &s, const AbstractMetaClassCPtr &metaClass, const GeneratorContext &classContext, const QString &signatures) const; + static QStringList pyBaseTypes(const AbstractMetaClassCPtr &metaClass); static QString destructorClassName(const AbstractMetaClassCPtr &metaClass, const GeneratorContext &classContext); static void writeStaticFieldInitialization(TextStream &s, @@ -477,6 +481,8 @@ private: const AbstractMetaType &type, const ApiExtractorResult &api); void writeSmartPointerConverterInitialization(TextStream &s, const AbstractMetaType &ype) const; + + static QString typeInitStruct(const TypeEntryCPtr &te); static void writeExtendedConverterInitialization(TextStream &s, const TypeEntryCPtr &externalType, const AbstractMetaClassCList &conversions); diff --git a/sources/shiboken6/generator/shiboken/headergenerator.cpp b/sources/shiboken6/generator/shiboken/headergenerator.cpp index c0aa2e129..1f574b47c 100644 --- a/sources/shiboken6/generator/shiboken/headergenerator.cpp +++ b/sources/shiboken6/generator/shiboken/headergenerator.cpp @@ -274,8 +274,15 @@ void *qt_metacast(const char *_clname) override; s << "static void pysideInitQtMetaTypes();\n"; s << "void resetPyMethodCache();\n" - << outdent << "private:\n" << indent - << "mutable bool m_PyMethodCache[" << maxOverrides << "];\n" + << outdent << "private:\n" << indent; + + if (!metaClass->userAddedPythonOverrides().isEmpty()) { + for (const auto &f : metaClass->userAddedPythonOverrides()) + s << functionSignature(f, {}, {}, Generator::OriginalTypeDescription) << ";\n"; + s << '\n'; + } + + s << "mutable bool m_PyMethodCache[" << maxOverrides << "];\n" << outdent << "};\n\n"; } @@ -286,8 +293,6 @@ void HeaderGenerator::writeMemberFunctionWrapper(TextStream &s, { Q_ASSERT(!func->isConstructor() && !func->isOperatorOverload()); s << "inline "; - if (func->isStatic()) - s << "static "; s << functionSignature(func, {}, postfix, Generator::OriginalTypeDescription) << " { "; if (!func->isVoid()) diff --git a/sources/shiboken6/generator/shiboken/shibokengenerator.cpp b/sources/shiboken6/generator/shiboken/shibokengenerator.cpp index ac098f394..a1417e5d9 100644 --- a/sources/shiboken6/generator/shiboken/shibokengenerator.cpp +++ b/sources/shiboken6/generator/shiboken/shibokengenerator.cpp @@ -1147,6 +1147,10 @@ void ShibokenGenerator::writeFunctionArguments(TextStream &s, Options options) const { int argUsed = 0; + if (func->isUserAddedPythonOverride()) { + s << "Shiboken::GilState &gil, PyObject *" << PYTHON_OVERRIDE_VAR; + argUsed += 2; + } for (const auto &arg : func->arguments()) { if (options.testFlag(Generator::SkipRemovedArguments) && arg.isModifiedRemoved()) continue; @@ -1183,6 +1187,8 @@ QString ShibokenGenerator::functionSignature(const AbstractMetaFunctionCPtr &fun { StringStream s(TextStream::Language::Cpp); // The actual function + if (!options.testFlag(Option::SkipDefaultValues) && func->isStatic()) // Declaration + s << "static "; if (func->isEmptyFunction() || func->needsReturnType()) s << functionReturnType(func, options) << ' '; else diff --git a/sources/shiboken6/libshiboken/basewrapper.cpp b/sources/shiboken6/libshiboken/basewrapper.cpp index 35d46b1aa..a79a4cb69 100644 --- a/sources/shiboken6/libshiboken/basewrapper.cpp +++ b/sources/shiboken6/libshiboken/basewrapper.cpp @@ -27,7 +27,9 @@ #include "signature_p.h" #include "voidptr.h" +#include <string> #include <iostream> +#include <sstream> #if defined(__APPLE__) #include <dlfcn.h> @@ -37,7 +39,73 @@ namespace { void _destroyParentInfo(SbkObject *obj, bool keepReference); } -static void callDestructor(const Shiboken::DtorAccumulatorVisitor::DestructorEntries &dts) +namespace Shiboken +{ +// Walk through the first level of non-user-type Sbk base classes relevant for +// C++ object allocation. Return true from the predicate to terminate. +template <class Predicate> +bool walkThroughBases(PyTypeObject *currentType, Predicate predicate) +{ + PyObject *bases = currentType->tp_bases; + const Py_ssize_t numBases = PyTuple_Size(bases); + bool result = false; + for (Py_ssize_t i = 0; !result && i < numBases; ++i) { + auto type = reinterpret_cast<PyTypeObject *>(PyTuple_GetItem(bases, i)); + if (PyType_IsSubtype(type, SbkObject_TypeF()) != 0) { + result = PepType_SOTP(type)->is_user_type + ? walkThroughBases(type, predicate) : predicate(type); + } + } + return result; +} + +int getTypeIndexOnHierarchy(PyTypeObject *baseType, PyTypeObject *desiredType) +{ + int index = -1; + walkThroughBases(baseType, [&index, desiredType](PyTypeObject *node) { + ++index; + return PyType_IsSubtype(node, desiredType) != 0; + }); + return index; +} + +int getNumberOfCppBaseClasses(PyTypeObject *baseType) +{ + int count = 0; + walkThroughBases(baseType, [&count](PyTypeObject *) { + ++count; + return false; + }); + return count; +} + +std::vector<PyTypeObject *> getCppBaseClasses(PyTypeObject *baseType) +{ + std::vector<PyTypeObject *> cppBaseClasses; + walkThroughBases(baseType, [&cppBaseClasses](PyTypeObject *node) { + cppBaseClasses.push_back(node); + return false; + }); + return cppBaseClasses; +} + +using DestructorEntries = std::vector<DestructorEntry>; + +DestructorEntries getDestructorEntries(SbkObject *o) +{ + DestructorEntries result; + void **cptrs = o->d->cptr; + walkThroughBases(Py_TYPE(o), [&result, cptrs](PyTypeObject *node) { + auto *sotp = PepType_SOTP(node); + auto index = result.size(); + result.push_back(DestructorEntry{sotp->cpp_dtor, + cptrs[index]}); + return false; + }); + return result; +} + +static void callDestructor(const DestructorEntries &dts) { for (const auto &e : dts) { Shiboken::ThreadStateSaver threadSaver; @@ -46,6 +114,8 @@ static void callDestructor(const Shiboken::DtorAccumulatorVisitor::DestructorEnt } } +} // namespace Shiboken + extern "C" { @@ -265,8 +335,6 @@ static PyTypeObject *createObjectType() offsetof(SbkObject, ob_dict), offsetof(SbkObject, weakreflist), nullptr); // bufferprocs - // Initialize the hidden data area. - _PepPostInit_SbkObject_Type(type); return type; } @@ -342,9 +410,8 @@ static void SbkDeallocWrapperCommon(PyObject *pyObj, bool canDelete) if (sotp->delete_in_main_thread && Shiboken::currentThreadId() != Shiboken::mainThreadId()) { auto &bindingManager = Shiboken::BindingManager::instance(); if (sotp->is_multicpp) { - Shiboken::DtorAccumulatorVisitor visitor(sbkObj); - Shiboken::walkThroughClassHierarchy(Py_TYPE(pyObj), &visitor); - for (const auto &e : visitor.entries()) + const auto entries = Shiboken::getDestructorEntries(sbkObj); + for (const auto &e : entries) bindingManager.addToDeletionInMainThread(e); } else { Shiboken::DestructorEntry e{sotp->cpp_dtor, sbkObj->d->cptr[0]}; @@ -362,10 +429,9 @@ static void SbkDeallocWrapperCommon(PyObject *pyObj, bool canDelete) if (canDelete) { if (sotp->is_multicpp) { - Shiboken::DtorAccumulatorVisitor visitor(sbkObj); - Shiboken::walkThroughClassHierarchy(Py_TYPE(pyObj), &visitor); + const auto entries = Shiboken::getDestructorEntries(sbkObj); Shiboken::Object::deallocData(sbkObj, true); - callDestructor(visitor.entries()); + callDestructor(entries); } else { void *cptr = sbkObj->d->cptr[0]; Shiboken::Object::deallocData(sbkObj, true); @@ -702,54 +768,24 @@ void _destroyParentInfo(SbkObject *obj, bool keepReference) namespace Shiboken { -bool walkThroughClassHierarchy(PyTypeObject *currentType, HierarchyVisitor *visitor) -{ - PyObject *bases = currentType->tp_bases; - Py_ssize_t numBases = PyTuple_GET_SIZE(bases); - bool result = false; - for (int i = 0; !result && i < numBases; ++i) { - auto type = reinterpret_cast<PyTypeObject *>(PyTuple_GET_ITEM(bases, i)); - if (PyType_IsSubtype(type, reinterpret_cast<PyTypeObject *>(SbkObject_TypeF()))) { - result = PepType_SOTP(type)->is_user_type - ? walkThroughClassHierarchy(type, visitor) : visitor->visit(type); - } - } - return result; -} // Wrapper metatype and base type ---------------------------------------------------------- -HierarchyVisitor::HierarchyVisitor() = default; -HierarchyVisitor::~HierarchyVisitor() = default; - -bool BaseCountVisitor::visit(PyTypeObject *) -{ - m_count++; - return false; -} - -bool BaseAccumulatorVisitor::visit(PyTypeObject *node) -{ - m_bases.push_back(node); - return false; -} - -bool GetIndexVisitor::visit(PyTypeObject *node) -{ - m_index++; - return PyType_IsSubtype(node, m_desiredType); -} +void _initMainThreadId(); // helper.cpp -bool DtorAccumulatorVisitor::visit(PyTypeObject *node) +static std::string msgFailedToInitializeType(const char *description) { - auto *sotp = PepType_SOTP(node); - m_entries.push_back(DestructorEntry{sotp->cpp_dtor, - m_pyObject->d->cptr[m_entries.size()]}); - return false; + std::ostringstream stream; + stream << "[libshiboken] Failed to initialize " << description; + if (auto *error = PepErr_GetRaisedException()) { + if (auto *str = PyObject_Str(error)) + stream << ": " << Shiboken::String::toCString(str); + Py_DECREF(error); + } + stream << '.'; + return stream.str(); } -void _initMainThreadId(); // helper.cpp - namespace Conversions { void init(); } void init() @@ -765,11 +801,13 @@ void init() //Init private data Pep384_Init(); - if (PyType_Ready(SbkObjectType_TypeF()) < 0) - Py_FatalError("[libshiboken] Failed to initialize Shiboken.BaseWrapperType metatype."); + auto *type = SbkObjectType_TypeF(); + if (type == nullptr || PyType_Ready(type) < 0) + Py_FatalError(msgFailedToInitializeType("Shiboken.BaseWrapperType metatype").c_str()); - if (PyType_Ready(SbkObject_TypeF()) < 0) - Py_FatalError("[libshiboken] Failed to initialize Shiboken.BaseWrapper type."); + type = SbkObject_TypeF(); + if (type == nullptr || PyType_Ready(type) < 0) + Py_FatalError(msgFailedToInitializeType("Shiboken.BaseWrapper type").c_str()); VoidPtr::init(); @@ -843,20 +881,6 @@ PyObject *checkInvalidArgumentCount(Py_ssize_t numArgs, Py_ssize_t minArgs, Py_s return result; } -class FindBaseTypeVisitor : public HierarchyVisitor -{ -public: - explicit FindBaseTypeVisitor(PyTypeObject *typeToFind) : m_typeToFind(typeToFind) {} - - bool visit(PyTypeObject *node) override - { - return node == m_typeToFind; - } - -private: - PyTypeObject *m_typeToFind; -}; - std::vector<SbkObject *> splitPyObject(PyObject *pyObj) { std::vector<SbkObject *> result; @@ -897,8 +921,8 @@ bool isUserType(PyTypeObject *type) bool canCallConstructor(PyTypeObject *myType, PyTypeObject *ctorType) { - FindBaseTypeVisitor visitor(ctorType); - if (!walkThroughClassHierarchy(myType, &visitor)) { + auto findBasePred = [ctorType](PyTypeObject *type) { return type == ctorType; }; + if (!walkThroughBases(myType, findBasePred)) { PyErr_Format(PyExc_TypeError, "%s isn't a direct base class of %s", ctorType->tp_name, myType->tp_name); return false; } @@ -970,21 +994,15 @@ introduceWrapperType(PyObject *enclosingObject, const char *originalName, PyType_Spec *typeSpec, ObjectDestructor cppObjDtor, - PyTypeObject *baseType, - PyObject *baseTypes, + PyObject *bases, unsigned wrapperFlags) { - auto *base = baseType ? baseType : SbkObject_TypeF(); - typeSpec->slots[0].pfunc = reinterpret_cast<void *>(base); - auto *bases = baseTypes ? baseTypes : PyTuple_Pack(1, base); + const auto basesSize = PySequence_Fast_GET_SIZE(bases); + assert(basesSize > 0); + typeSpec->slots[0].pfunc = PySequence_Fast_GET_ITEM(bases, 0); auto *type = SbkType_FromSpecBasesMeta(typeSpec, bases, SbkObjectType_TypeF()); - for (int i = 0; i < PySequence_Fast_GET_SIZE(bases); ++i) { - auto *st = reinterpret_cast<PyTypeObject *>(PySequence_Fast_GET_ITEM(bases, i)); - BindingManager::instance().addClassInheritance(st, type); - } - auto sotp = PepType_SOTP(type); if (wrapperFlags & DeleteInMainThread) sotp->delete_in_main_thread = 1; @@ -1054,6 +1072,26 @@ bool hasSpecialCastFunction(PyTypeObject *sbkType) return d != nullptr && d->mi_specialcast != nullptr; } +// Find whether base is a direct single line base class of type +// (no multiple inheritance), that is, a C++ pointer cast can safely be done. +static bool isDirectAncestor(PyTypeObject *type, PyTypeObject *base) +{ + if (type == base) + return true; + if (PyTuple_Size(type->tp_bases) == 0) + return false; + auto *sbkObjectType = SbkObject_TypeF(); + auto *firstBase = reinterpret_cast<PyTypeObject *>(PyTuple_GetItem(type->tp_bases, 0)); + return firstBase != sbkObjectType + && PyType_IsSubtype(type, sbkObjectType) != 0 + && isDirectAncestor(firstBase, base); +} + +bool canDowncastTo(PyTypeObject *baseType, PyTypeObject *targetType) +{ + return isDirectAncestor(targetType, baseType); +} + } // namespace ObjectType @@ -1142,9 +1180,7 @@ void callCppDestructors(SbkObject *pyObj) PyTypeObject *type = Py_TYPE(pyObj); auto *sotp = PepType_SOTP(type); if (sotp->is_multicpp) { - Shiboken::DtorAccumulatorVisitor visitor(pyObj); - Shiboken::walkThroughClassHierarchy(type, &visitor); - callDestructor(visitor.entries()); + callDestructor(getDestructorEntries(pyObj)); } else { Shiboken::ThreadStateSaver threadSaver; threadSaver.save(); @@ -1425,20 +1461,67 @@ SbkObject *findColocatedChild(SbkObject *wrapper, return nullptr; } +// Legacy, for compatibility only. PyObject *newObject(PyTypeObject *instanceType, void *cptr, bool hasOwnership, bool isExactType, const char *typeName) { - // Try to find the exact type of cptr. - if (!isExactType) { - if (PyTypeObject *exactType = ObjectType::typeForTypeName(typeName)) - instanceType = exactType; - else - instanceType = BindingManager::instance().resolveType(&cptr, instanceType); + return isExactType + ? newObjectForType(instanceType, cptr, hasOwnership) + : newObjectWithHeuristics(instanceType, cptr, hasOwnership, typeName); +} + +static PyObject *newObjectWithHeuristicsHelper(PyTypeObject *instanceType, + PyTypeObject *exactType, + void *cptr, + bool hasOwnership) +{ + // Try to find the exact type of cptr. For hierarchies with + // non-virtual destructors, typeid() will return the base name. + // Try type discovery in these cases. + if (exactType == nullptr || exactType == instanceType) { + auto resolved = BindingManager::instance().findDerivedType(cptr, instanceType); + if (resolved.first != nullptr + && Shiboken::ObjectType::canDowncastTo(instanceType, resolved.first)) { + exactType = resolved.first; + cptr = resolved.second; + } } + return newObjectForType(exactType != nullptr ? exactType : instanceType, + cptr, hasOwnership); +} + +PyObject *newObjectForPointer(PyTypeObject *instanceType, + void *cptr, + bool hasOwnership, + const char *typeName) +{ + // Try to find the exact type of cptr. + PyTypeObject *exactType = ObjectType::typeForTypeName(typeName); + // PYSIDE-868: In case of multiple inheritance, (for example, + // a function returning a QPaintDevice * from a QWidget *), + // use instance type to avoid pointer offset errors. + return exactType != nullptr && !Shiboken::ObjectType::canDowncastTo(instanceType, exactType) + ? newObjectForType(instanceType, cptr, hasOwnership) + : newObjectWithHeuristicsHelper(instanceType, exactType, cptr, hasOwnership); +} + + +PyObject *newObjectWithHeuristics(PyTypeObject *instanceType, + void *cptr, + bool hasOwnership, + const char *typeName) +{ + return newObjectWithHeuristicsHelper(instanceType, + ObjectType::typeForTypeName(typeName), + cptr, hasOwnership); +} + +PyObject *newObjectForType(PyTypeObject *instanceType, void *cptr, bool hasOwnership) +{ bool shouldCreate = true; bool shouldRegister = true; SbkObject *self = nullptr; diff --git a/sources/shiboken6/libshiboken/basewrapper.h b/sources/shiboken6/libshiboken/basewrapper.h index f2189d824..ec5545aea 100644 --- a/sources/shiboken6/libshiboken/basewrapper.h +++ b/sources/shiboken6/libshiboken/basewrapper.h @@ -229,13 +229,12 @@ enum WrapperFlags * \returns true if the initialization went fine, false otherwise. */ LIBSHIBOKEN_API PyTypeObject *introduceWrapperType(PyObject *enclosingObject, - const char *typeName, - const char *originalName, - PyType_Spec *typeSpec, - ObjectDestructor cppObjDtor, - PyTypeObject *baseType, - PyObject *baseTypes, - unsigned wrapperFlags = 0); + const char *typeName, + const char *originalName, + PyType_Spec *typeSpec, + ObjectDestructor cppObjDtor, + PyObject *bases, + unsigned wrapperFlags = 0); /** * Set the subtype init hook for a type. @@ -266,6 +265,14 @@ LIBSHIBOKEN_API PyTypeObject *typeForTypeName(const char *typeName); * \since 5.12 */ LIBSHIBOKEN_API bool hasSpecialCastFunction(PyTypeObject *sbkType); + +/// Returns whether a C++ pointer of \p baseType can be safely downcast +/// to \p targetType (base is a direct, single line base class of targetType). +/// (is a direct, single-line inheritance) +/// \param baseType Python type of base class +/// \param targetType Python type of derived class +/// \since 6.8 +LIBSHIBOKEN_API bool canDowncastTo(PyTypeObject *baseType, PyTypeObject *targetType); } namespace Object { @@ -298,7 +305,8 @@ LIBSHIBOKEN_API SbkObject *findColocatedChild(SbkObject *wrapper, const PyTypeObject *instanceType); /** - * Bind a C++ object to Python. + * Bind a C++ object to Python. Forwards to + * newObjectWithHeuristics(), newObjectForType() depending on \p isExactType. * \param instanceType equivalent Python type for the C++ object. * \param hasOwnership if true, Python will try to delete the underlying C++ object when there's no more refs. * \param isExactType if false, Shiboken will use some heuristics to detect the correct Python type of this C++ @@ -312,6 +320,40 @@ LIBSHIBOKEN_API PyObject *newObject(PyTypeObject *instanceType, bool isExactType = false, const char *typeName = nullptr); +/// Bind a C++ object to Python for polymorphic pointers. Calls +/// newObjectWithHeuristics() with an additional check for multiple +/// inheritance, in which case it will fall back to instanceType. +/// \param instanceType Equivalent Python type for the C++ object. +/// \param hasOwnership if true, Python will try to delete the underlying C++ object +/// when there's no more refs. +/// \param typeName If non-null, this will be used as helper to find the correct +/// Python type for this object (obtained by typeid().name(). +LIBSHIBOKEN_API PyObject *newObjectForPointer(PyTypeObject *instanceType, + void *cptr, + bool hasOwnership = true, + const char *typeName = nullptr); + +/// Bind a C++ object to Python using some heuristics to detect the correct +/// Python type of this C++ object. In any case \p instanceType must be provided; +/// it'll be used as search starting point and as fallback. +/// \param instanceType Equivalent Python type for the C++ object. +/// \param hasOwnership if true, Python will try to delete the underlying C++ object +/// C++ object when there are no more references. +/// when there's no more refs. +/// \param typeName If non-null, this will be used as helper to find the correct +/// Python type for this object (obtained by typeid().name(). +LIBSHIBOKEN_API PyObject *newObjectWithHeuristics(PyTypeObject *instanceType, + void *cptr, + bool hasOwnership = true, + const char *typeName = nullptr); + +/// Bind a C++ object to Python using the given type. +/// \param instanceType Equivalent Python type for the C++ object. +/// \param hasOwnership if true, Python will try to delete the underlying +/// C++ object when there are no more references. +LIBSHIBOKEN_API PyObject *newObjectForType(PyTypeObject *instanceType, + void *cptr, bool hasOwnership = true); + /** * Changes the valid flag of a PyObject, invalid objects will raise an exception when someone tries to access it. */ diff --git a/sources/shiboken6/libshiboken/basewrapper_p.h b/sources/shiboken6/libshiboken/basewrapper_p.h index 526bf9fa3..fb9140793 100644 --- a/sources/shiboken6/libshiboken/basewrapper_p.h +++ b/sources/shiboken6/libshiboken/basewrapper_p.h @@ -114,7 +114,8 @@ struct SbkObjectTypePrivate /// True if this type holds two or more C++ instances, e.g.: a Python class which inherits from two C++ classes. unsigned int is_multicpp : 1; - /// True if this type was defined by the user. + /// True if this type was defined by the user (a class written in Python inheriting + /// a class provided by a Shiboken binding). unsigned int is_user_type : 1; /// Tells is the type is a value type or an object-type, see BEHAVIOUR_ *constants. unsigned int type_behaviour : 2; @@ -142,107 +143,7 @@ struct DestructorEntry **/ std::vector<SbkObject *> splitPyObject(PyObject *pyObj); -/** -* Visitor class used by walkOnClassHierarchy function. -*/ -class HierarchyVisitor -{ -public: - HierarchyVisitor(const HierarchyVisitor &) = delete; - HierarchyVisitor(HierarchyVisitor &&) = delete; - HierarchyVisitor &operator=(const HierarchyVisitor &) = delete; - HierarchyVisitor &operator=(HierarchyVisitor &&) = delete; - - HierarchyVisitor(); - virtual ~HierarchyVisitor(); - - virtual bool visit(PyTypeObject *node) = 0; // return true to terminate -}; - -class BaseCountVisitor : public HierarchyVisitor -{ -public: - bool visit(PyTypeObject *) override; - - int count() const { return m_count; } - -private: - int m_count = 0; -}; - -class BaseAccumulatorVisitor : public HierarchyVisitor -{ -public: - using Result = std::vector<PyTypeObject *>; - - bool visit(PyTypeObject *node) override; - - Result bases() const { return m_bases; } - -private: - Result m_bases; -}; - -class GetIndexVisitor : public HierarchyVisitor -{ -public: - explicit GetIndexVisitor(PyTypeObject *desiredType) : m_desiredType(desiredType) {} - - bool visit(PyTypeObject *node) override; - - int index() const { return m_index; } - -private: - int m_index = -1; - PyTypeObject *m_desiredType; -}; - -/// Collect destructors and C++ instances of each C++ object held by a Python -/// object -class DtorAccumulatorVisitor : public HierarchyVisitor -{ -public: - explicit DtorAccumulatorVisitor(SbkObject *pyObj) : m_pyObject(pyObj) {} - - bool visit(PyTypeObject *node) override; - - using DestructorEntries = std::vector<DestructorEntry>; - - const DestructorEntries &entries() const { return m_entries; } - -private: - DestructorEntries m_entries; - SbkObject *m_pyObject; -}; - -/// \internal Internal function used to walk on classes inheritance trees. -/** -* Walk on class hierarchy using a DFS algorithm. -* For each pure Shiboken type found, HierarchyVisitor::visit is called and the algorithm -* considers all children of this type as visited. -*/ -bool walkThroughClassHierarchy(PyTypeObject *currentType, HierarchyVisitor *visitor); - -inline int getTypeIndexOnHierarchy(PyTypeObject *baseType, PyTypeObject *desiredType) -{ - GetIndexVisitor visitor(desiredType); - walkThroughClassHierarchy(baseType, &visitor); - return visitor.index(); -} - -inline int getNumberOfCppBaseClasses(PyTypeObject *baseType) -{ - BaseCountVisitor visitor; - walkThroughClassHierarchy(baseType, &visitor); - return visitor.count(); -} - -inline std::vector<PyTypeObject *> getCppBaseClasses(PyTypeObject *baseType) -{ - BaseAccumulatorVisitor visitor; - walkThroughClassHierarchy(baseType, &visitor); - return visitor.bases(); -} +int getNumberOfCppBaseClasses(PyTypeObject *baseType); namespace Object { diff --git a/sources/shiboken6/libshiboken/bindingmanager.cpp b/sources/shiboken6/libshiboken/bindingmanager.cpp index a0acc4e4b..83c927ae5 100644 --- a/sources/shiboken6/libshiboken/bindingmanager.cpp +++ b/sources/shiboken6/libshiboken/bindingmanager.cpp @@ -7,6 +7,7 @@ #include "bindingmanager.h" #include "gilstate.h" #include "helper.h" +#include "sbkmodule.h" #include "sbkstring.h" #include "sbkstaticstrings.h" #include "sbkfeature_base.h" @@ -15,94 +16,152 @@ #include <cstddef> #include <cstring> #include <fstream> +#include <iostream> #include <mutex> +#include <string_view> #include <unordered_map> +#include <unordered_set> + +// GraphNode for the dependency graph. It keeps a pointer to +// the TypeInitStruct to be able to lazily create the type and hashes +// by the full type name. +struct GraphNode +{ + explicit GraphNode(Shiboken::Module::TypeInitStruct *i) : name(i->fullName), initStruct(i) {} + explicit GraphNode(const char *n) : name(n), initStruct(nullptr) {} // Only for searching + + std::string_view name; + Shiboken::Module::TypeInitStruct *initStruct; + + friend bool operator==(const GraphNode &n1, const GraphNode &n2) { return n1.name == n2.name; } + friend bool operator!=(const GraphNode &n1, const GraphNode &n2) { return n1.name != n2.name; } +}; + +template <> +struct std::hash<GraphNode> { + size_t operator()(const GraphNode &n) const noexcept + { + return std::hash<std::string_view>{}(n.name); + } +}; namespace Shiboken { using WrapperMap = std::unordered_map<const void *, SbkObject *>; -class Graph +template <class NodeType> +class BaseGraph { public: - using NodeList = std::vector<PyTypeObject *>; - using Edges = std::unordered_map<PyTypeObject *, NodeList>; + using NodeList = std::vector<NodeType>; + using NodeSet = std::unordered_set<NodeType>; + + using Edges = std::unordered_map<NodeType, NodeList>; Edges m_edges; - Graph() = default; + BaseGraph() = default; - void addEdge(PyTypeObject *from, PyTypeObject *to) + void addEdge(NodeType from, NodeType to) { m_edges[from].push_back(to); } -#ifndef NDEBUG - void dumpDotGraph() const + NodeSet nodeSet() const { - std::ofstream file("/tmp/shiboken_graph.dot"); - - file << "digraph D {\n"; - + NodeSet result; for (const auto &p : m_edges) { - auto *node1 = p.first; - const NodeList &nodeList = p.second; - for (const PyTypeObject *o : nodeList) { - auto *node2 = o; - file << '"' << node2->tp_name << "\" -> \"" - << node1->tp_name << "\"\n"; - } + result.insert(p.first); + for (const auto node2 : p.second) + result.insert(node2); } - file << "}\n"; + return result; } -#endif +}; - PyTypeObject *identifyType(void **cptr, PyTypeObject *type, PyTypeObject *baseType) const +class Graph : public BaseGraph<GraphNode> +{ +public: + using TypeCptrPair = BindingManager::TypeCptrPair; + + TypeCptrPair identifyType(void *cptr, PyTypeObject *type, PyTypeObject *baseType) const { - auto edgesIt = m_edges.find(type); - if (edgesIt != m_edges.end()) { - const NodeList &adjNodes = m_edges.find(type)->second; - for (PyTypeObject *node : adjNodes) { - PyTypeObject *newType = identifyType(cptr, node, baseType); - if (newType) - return newType; - } - } - void *typeFound = nullptr; - auto *sotp = PepType_SOTP(type); - if (sotp->type_discovery) - typeFound = sotp->type_discovery(*cptr, baseType); - if (typeFound) { - // This "typeFound != type" is needed for backwards compatibility with old modules using a newer version of - // libshiboken because old versions of type_discovery function used to return a PyTypeObject *instead of - // a possible variation of the C++ instance pointer (*cptr). - if (typeFound != type) - *cptr = typeFound; - return type; - } - return nullptr; + return identifyType(cptr, GraphNode(type->tp_name), type, baseType); } -}; + bool dumpTypeGraph(const char *fileName) const; -#ifndef NDEBUG -static void showWrapperMap(const WrapperMap &wrapperMap) +private: + TypeCptrPair identifyType(void *cptr, const GraphNode &typeNode, PyTypeObject *type, + PyTypeObject *baseType) const; +}; + +Graph::TypeCptrPair Graph::identifyType(void *cptr, + const GraphNode &typeNode, PyTypeObject *type, + PyTypeObject *baseType) const { - if (Shiboken::pyVerbose() > 0) { - fprintf(stderr, "-------------------------------\n"); - fprintf(stderr, "WrapperMap: %p (size: %d)\n", &wrapperMap, (int) wrapperMap.size()); - for (auto it = wrapperMap.begin(), end = wrapperMap.end(); it != end; ++it) { - const SbkObject *sbkObj = it->second; - fprintf(stderr, "key: %p, value: %p (%s, refcnt: %d)\n", it->first, - static_cast<const void *>(sbkObj), - (Py_TYPE(sbkObj))->tp_name, - int(Py_REFCNT(reinterpret_cast<const PyObject *>(sbkObj)))); + assert(typeNode.initStruct != nullptr || type != nullptr); + auto edgesIt = m_edges.find(typeNode); + if (edgesIt != m_edges.end()) { + const NodeList &adjNodes = edgesIt->second; + for (const auto &node : adjNodes) { + auto newType = identifyType(cptr, node, nullptr, baseType); + if (newType.first != nullptr) + return newType; } - fprintf(stderr, "-------------------------------\n"); } + + if (type == nullptr) { + if (typeNode.initStruct->type == nullptr) // Layzily create type + type = Shiboken::Module::get(*typeNode.initStruct); + else + type = typeNode.initStruct->type; + } + + auto *sotp = PepType_SOTP(type); + if (sotp->type_discovery != nullptr) { + if (void *derivedCPtr = sotp->type_discovery(cptr, baseType)) + return {type, derivedCPtr}; + } + return {nullptr, nullptr}; +} + +static void formatDotNode(std::string_view name, std::ostream &file) +{ + auto lastDot = name.rfind('.'); + file << " \"" << name << "\" [ label="; + if (lastDot != std::string::npos) { + file << '"' << name.substr(lastDot + 1) << "\" tooltip=\"" + << name.substr(0, lastDot) << '"'; + } else { + file << '"' << name << '"'; + } + file << " ]\n"; +} + +bool Graph::dumpTypeGraph(const char *fileName) const +{ + std::ofstream file(fileName); + if (!file.good()) + return false; + + file << "digraph D {\n"; + + // Define nodes with short names + for (const auto &node : nodeSet()) + formatDotNode(node.name, file); + + // Write edges + for (const auto &p : m_edges) { + const auto &node1 = p.first; + const NodeList &nodeList = p.second; + for (const auto &node2 : nodeList) + file << " \"" << node2.name << "\" -> \"" << node1.name << "\"\n"; + } + file << "}\n"; + return true; } -#endif struct BindingManager::BindingManagerPrivate { using DestructorEntries = std::vector<DestructorEntry>; @@ -115,9 +174,6 @@ struct BindingManager::BindingManagerPrivate { std::recursive_mutex wrapperMapLock; Graph classHierarchy; DestructorEntries deleteInMainThread; - bool destroying; - - BindingManagerPrivate() : destroying(false) {} bool releaseWrapper(void *cptr, SbkObject *wrapper, const int *bases = nullptr); bool releaseWrapperHelper(void *cptr, SbkObject *wrapper); @@ -189,7 +245,8 @@ BindingManager::~BindingManager() debugRemoveFreeHook(); #endif #ifndef NDEBUG - showWrapperMap(m_d->wrapperMapper); + if (Shiboken::pyVerbose() > 0) + dumpWrapperMap(); #endif /* Cleanup hanging references. We just invalidate them as when * the BindingManager is being destroyed the interpreter is alredy @@ -363,15 +420,24 @@ PyObject *BindingManager::getOverride(const void *cptr, return nullptr; } -void BindingManager::addClassInheritance(PyTypeObject *parent, PyTypeObject *child) +void BindingManager::addClassInheritance(Module::TypeInitStruct *parent, + Module::TypeInitStruct *child) +{ + m_d->classHierarchy.addEdge(GraphNode(parent), GraphNode(child)); +} + +BindingManager::TypeCptrPair BindingManager::findDerivedType(void *cptr, PyTypeObject *type) const { - m_d->classHierarchy.addEdge(parent, child); + return m_d->classHierarchy.identifyType(cptr, type, type); } +// FIXME PYSIDE7: remove, just for compatibility PyTypeObject *BindingManager::resolveType(void **cptr, PyTypeObject *type) { - PyTypeObject *identifiedType = m_d->classHierarchy.identifyType(cptr, type, type); - return identifiedType ? identifiedType : type; + auto result = findDerivedType(*cptr, type); + if (result.second != nullptr) + *cptr = result.second; + return result.first != nullptr ? result.first : type; } std::set<PyObject *> BindingManager::getAllPyObjects() @@ -395,6 +461,27 @@ void BindingManager::visitAllPyObjects(ObjectVisitor visitor, void *data) } } +bool BindingManager::dumpTypeGraph(const char *fileName) const +{ + return m_d->classHierarchy.dumpTypeGraph(fileName); +} + +void BindingManager::dumpWrapperMap() +{ + const auto &wrapperMap = m_d->wrapperMapper; + std::cerr << "-------------------------------\n" + << "WrapperMap size: " << wrapperMap.size() << " Types: " + << m_d->classHierarchy.nodeSet().size() << '\n'; + for (auto it = wrapperMap.begin(), end = wrapperMap.end(); it != end; ++it) { + const SbkObject *sbkObj = it->second; + std::cerr << "key: " << it->first << ", value: " + << static_cast<const void *>(sbkObj) << " (" + << (Py_TYPE(sbkObj))->tp_name << ", refcnt: " + << Py_REFCNT(reinterpret_cast<const PyObject *>(sbkObj)) << ")\n"; + } + std::cerr << "-------------------------------\n"; +} + static bool isPythonType(PyTypeObject *type) { // This is a type which should be called by multiple inheritance. diff --git a/sources/shiboken6/libshiboken/bindingmanager.h b/sources/shiboken6/libshiboken/bindingmanager.h index 4b21ae835..54c4e486a 100644 --- a/sources/shiboken6/libshiboken/bindingmanager.h +++ b/sources/shiboken6/libshiboken/bindingmanager.h @@ -5,14 +5,20 @@ #define BINDINGMANAGER_H #include "sbkpython.h" -#include <set> #include "shibokenmacros.h" +#include <set> +#include <utility> + struct SbkObject; namespace Shiboken { +namespace Module { +struct TypeInitStruct; +} + struct DestructorEntry; using ObjectVisitor = void (*)(SbkObject *, void *); @@ -38,7 +44,15 @@ public: SbkObject *retrieveWrapper(const void *cptr); PyObject *getOverride(const void *cptr, PyObject *nameCache[], const char *methodName); - void addClassInheritance(PyTypeObject *parent, PyTypeObject *child); + void addClassInheritance(Module::TypeInitStruct *parent, Module::TypeInitStruct *child); + /// Try to find the correct type of cptr via type discovery knowing that it's at least + /// of type \p type. If a derived class is found, it returns a cptr cast to the type + /// (which may be different in case of multiple inheritance. + /// \param cptr a pointer to the instance of type \p type + /// \param type type of cptr + using TypeCptrPair = std::pair<PyTypeObject *, void *>; + TypeCptrPair findDerivedType(void *cptr, PyTypeObject *type) const; + /** * Try to find the correct type of *cptr knowing that it's at least of type \p type. * In case of multiple inheritance this function may change the contents of cptr. @@ -46,7 +60,7 @@ public: * \param type type of *cptr * \warning This function is slow, use it only as last resort. */ - PyTypeObject *resolveType(void **cptr, PyTypeObject *type); + [[deprecated]] PyTypeObject *resolveType(void **cptr, PyTypeObject *type); std::set<PyObject *> getAllPyObjects(); @@ -59,6 +73,9 @@ public: */ void visitAllPyObjects(ObjectVisitor visitor, void *data); + bool dumpTypeGraph(const char *fileName) const; + void dumpWrapperMap(); + private: ~BindingManager(); BindingManager(); diff --git a/sources/shiboken6/libshiboken/helper.cpp b/sources/shiboken6/libshiboken/helper.cpp index 23663433e..46af68956 100644 --- a/sources/shiboken6/libshiboken/helper.cpp +++ b/sources/shiboken6/libshiboken/helper.cpp @@ -13,6 +13,7 @@ #include <iomanip> #include <iostream> +#include <climits> #include <cstring> #include <cstdarg> #include <cctype> @@ -43,14 +44,21 @@ static std::optional<int> getIntAttr(PyObject *obj, const char *what) return std::nullopt; } +static bool verbose = false; + static void formatTypeTuple(PyObject *t, const char *what, std::ostream &str); static void formatPyTypeObject(const PyTypeObject *obj, std::ostream &str, bool verbose) { - if (obj) { + if (obj == nullptr) { + str << '0'; + return; + } + + str << '"' << obj->tp_name << '"'; + if (verbose) { bool immutableType = false; - str << '"' << obj->tp_name << "\", 0x" << std::hex - << obj->tp_flags << std::dec; + str << ", 0x" << std::hex << obj->tp_flags << std::dec; if (obj->tp_flags & Py_TPFLAGS_HEAPTYPE) str << " [heaptype]"; if (obj->tp_flags & Py_TPFLAGS_BASETYPE) @@ -108,8 +116,6 @@ static void formatPyTypeObject(const PyTypeObject *obj, std::ostream &str, bool } } } - } else { - str << '0'; } } @@ -206,6 +212,8 @@ static void formatPyUnicode(PyObject *obj, std::ostream &str) { // Note: The below call create the PyCompactUnicodeObject.utf8 representation str << '"' << _PepUnicode_AsString(obj) << '"'; + if (!verbose) + return; str << " (" << PyUnicode_GetLength(obj) << ')'; const auto kind = _PepUnicode_KIND(obj); @@ -322,7 +330,11 @@ static void formatPyObjectHelper(PyObject *obj, std::ostream &str) str << "False"; return; } - str << "refs=" << Py_REFCNT(obj) << ", "; + const auto refs = Py_REFCNT(obj); + if (refs == UINT_MAX) // _Py_IMMORTAL_REFCNT + str << "immortal, "; + else + str << "refs=" << refs << ", "; if (PyType_Check(obj)) { str << "type: "; formatPyTypeObject(reinterpret_cast<PyTypeObject *>(obj), str, true); @@ -424,6 +436,18 @@ std::ostream &operator<<(std::ostream &str, const debugPyBuffer &b) return str; } +std::ios_base &debugVerbose(std::ios_base &s) +{ + verbose = true; + return s; +} + +std::ios_base &debugBrief(std::ios_base &s) +{ + verbose = false; + return s; +} + #ifdef _WIN32 // Converts a Unicode string to a string encoded in the Windows console's // code page via wchar_t for use with argv (PYSIDE-1425). diff --git a/sources/shiboken6/libshiboken/helper.h b/sources/shiboken6/libshiboken/helper.h index 4e14b8c4b..f226e8c24 100644 --- a/sources/shiboken6/libshiboken/helper.h +++ b/sources/shiboken6/libshiboken/helper.h @@ -112,7 +112,8 @@ LIBSHIBOKEN_API std::ostream &operator<<(std::ostream &str, const debugSbkObject LIBSHIBOKEN_API std::ostream &operator<<(std::ostream &str, const debugPyTypeObject &o); LIBSHIBOKEN_API std::ostream &operator<<(std::ostream &str, const debugPyBuffer &b); LIBSHIBOKEN_API std::ostream &operator<<(std::ostream &str, const debugPyArrayObject &b); - +LIBSHIBOKEN_API std::ios_base &debugVerbose(std::ios_base &s); +LIBSHIBOKEN_API std::ios_base &debugBrief(std::ios_base &s); } // namespace Shiboken diff --git a/sources/shiboken6/libshiboken/pep384impl.cpp b/sources/shiboken6/libshiboken/pep384impl.cpp index 4826fb379..2b04af857 100644 --- a/sources/shiboken6/libshiboken/pep384impl.cpp +++ b/sources/shiboken6/libshiboken/pep384impl.cpp @@ -105,13 +105,13 @@ static PyType_Spec typeprobe_spec = { static void check_PyTypeObject_valid() { - auto *obtype = reinterpret_cast<PyObject *>(&PyType_Type); - auto *probe_tp_base = reinterpret_cast<PyTypeObject *>( - PyObject_GetAttr(obtype, Shiboken::PyMagicName::base())); + auto *typetype = &PyType_Type; + auto *obtype = reinterpret_cast<PyObject *>(typetype); + auto *probe_tp_base_obj = PyObject_GetAttr(obtype, Shiboken::PyMagicName::base()); + auto *probe_tp_base = reinterpret_cast<PyTypeObject *>(probe_tp_base_obj); auto *probe_tp_bases = PyObject_GetAttr(obtype, Shiboken::PyMagicName::bases()); - auto *check = reinterpret_cast<PyTypeObject *>( - PyType_FromSpecWithBases(&typeprobe_spec, probe_tp_bases)); - auto *typetype = reinterpret_cast<PyTypeObject *>(obtype); + auto *checkObj = PyType_FromSpecWithBases(&typeprobe_spec, probe_tp_bases); + auto *check = reinterpret_cast<PyTypeObject *>(checkObj); PyObject *w = PyObject_GetAttr(obtype, Shiboken::PyMagicName::weakrefoffset()); long probe_tp_weakrefoffset = PyLong_AsLong(w); PyObject *d = PyObject_GetAttr(obtype, Shiboken::PyMagicName::dictoffset()); @@ -149,8 +149,8 @@ check_PyTypeObject_valid() || probe_tp_mro != typetype->tp_mro || Py_TPFLAGS_DEFAULT != (check->tp_flags & Py_TPFLAGS_DEFAULT)) Py_FatalError("The structure of type objects has changed!"); - Py_DECREF(check); - Py_DECREF(probe_tp_base); + Py_DECREF(checkObj); + Py_DECREF(probe_tp_base_obj); Py_DECREF(w); Py_DECREF(d); Py_DECREF(probe_tp_bases); @@ -482,6 +482,22 @@ Pep_GetVerboseFlag() } #endif // Py_LIMITED_API +// Support for pyerrors.h + +#if defined(Py_LIMITED_API) || PY_VERSION_HEX < 0x030C0000 +// Emulate PyErr_GetRaisedException() using the deprecated PyErr_Fetch()/PyErr_Store() +PyObject *PepErr_GetRaisedException() +{ + PyObject *type{}; + PyObject *value{}; + PyObject *traceback{}; + PyErr_Fetch(&type, &value, &traceback); + Py_XINCREF(value); + PyErr_Restore(type, value, traceback); + return value; +} +#endif // Limited or < 3.12 + /***************************************************************************** * * Support for code.h @@ -722,11 +738,8 @@ PyTypeObject *PepStaticMethod_TypePtr = nullptr; static PyTypeObject * getStaticMethodType(void) { - // this works for Python 3, only - // "StaticMethodType = type(str.__dict__['maketrans'])\n"; static const char prog[] = - "from xxsubtype import spamlist\n" - "result = type(spamlist.__dict__['staticmeth'])\n"; + "result = type(str.__dict__['maketrans'])\n"; return reinterpret_cast<PyTypeObject *>(PepRun_GetResult(prog)); } @@ -1009,9 +1022,12 @@ long _PepRuntimeVersion() SbkObjectTypePrivate *PepType_SOTP(PyTypeObject *type) { + // PYSIDE-2676: Use the meta type explicitly. + // A derived type would fail the offset calculation. + static auto *meta = SbkObjectType_TypeF(); assert(SbkObjectType_Check(type)); auto *obType = reinterpret_cast<PyObject *>(type); - void *data = PyObject_GetTypeData(obType, Py_TYPE(obType)); + void *data = PyObject_GetTypeData(obType, meta); return reinterpret_cast<SbkObjectTypePrivate *>(data); } @@ -1061,11 +1077,12 @@ static thread_local SbkObjectTypePrivate *SOTP_value{}; SbkObjectTypePrivate *PepType_SOTP(PyTypeObject *type) { + static auto *meta = SbkObjectType_TypeF(); static bool use_312 = _PepRuntimeVersion() >= 0x030C00; assert(SbkObjectType_Check(type)); if (use_312) { auto *obType = reinterpret_cast<PyObject *>(type); - void *data = PepObject_GetTypeData(obType, Py_TYPE(obType)); + void *data = PepObject_GetTypeData(obType, meta); return reinterpret_cast<SbkObjectTypePrivate *>(data); } if (type == SOTP_key) @@ -1092,18 +1109,6 @@ void PepType_SOTP_delete(PyTypeObject *type) #endif // !defined(Py_LIMITED_API) && PY_VERSION_HEX >= 0x030C0000 -void _PepPostInit_SbkObject_Type(PyTypeObject *type) -{ - // Special init for SbkObject_Type. - // A normal initialization would recurse PepType_SOTP. - if (_PepRuntimeVersion() >= 0x030C00) { - auto *obType = reinterpret_cast<PyObject *>(type); - void *data = PepObject_GetTypeData(obType, Py_TYPE(obType)); - auto *sbkExt = reinterpret_cast<SbkObjectTypePrivate *>(data); - std::fill_n(reinterpret_cast<char *>(data), sizeof(*sbkExt), 0); - } -} - /* * SbkEnumType extender */ diff --git a/sources/shiboken6/libshiboken/pep384impl.h b/sources/shiboken6/libshiboken/pep384impl.h index 31fd65219..e1bf773b3 100644 --- a/sources/shiboken6/libshiboken/pep384impl.h +++ b/sources/shiboken6/libshiboken/pep384impl.h @@ -156,9 +156,6 @@ struct SbkObjectTypePrivate; LIBSHIBOKEN_API SbkObjectTypePrivate *PepType_SOTP(PyTypeObject *type); LIBSHIBOKEN_API void PepType_SOTP_delete(PyTypeObject *type); -// PYSIDE-2230: SbkObjectType needs a special init -LIBSHIBOKEN_API void _PepPostInit_SbkObject_Type(PyTypeObject *type); - struct SbkEnumType; struct SbkEnumTypePrivate; @@ -191,6 +188,13 @@ LIBSHIBOKEN_API int Pep_GetFlag(const char *name); LIBSHIBOKEN_API int Pep_GetVerboseFlag(void); #endif +// pyerrors.h +#if defined(Py_LIMITED_API) || PY_VERSION_HEX < 0x030C0000 +LIBSHIBOKEN_API PyObject *PepErr_GetRaisedException(); +#else +# define PepErr_GetRaisedException PyErr_GetRaisedException +#endif + /***************************************************************************** * * RESOLVED: unicodeobject.h diff --git a/sources/shiboken6/libshiboken/sbkcontainer.h b/sources/shiboken6/libshiboken/sbkcontainer.h index 240c772a9..8ad5aadc6 100644 --- a/sources/shiboken6/libshiboken/sbkcontainer.h +++ b/sources/shiboken6/libshiboken/sbkcontainer.h @@ -74,10 +74,9 @@ public: static PyObject *tpNewInvalid(PyTypeObject * /* subtype */, PyObject * /* args */, PyObject * /* kwds */) { - PyErr_Format(PyExc_NotImplementedError, + return PyErr_Format(PyExc_NotImplementedError, "Opaque containers of type '%s' cannot be instantiated.", typeid(SequenceContainer).name()); - return nullptr; } static int tpInit(PyObject * /* self */, PyObject * /* args */, PyObject * /* kwds */) @@ -105,10 +104,8 @@ public: static PyObject *sqGetItem(PyObject *self, Py_ssize_t i) { auto *d = get(self); - if (i < 0 || i >= Py_ssize_t(d->m_list->size())) { - PyErr_SetString(PyExc_IndexError, "index out of bounds"); - return nullptr; - } + if (i < 0 || i >= Py_ssize_t(d->m_list->size())) + return PyErr_Format(PyExc_IndexError, "index out of bounds"); auto it = std::cbegin(*d->m_list); std::advance(it, i); return ShibokenContainerValueConverter<value_type>::convertValueToPython(*it); @@ -133,14 +130,10 @@ public: static PyObject *push_back(PyObject *self, PyObject *pyArg) { auto *d = get(self); - if (!ShibokenContainerValueConverter<value_type>::checkValue(pyArg)) { - PyErr_SetString(PyExc_TypeError, "wrong type passed to append."); - return nullptr; - } - if (d->m_const) { - PyErr_SetString(PyExc_TypeError, msgModifyConstContainer); - return nullptr; - } + if (!ShibokenContainerValueConverter<value_type>::checkValue(pyArg)) + return PyErr_Format(PyExc_TypeError, "wrong type passed to append."); + if (d->m_const) + return PyErr_Format(PyExc_TypeError, msgModifyConstContainer); OptionalValue value = ShibokenContainerValueConverter<value_type>::convertValueToCpp(pyArg); if (!value.has_value()) @@ -152,14 +145,10 @@ public: static PyObject *push_front(PyObject *self, PyObject *pyArg) { auto *d = get(self); - if (!ShibokenContainerValueConverter<value_type>::checkValue(pyArg)) { - PyErr_SetString(PyExc_TypeError, "wrong type passed to append."); - return nullptr; - } - if (d->m_const) { - PyErr_SetString(PyExc_TypeError, msgModifyConstContainer); - return nullptr; - } + if (!ShibokenContainerValueConverter<value_type>::checkValue(pyArg)) + return PyErr_Format(PyExc_TypeError, "wrong type passed to append."); + if (d->m_const) + return PyErr_Format(PyExc_TypeError, msgModifyConstContainer); OptionalValue value = ShibokenContainerValueConverter<value_type>::convertValueToCpp(pyArg); if (!value.has_value()) @@ -171,10 +160,8 @@ public: static PyObject *clear(PyObject *self) { auto *d = get(self); - if (d->m_const) { - PyErr_SetString(PyExc_TypeError, msgModifyConstContainer); - return nullptr; - } + if (d->m_const) + return PyErr_Format(PyExc_TypeError, msgModifyConstContainer); d->m_list->clear(); Py_RETURN_NONE; @@ -183,10 +170,8 @@ public: static PyObject *pop_back(PyObject *self) { auto *d = get(self); - if (d->m_const) { - PyErr_SetString(PyExc_TypeError, msgModifyConstContainer); - return nullptr; - } + if (d->m_const) + return PyErr_Format(PyExc_TypeError, msgModifyConstContainer); d->m_list->pop_back(); Py_RETURN_NONE; @@ -195,10 +180,8 @@ public: static PyObject *pop_front(PyObject *self) { auto *d = get(self); - if (d->m_const) { - PyErr_SetString(PyExc_TypeError, msgModifyConstContainer); - return nullptr; - } + if (d->m_const) + return PyErr_Format(PyExc_TypeError, msgModifyConstContainer); d->m_list->pop_front(); Py_RETURN_NONE; @@ -208,21 +191,16 @@ public: static PyObject *reserve(PyObject *self, PyObject *pyArg) { auto *d = get(self); - if (PyLong_Check(pyArg) == 0) { - PyErr_SetString(PyExc_TypeError, "wrong type passed to reserve()."); - return nullptr; - } - if (d->m_const) { - PyErr_SetString(PyExc_TypeError, msgModifyConstContainer); - return nullptr; - } + if (PyLong_Check(pyArg) == 0) + return PyErr_Format(PyExc_TypeError, "wrong type passed to reserve()."); + if (d->m_const) + return PyErr_Format(PyExc_TypeError, msgModifyConstContainer); if constexpr (ShibokenContainerHasReserve<SequenceContainer>::value) { const Py_ssize_t size = PyLong_AsSsize_t(pyArg); d->m_list->reserve(size); } else { - PyErr_SetString(PyExc_TypeError, "Container does not support reserve()."); - return nullptr; + return PyErr_Format(PyExc_TypeError, "Container does not support reserve()."); } Py_RETURN_NONE; diff --git a/sources/shiboken6/libshiboken/sbkconverter.cpp b/sources/shiboken6/libshiboken/sbkconverter.cpp index 358827aa8..22dc89a70 100644 --- a/sources/shiboken6/libshiboken/sbkconverter.cpp +++ b/sources/shiboken6/libshiboken/sbkconverter.cpp @@ -12,7 +12,11 @@ #include "voidptr.h" #include <string> +#include <cstring> +#include <iostream> #include <unordered_map> +#include <map> +#include <set> static SbkConverter **PrimitiveTypeConverters; @@ -72,6 +76,99 @@ void init() initArrayConverters(); } +static void dumpPyTypeObject(std::ostream &str, PyTypeObject *t) +{ + str << "\nPython type "; + if (t == nullptr) { + str << "<None>"; + return; + } + str << '"' << t->tp_name << '"'; + if (t->tp_base != nullptr && t->tp_base != &PyBaseObject_Type) + str << '(' << t->tp_base->tp_name << ')'; +} + +static void dumpSbkConverter(std::ostream &str, const SbkConverter *c) +{ + str << "SbkConverter " << static_cast<const void *>(c) << ": "; + if (c->pointerToPython != nullptr) + str << ", C++ pointer->Python"; + if (c->copyToPython != nullptr) + str << ", copy->Python"; + if (c->toCppPointerConversion.second != nullptr) + str << ", Python->C++ pointer"; + if (!c->toCppConversions.empty()) + str << ", " << c->toCppConversions.size() << " Python->C++ conversions"; +} + +// Less than operator for a PyTypeObject for dumping the converter map +static bool pyTypeObjectLessThan(const PyTypeObject *t1, const PyTypeObject *t2) +{ + const bool isNull1 = t1 == nullptr; + const bool isNull2 = t2 == nullptr; + if (isNull1 || isNull2) + return isNull1 && !isNull2; + // Internal types (lower case) first + const bool isInternal1 = std::islower(t1->tp_name[0]); + const bool isInternal2 = std::islower(t2->tp_name[0]); + if (isInternal1 != isInternal2) + return !isInternal2; + return std::strcmp(t1->tp_name, t2->tp_name) < 0; +} + +void dumpConverters() +{ + struct PyTypeObjectLess { + + bool operator()(const PyTypeObject *t1, const PyTypeObject *t2) const { + return pyTypeObjectLessThan(t1, t2); + } + }; + + using StringSet = std::set<std::string>; + using SbkConverterNamesMap = std::unordered_map<SbkConverter *, StringSet>; + using PyTypeObjectConverterMap = std::map<PyTypeObject *, SbkConverterNamesMap, + PyTypeObjectLess>; + + auto &str = std::cerr; + + // Sort the entries by the associated PyTypeObjects and converters + PyTypeObjectConverterMap pyTypeObjectConverterMap; + for (const auto &converter : converters) { + auto *sbkConverter = converter.second; + auto *typeObject = sbkConverter->pythonType; + auto typeIt = pyTypeObjectConverterMap.find(typeObject); + if (typeIt == pyTypeObjectConverterMap.end()) + typeIt = pyTypeObjectConverterMap.insert(std::make_pair(typeObject, + SbkConverterNamesMap{})).first; + SbkConverterNamesMap &sbkConverterMap = typeIt->second; + auto convIt = sbkConverterMap.find(sbkConverter); + if (convIt == sbkConverterMap.end()) + convIt = sbkConverterMap.insert(std::make_pair(sbkConverter, + StringSet{})).first; + convIt->second.insert(converter.first); + } + + for (const auto &tc : pyTypeObjectConverterMap) { + dumpPyTypeObject(str, tc.first); + str << ", " << tc.second.size() << " converter(s):\n"; + for (const auto &cn : tc.second) { + str << " "; + dumpSbkConverter(str, cn.first); + str << ", " << cn.second.size() << " alias(es):"; + int i = 0; + for (const auto &name : cn.second) { + if ((i++ % 5) == 0) + str << "\n "; + str << " \"" << name << '"'; + } + str << '\n'; + } + } + + str << '\n'; +} + SbkConverter *createConverterObject(PyTypeObject *type, PythonToCppFunc toCppPointerConvFunc, IsConvertibleToCppFunc toCppPointerCheckFunc, @@ -704,14 +801,7 @@ PyTypeObject *getPythonTypeObject(const SbkConverter *converter) PyTypeObject *getPythonTypeObject(const char *typeName) { - auto *type = getPythonTypeObject(getConverter(typeName)); - if (type == nullptr) { - // PYSIDE-2404: Did not find the name. Load the lazy classes - // which have this name and try again. - Shiboken::Module::loadLazyClassesWithName(getRealTypeName(typeName).c_str()); - type = getPythonTypeObject(getConverter(typeName)); - } - return type; + return getPythonTypeObject(getConverter(typeName)); } bool pythonTypeIsValueType(const SbkConverter *converter) diff --git a/sources/shiboken6/libshiboken/sbkconverter_p.h b/sources/shiboken6/libshiboken/sbkconverter_p.h index c886c9b9f..5d8c1977a 100644 --- a/sources/shiboken6/libshiboken/sbkconverter_p.h +++ b/sources/shiboken6/libshiboken/sbkconverter_p.h @@ -531,6 +531,9 @@ SbkConverter *createConverterObject(PyTypeObject *type, IsConvertibleToCppFunc toCppPointerCheckFunc, CppToPythonFunc pointerToPythonFunc, CppToPythonFunc copyToPythonFunc); + +LIBSHIBOKEN_API void dumpConverters(); + } // namespace Shiboken::Conversions #endif // SBK_CONVERTER_P_H diff --git a/sources/shiboken6/libshiboken/sbkcpptonumpy.cpp b/sources/shiboken6/libshiboken/sbkcpptonumpy.cpp index 44e900f01..7637efa70 100644 --- a/sources/shiboken6/libshiboken/sbkcpptonumpy.cpp +++ b/sources/shiboken6/libshiboken/sbkcpptonumpy.cpp @@ -49,17 +49,17 @@ PyObject *createByteArray1(Py_ssize_t, const uint8_t *) PyObject *createDoubleArray1(Py_ssize_t, const double *) { - return Py_None; + Py_RETURN_NONE; } PyObject *createFloatArray1(Py_ssize_t, const float *) { - return Py_None; + Py_RETURN_NONE; } PyObject *createIntArray1(Py_ssize_t, const int *) { - return Py_None; + Py_RETURN_NONE; } #endif // !HAVE_NUMPY diff --git a/sources/shiboken6/libshiboken/sbkfeature_base.cpp b/sources/shiboken6/libshiboken/sbkfeature_base.cpp index caf3517e5..f31b8f4f7 100644 --- a/sources/shiboken6/libshiboken/sbkfeature_base.cpp +++ b/sources/shiboken6/libshiboken/sbkfeature_base.cpp @@ -65,14 +65,24 @@ void disassembleFrame(const char *marker) static PyObject *dismodule = PyImport_ImportModule("dis"); static PyObject *disco = PyObject_GetAttrString(dismodule, "disco"); static PyObject *const _f_lasti = Shiboken::String::createStaticString("f_lasti"); + static PyObject *const _f_lineno = Shiboken::String::createStaticString("f_lineno"); static PyObject *const _f_code = Shiboken::String::createStaticString("f_code"); - auto *frame = reinterpret_cast<PyObject *>(PyEval_GetFrame()); - AutoDecRef f_lasti(PyObject_GetAttr(frame, _f_lasti)); - AutoDecRef f_code(PyObject_GetAttr(frame, _f_code)); + static PyObject *const _co_filename = Shiboken::String::createStaticString("co_filename"); AutoDecRef ignore{}; - fprintf(stdout, "\n%s BEGIN\n", marker); - ignore.reset(PyObject_CallFunctionObjArgs(disco, f_code.object(), f_lasti.object(), nullptr)); - fprintf(stdout, "%s END\n\n", marker); + auto *frame = reinterpret_cast<PyObject *>(PyEval_GetFrame()); + if (frame == nullptr) { + fprintf(stdout, "\n%s BEGIN no frame END\n\n", marker); + } else { + AutoDecRef f_lasti(PyObject_GetAttr(frame, _f_lasti)); + AutoDecRef f_lineno(PyObject_GetAttr(frame, _f_lineno)); + AutoDecRef f_code(PyObject_GetAttr(frame, _f_code)); + AutoDecRef co_filename(PyObject_GetAttr(f_code, _co_filename)); + long line = PyLong_AsLong(f_lineno); + const char *fname = String::toCString(co_filename); + fprintf(stdout, "\n%s BEGIN line=%ld %s\n", marker, line, fname); + ignore.reset(PyObject_CallFunctionObjArgs(disco, f_code.object(), f_lasti.object(), nullptr)); + fprintf(stdout, "%s END line=%ld %s\n\n", marker, line, fname); + } #if PY_VERSION_HEX >= 0x030C0000 && !Py_LIMITED_API if (error_type) PyErr_DisplayException(error_value); diff --git a/sources/shiboken6/libshiboken/sbkmodule.cpp b/sources/shiboken6/libshiboken/sbkmodule.cpp index b705e8380..5df34ef26 100644 --- a/sources/shiboken6/libshiboken/sbkmodule.cpp +++ b/sources/shiboken6/libshiboken/sbkmodule.cpp @@ -10,18 +10,23 @@ #include <unordered_map> #include <unordered_set> +#include <vector> #include <cstring> +/// This hash maps module objects to arrays of converters. +using ModuleConvertersMap = std::unordered_map<PyObject *, SbkConverter **> ; + /// This hash maps module objects to arrays of Python types. using ModuleTypesMap = std::unordered_map<PyObject *, Shiboken::Module::TypeInitStruct *> ; -/// This hash maps module objects to arrays of converters. -using ModuleConvertersMap = std::unordered_map<PyObject *, SbkConverter **>; +struct TypeCreationStruct +{ + Shiboken::Module::TypeCreationFunction func; + std::vector<std::string> subtypeNames; +}; -/// This hash maps type names to type creation functions. -using TypeCreationFunctionModulePair = - std::pair<Shiboken::Module::TypeCreationFunction, PyObject *>; -using NameToTypeFunctionMap = std::unordered_map<std::string, TypeCreationFunctionModulePair>; +/// This hash maps type names to type creation structs. +using NameToTypeFunctionMap = std::unordered_map<std::string, TypeCreationStruct> ; /// This hash maps module objects to maps of names to functions. using ModuleToFuncsMap = std::unordered_map<PyObject *, NameToTypeFunctionMap> ; @@ -50,7 +55,7 @@ LIBSHIBOKEN_API PyTypeObject *get(TypeInitStruct &typeStruct) // As soon as types[index] gets filled, we can stop. std::string_view names(typeStruct.fullName); - bool usePySide = names.substr(0, 8) == std::string("PySide6."); + const bool usePySide = names.compare(0, 8, "PySide6.") == 0; auto dotPos = usePySide ? names.find('.', 8) : names.find('.'); auto startPos = dotPos + 1; AutoDecRef modName(String::fromCppStringView(names.substr(0, dotPos))); @@ -74,6 +79,39 @@ LIBSHIBOKEN_API PyTypeObject *get(TypeInitStruct &typeStruct) return typeStruct.type; } +static void incarnateHelper(PyObject *module, const std::string_view names, + const NameToTypeFunctionMap &nameToFunc) +{ + auto dotPos = names.find('.'); + std::string::size_type startPos = 0; + auto *modOrType{module}; + while (dotPos != std::string::npos) { + auto typeName = names.substr(startPos, dotPos - startPos); + AutoDecRef obTypeName(String::fromCppStringView(typeName)); + modOrType = PyObject_GetAttr(modOrType, obTypeName); + startPos = dotPos + 1; + dotPos = names.find('.', startPos); + } + // now we have the type to create. + auto funcIter = nameToFunc.find(std::string(names)); + // - call this function that returns a PyTypeObject + auto tcStruct = funcIter->second; + auto initFunc = tcStruct.func; + PyTypeObject *type = initFunc(modOrType); + auto name = names.substr(startPos); + PyObject_SetAttrString(modOrType, name.data(), reinterpret_cast<PyObject *>(type)); +} + +static void incarnateSubtypes(PyObject *module, + const std::vector<std::string> &nameList, + NameToTypeFunctionMap &nameToFunc) +{ + for (auto const & tableIter : nameList) { + std::string_view names(tableIter); + incarnateHelper(module, names, nameToFunc); + } +} + static PyTypeObject *incarnateType(PyObject *module, const char *name, NameToTypeFunctionMap &nameToFunc) { @@ -85,13 +123,15 @@ static PyTypeObject *incarnateType(PyObject *module, const char *name, return nullptr; } // - call this function that returns a PyTypeObject - auto pair = funcIter->second; - auto initFunc = pair.first; - auto *modOrType = pair.second; + auto tcStruct = funcIter->second; + auto initFunc = tcStruct.func; + auto *modOrType{module}; // PYSIDE-2404: Make sure that no switching happens during type creation. auto saveFeature = initSelectableFeature(nullptr); PyTypeObject *type = initFunc(modOrType); + if (!tcStruct.subtypeNames.empty()) + incarnateSubtypes(module, tcStruct.subtypeNames, nameToFunc); initSelectableFeature(saveFeature); // - assign this object to the name in the module @@ -164,7 +204,7 @@ static PyObject *PyModule_lazyGetAttro(PyObject *module, PyObject *name) // - locate the name and retrieve the generating function const char *attrNameStr = Shiboken::String::toCString(name); auto &nameToFunc = tableIter->second; - // - create the real type (incarnateType checks this) + // - create the real type and handle subtypes auto *type = incarnateType(module, attrNameStr, nameToFunc); auto *ret = reinterpret_cast<PyObject *>(type); // - if attribute does really not exist use the original @@ -172,7 +212,6 @@ static PyObject *PyModule_lazyGetAttro(PyObject *module, PyObject *name) PyErr_Clear(); return origModuleGetattro(module, name); } - return ret; } @@ -292,25 +331,12 @@ static bool shouldLazyLoad(PyObject *module) return std::strncmp(modName, "PySide6.", 8) == 0; } -void AddTypeCreationFunction(PyObject *module, - const char *name, - TypeCreationFunction func) +void checkIfShouldLoadImmediately(PyObject *module, const std::string &name, + const NameToTypeFunctionMap &nameToFunc) { static const char *flag = getenv("PYSIDE6_OPTION_LAZY"); static const int value = flag != nullptr ? std::atoi(flag) : 1; - // - locate the module in the moduleTofuncs mapping - auto tableIter = moduleToFuncs.find(module); - assert(tableIter != moduleToFuncs.end()); - // - Assign the name/generating function pair. - auto &nameToFunc = tableIter->second; - TypeCreationFunctionModulePair pair{func, module}; - auto nit = nameToFunc.find(name); - if (nit == nameToFunc.end()) - nameToFunc.insert(std::make_pair(name, pair)); - else - nit->second = pair; - // PYSIDE-2404: Lazy Loading // // Options: @@ -319,56 +345,56 @@ void AddTypeCreationFunction(PyObject *module, // 3 - lazy loading for any module. // // By default we lazy load all known modules (option = 1). - if (value == 0 // completely disabled || canNotLazyLoad(module) // for some reason we cannot lazy load || (value == 1 && !shouldLazyLoad(module)) // not a known module ) { - PyTypeObject *type = func(module); - PyModule_AddObject(module, name, reinterpret_cast<PyObject *>(type)); // steals reference + incarnateHelper(module, name, nameToFunc); } } void AddTypeCreationFunction(PyObject *module, const char *name, - TypeCreationFunction func, - const char *containerName) + TypeCreationFunction func) { - // This version could be delayed as well, but for the few cases - // we simply fetch the container type and insert directly. - AutoDecRef obContainerType(PyObject_GetAttrString(module, containerName)); - PyTypeObject *type = func(obContainerType); - PyObject_SetAttrString(obContainerType, name, reinterpret_cast<PyObject *>(type)); // steals reference -} + // - locate the module in the moduleTofuncs mapping + auto tableIter = moduleToFuncs.find(module); + assert(tableIter != moduleToFuncs.end()); + // - Assign the name/generating function tcStruct. + auto &nameToFunc = tableIter->second; + TypeCreationStruct tcStruct{func, {}}; + auto nit = nameToFunc.find(name); + if (nit == nameToFunc.end()) + nameToFunc.insert(std::make_pair(name, tcStruct)); + else + nit->second = tcStruct; -void AddTypeCreationFunction(PyObject *module, - const char *name, - TypeCreationFunction func, - const char *outerContainerName, - const char *innerContainerName) -{ - // This version has even more indirection. It is very rare, and - // we handle it directly. - AutoDecRef obOuterType(PyObject_GetAttrString(module, outerContainerName)); - AutoDecRef obInnerType(PyObject_GetAttrString(obOuterType, innerContainerName)); - PyTypeObject *type = func(obInnerType); - PyObject_SetAttrString(obInnerType, name, reinterpret_cast<PyObject *>(type)); // steals reference + checkIfShouldLoadImmediately(module, name, nameToFunc); } void AddTypeCreationFunction(PyObject *module, - const char *name, + const char *containerName, TypeCreationFunction func, - const char *containerName3, - const char *containerName2, - const char *containerName) + const char *namePath) { - // This version has even mode indirection. It is very rare, and - // we handle it directly. - AutoDecRef obContainerType3(PyObject_GetAttrString(module, containerName3)); - AutoDecRef obContainerType2(PyObject_GetAttrString(obContainerType3, containerName2)); - AutoDecRef obContainerType(PyObject_GetAttrString(obContainerType2, containerName)); - PyTypeObject *type = func(obContainerType); - PyObject_SetAttrString(obContainerType, name, reinterpret_cast<PyObject *>(type)); // steals reference + // - locate the module in the moduleTofuncs mapping + auto tableIter = moduleToFuncs.find(module); + assert(tableIter != moduleToFuncs.end()); + // - Assign the name/generating function tcStruct. + auto &nameToFunc = tableIter->second; + auto nit = nameToFunc.find(containerName); + + // - insert namePath into the subtype vector of the main type. + nit->second.subtypeNames.push_back(namePath); + // - insert it also as its own entry. + nit = nameToFunc.find(namePath); + TypeCreationStruct tcStruct{func, {}}; + if (nit == nameToFunc.end()) + nameToFunc.insert(std::make_pair(namePath, tcStruct)); + else + nit->second = tcStruct; + + checkIfShouldLoadImmediately(module, namePath, nameToFunc); } PyObject *import(const char *moduleName) diff --git a/sources/shiboken6/libshiboken/sbknumpy.cpp b/sources/shiboken6/libshiboken/sbknumpy.cpp index 2e1c64d73..b6422e73f 100644 --- a/sources/shiboken6/libshiboken/sbknumpy.cpp +++ b/sources/shiboken6/libshiboken/sbknumpy.cpp @@ -29,10 +29,8 @@ static void initNumPy() // Expanded from macro "import_array" in __multiarray_api.h // Make sure to read about the magic defines PY_ARRAY_UNIQUE_SYMBOL etc., // when changing this or spreading the code over several source files. - if (_import_array() < 0) { + if (_import_array() < 0) PyErr_Print(); - PyErr_Clear(); - } } #endif // HAVE_NUMPY diff --git a/sources/shiboken6/libshiboken/voidptr.cpp b/sources/shiboken6/libshiboken/voidptr.cpp index 7045b08b1..8bb3f6ac8 100644 --- a/sources/shiboken6/libshiboken/voidptr.cpp +++ b/sources/shiboken6/libshiboken/voidptr.cpp @@ -156,10 +156,9 @@ PyObject *SbkVoidPtrObject_int(PyObject *v) PyObject *toBytes(PyObject *self, PyObject * /* args */) { auto *sbkObject = reinterpret_cast<SbkVoidPtrObject *>(self); - if (sbkObject->size < 0) { - PyErr_SetString(PyExc_IndexError, "VoidPtr does not have a size set."); - return nullptr; - } + if (sbkObject->size < 0) + return PyErr_Format(PyExc_IndexError, "VoidPtr does not have a size set."); + PyObject *bytes = PyBytes_FromStringAndSize(reinterpret_cast<const char *>(sbkObject->cptr), sbkObject->size); Py_XINCREF(bytes); diff --git a/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/lib/enum_sig.py b/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/lib/enum_sig.py index 11bda9779..5650e2bc1 100644 --- a/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/lib/enum_sig.py +++ b/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/lib/enum_sig.py @@ -167,7 +167,7 @@ class ExactEnumerator(object): # find out how many functions create a signature sigs = list(_ for _ in functions if get_sig(_[1])) self.fmt.have_body = bool(subclasses or sigs or properties or enums or # noqa W:504 - init_signature or signals) + init_signature or signals or attributes) with self.fmt.klass(class_name, class_str): self.fmt.level += 1 diff --git a/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/lib/pyi_generator.py b/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/lib/pyi_generator.py index 644f49ff6..ce12dd6c8 100644 --- a/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/lib/pyi_generator.py +++ b/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/lib/pyi_generator.py @@ -179,6 +179,7 @@ FROM_IMPORTS = [ (None, ["builtins"]), (None, ["os"]), (None, ["enum"]), + ("collections.abc", ["Iterable"]), ("typing", sorted(typing.__all__)), ("PySide6.QtCore", ["PyClassProperty", "Signal", "SignalInstance"]), ("shiboken6", ["Shiboken"]), @@ -199,6 +200,10 @@ def filter_from_imports(from_struct, text): if (f"class {each}(") not in text: if re.search(rf"(\b|@){each}\b([^\s\(:]|\n)", text): lis.append(each) + # Search if a type is present in the return statement + # of function declarations: '... -> here:' + if re.search(rf"->.*{each}.*:", text): + lis.append(each) if not lis: nfs.pop() return nfs @@ -282,7 +287,7 @@ def generate_pyi(import_name, outpath, options): wr.print(f"from {mod} import {import_args}") wr.print() wr.print() - wr.print("NoneType = type(None)") + wr.print("NoneType: TypeAlias = type[None]") wr.print() else: wr.print(line) diff --git a/sources/shiboken6/shibokenmodule/shibokenmodule.cpp b/sources/shiboken6/shibokenmodule/shibokenmodule.cpp index 6feca9ea8..5c6219885 100644 --- a/sources/shiboken6/shibokenmodule/shibokenmodule.cpp +++ b/sources/shiboken6/shibokenmodule/shibokenmodule.cpp @@ -91,6 +91,19 @@ for (auto *o : setAll) { return listAll; // @snippet getallvalidwrappers +// @snippet dumptypegraph +const bool ok = Shiboken::BindingManager::instance().dumpTypeGraph(%1); +%PYARG_0 = %CONVERTTOPYTHON[bool](ok); +// @snippet dumptypegraph + +// @snippet dumpwrappermap +Shiboken::BindingManager::instance().dumpWrapperMap(); +// @snippet dumpwrappermap + +// @snippet dumpconverters +Shiboken::Conversions::dumpConverters(); +// @snippet dumpconverters + // @snippet init // Add __version__ and __version_info__ attributes to the module PyObject* version = PyTuple_New(5); diff --git a/sources/shiboken6/shibokenmodule/typesystem_shiboken.xml b/sources/shiboken6/shibokenmodule/typesystem_shiboken.xml index 2288ca7a4..acb522ecc 100644 --- a/sources/shiboken6/shibokenmodule/typesystem_shiboken.xml +++ b/sources/shiboken6/shibokenmodule/typesystem_shiboken.xml @@ -49,9 +49,22 @@ <inject-code file="shibokenmodule.cpp" snippet="getallvalidwrappers"/> </add-function> + <add-function signature="dumpTypeGraph(const char *@fileName@)" return-type="bool"> + <inject-code file="shibokenmodule.cpp" snippet="dumptypegraph"/> + </add-function> + + <add-function signature="dumpWrapperMap()"> + <inject-code file="shibokenmodule.cpp" snippet="dumpwrappermap"/> + </add-function> + + <add-function signature="dumpConverters()"> + <inject-code file="shibokenmodule.cpp" snippet="dumpconverters"/> + </add-function> + <extra-includes> <include file-name="sbkversion.h" location="local"/> <include file-name="voidptr.h" location="local"/> + <include file-name="sbkconverter_p.h" location="local"/> </extra-includes> <inject-code position="end" file="shibokenmodule.cpp" snippet="init"/> </typesystem> diff --git a/sources/shiboken6/tests/libother/othermultiplederived.h b/sources/shiboken6/tests/libother/othermultiplederived.h index a8e265388..cd9910687 100644 --- a/sources/shiboken6/tests/libother/othermultiplederived.h +++ b/sources/shiboken6/tests/libother/othermultiplederived.h @@ -10,7 +10,7 @@ class ObjectType; -class LIBOTHER_API OtherMultipleDerived : public MDerived1 +class LIBOTHER_API OtherMultipleDerived : public OtherBase, public MDerived1 { public: // this will use CppCopier from other module (bug#142) diff --git a/sources/shiboken6/tests/libsample/abstract.cpp b/sources/shiboken6/tests/libsample/abstract.cpp index 648cedcd0..0d67d8630 100644 --- a/sources/shiboken6/tests/libsample/abstract.cpp +++ b/sources/shiboken6/tests/libsample/abstract.cpp @@ -49,6 +49,18 @@ void Abstract::show(PrintFormat format) const std::cout << '>'; } +void Abstract::virtualWithOutParameter(int &x) const +{ + x = 42; +} + +int Abstract::callVirtualWithOutParameter() const +{ + int x; + virtualWithOutParameter(x); + return x; +} + void Abstract::callVirtualGettingEnum(PrintFormat p) { virtualGettingAEnum(p); diff --git a/sources/shiboken6/tests/libsample/abstract.h b/sources/shiboken6/tests/libsample/abstract.h index 1e62a9c51..4c1b98d90 100644 --- a/sources/shiboken6/tests/libsample/abstract.h +++ b/sources/shiboken6/tests/libsample/abstract.h @@ -74,6 +74,9 @@ public: virtual void hideFunction(HideType *arg) = 0; + virtual void virtualWithOutParameter(int &x) const; + int callVirtualWithOutParameter() const; + protected: virtual const char *className() const { return "Abstract"; } diff --git a/sources/shiboken6/tests/otherbinding/typediscovery_test.py b/sources/shiboken6/tests/otherbinding/typediscovery_test.py index 791d3bdce..39dc5cf0f 100644 --- a/sources/shiboken6/tests/otherbinding/typediscovery_test.py +++ b/sources/shiboken6/tests/otherbinding/typediscovery_test.py @@ -13,7 +13,8 @@ sys.path.append(os.fspath(Path(__file__).resolve().parents[1])) from shiboken_paths import init_paths init_paths() -from sample import Abstract, Base1, Derived +from sample import (Abstract, Base1, Derived, + MDerived1, SonOfMDerived1, MDerived3) from other import OtherMultipleDerived @@ -32,14 +33,18 @@ class TypeDiscoveryTest(unittest.TestCase): def testMultipleInheritance(self): obj = OtherMultipleDerived.createObject("Base1") self.assertEqual(type(obj), Base1) - # PYSIDE-868: In case of multiple inheritance, a factory - # function will return the base class wrapper. + # PYSIDE-868: In case of single line direct inheritance, + # a factory function will return the class wrapper + # of the derived class. obj = OtherMultipleDerived.createObject("MDerived1") - self.assertEqual(type(obj), Base1) + self.assertEqual(type(obj), MDerived1) obj = OtherMultipleDerived.createObject("SonOfMDerived1") - self.assertEqual(type(obj), Base1) + self.assertEqual(type(obj), SonOfMDerived1) obj = OtherMultipleDerived.createObject("MDerived3") - self.assertEqual(type(obj), Base1) + self.assertEqual(type(obj), MDerived3) + # PYSIDE-868: OtherMultipleDerived inherits + # OtherBase, Base1. In this case, a factory + # function will return the base class wrapper. obj = OtherMultipleDerived.createObject("OtherMultipleDerived") self.assertEqual(type(obj), Base1) diff --git a/sources/shiboken6/tests/samplebinding/derived_test.py b/sources/shiboken6/tests/samplebinding/derived_test.py index 418c990d3..346f29136 100644 --- a/sources/shiboken6/tests/samplebinding/derived_test.py +++ b/sources/shiboken6/tests/samplebinding/derived_test.py @@ -33,6 +33,15 @@ class Deviant(Derived): return 'Deviant' +class ImplementVirtualWithOutParameter(Derived): + def __init__(self, value): + super().__init__() + self._value = value + + def virtualWithOutParameter(self): + return self._value + + class DerivedTest(unittest.TestCase): '''Test case for Derived class''' @@ -122,6 +131,13 @@ class DerivedTest(unittest.TestCase): obj = DerivedUsingCt(42) self.assertEqual(obj.value(), 42) + def testVirtualWithOutParameter(self): + d = Derived() + self.assertEqual(d.callVirtualWithOutParameter(), 42) + + d = ImplementVirtualWithOutParameter(1) + self.assertEqual(d.callVirtualWithOutParameter(), 1) + if __name__ == '__main__': unittest.main() diff --git a/sources/shiboken6/tests/samplebinding/typesystem_sample.xml b/sources/shiboken6/tests/samplebinding/typesystem_sample.xml index 36134e649..e315e599e 100644 --- a/sources/shiboken6/tests/samplebinding/typesystem_sample.xml +++ b/sources/shiboken6/tests/samplebinding/typesystem_sample.xml @@ -571,6 +571,14 @@ <modify-function signature="hideFunction(HideType*)" remove="all"/> <modify-field name="toBeRenamedField" rename="renamedField"/> <modify-field name="readOnlyField" write="false"/> + <modify-function signature="virtualWithOutParameter(int&)const"> + <inject-code class="shell" position="override"> + x = virtualWithOutParameterPyOverride(gil, pyOverride.object()); + return; + </inject-code> + </modify-function> + <add-function signature="virtualWithOutParameterPyOverride()" + return-type="int" python-override="true"/> </object-type> <object-type name="Derived" polymorphic-id-expression="%1->type() == Derived::TpDerived"> diff --git a/tools/scanqtclasses.py b/tools/scanqtclasses.py new file mode 100644 index 000000000..0f87d80bd --- /dev/null +++ b/tools/scanqtclasses.py @@ -0,0 +1,122 @@ +# Copyright (C) 2024 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +from pathlib import Path +import os +import re +import subprocess +import sys + +"""Scan the Qt C++ headers per module for classes that should be present + in the matching type system and print the missing classes.""" + + +VALUE_TYPE = re.compile(r'^\s*<value-type name="([^"]+)"') + + +OBJECT_TYPE = re.compile(r'^\s*<object-type name="([^"]+)"') + + +def query_qtpaths(keyword): + """Query qtpaths for a keyword.""" + query_cmd = ["qtpaths", "-query", keyword] + output = subprocess.check_output(query_cmd, stderr=subprocess.STDOUT, + universal_newlines=True) + return output.strip() + + +def is_class_exluded(name): + """Check for excluded classes that do not make sense in a typesystem.""" + if len(name) < 2: + return True + if "Iterator" in name or "iterator" in name: + return True + if name.startswith("If") or name.startswith("Is") or name.startswith("When"): + return True + if name[:1].islower(): + return True + if name.startswith("QOpenGLFunctions") and name.endswith("Backend"): + return True + return False + + +def class_from_header_line(line): + """Extract a class name from a C++ header line.""" + def _is_macro(token): + return "EXPORT" in token or "API" in token + + def _fix_class_name(name): + pos = name.find('<') # Some template specialization "class Name<TemplateParam>" + if pos > 0: + name = name[:pos] + if name.endswith(':'): + name = name[:-1] + return name + + if line.startswith('//') or line.endswith(';'): # comment/forward decl + return None + line = line.strip() + if not line.startswith("class ") and not line.startswith("struct "): + return None + tokens = line.split() + pos = 1 + while pos < len(tokens) and _is_macro(tokens[pos]): + pos += 1 + return _fix_class_name(tokens[pos]) if pos < len(tokens) else None + + +def classes_from_header(header): + """Extract classes from C++ header file.""" + result = [] + for line in header.read_text("utf-8").splitlines(): + name = class_from_header_line(line) + if name and not is_class_exluded(name): + result.append(name) + return sorted(result) + + +def classes_from_typesystem(typesystem): + """Extract classes from typesystem XML file.""" + result = [] + for line in typesystem.read_text("utf-8").splitlines(): + match = VALUE_TYPE.search(line) or OBJECT_TYPE.search(line) + if match: + result.append(match.group(1)) + return sorted(result) + + +def check_classes(qt_module_inc_dir, pyside_dir): + """Check classes of a module.""" + module_name = qt_module_inc_dir.name + sys.stderr.write(f"Checking {module_name} ") + cpp_classes = [] + typesystem_classes = [] + for header in qt_module_inc_dir.glob("q*.h"): + if not header.name.endswith("_p.h"): + cpp_classes.extend(classes_from_header(header)) + for typesystem in pyside_dir.glob("*.xml"): + typesystem_classes.extend(classes_from_typesystem(typesystem)) + + cpp_count = len(cpp_classes) + typesystem_count = len(typesystem_classes) + sys.stderr.write(f"found {cpp_count} C++ / {typesystem_count} typesystem classes") + if cpp_count <= typesystem_count: + sys.stderr.write(" ok\n") + else: + sys.stderr.write(f", {cpp_count-typesystem_count} missing\n") + for cpp_class in cpp_classes: + if cpp_class not in typesystem_classes: + wrapper_name = cpp_class.lower() + "_wrapper.cpp" + print(f"{module_name}:{cpp_class}:{wrapper_name}") + + +if __name__ == '__main__': + qt_version = query_qtpaths("QT_VERSION") + qt_inc_dir = Path(query_qtpaths("QT_INSTALL_HEADERS")) + print(f"Qt {qt_version} at {os.fspath(qt_inc_dir.parent)}", file=sys.stderr) + + dir = Path(__file__).parents[1].resolve() + for module_dir in (dir / "sources" / "pyside6" / "PySide6").glob("Qt*"): + qt_module_inc_dir = qt_inc_dir / module_dir.name + if qt_module_inc_dir.is_dir(): + check_classes(qt_module_inc_dir, module_dir) diff --git a/tools/snippets_translate/module_classes.py b/tools/snippets_translate/module_classes.py index 4992e170b..df4c7557c 100644 --- a/tools/snippets_translate/module_classes.py +++ b/tools/snippets_translate/module_classes.py @@ -532,6 +532,7 @@ module_classes = { "QAccessibleEvent", "QAccessibleInterface", "QAccessibleObject", + "QAccessibleSelectionInterface", "QAccessibleStateChangeEvent", "QAccessibleTableCellInterface", "QAccessibleTableModelChangeEvent", |