Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--build_scripts/config.py2
-rw-r--r--build_scripts/wheel_files.py1
-rw-r--r--coin/instructions/common_environment.yaml12
-rw-r--r--doc/changelogs/changes-6.9.164
-rw-r--r--examples/graphs/2d/graphsaudio/GraphsAudio/Main.qml50
-rw-r--r--examples/graphs/2d/graphsaudio/GraphsAudio/qmldir2
-rw-r--r--examples/graphs/2d/graphsaudio/doc/graphsaudio.rst8
-rw-r--r--examples/graphs/2d/graphsaudio/doc/graphsaudio.webpbin0 -> 12908 bytes
-rw-r--r--examples/graphs/2d/graphsaudio/graphsaudio.pyproject3
-rw-r--r--examples/graphs/2d/graphsaudio/main.py80
-rw-r--r--examples/xml/dombookmarks/dombookmarks.py20
-rw-r--r--requirements-coin.txt4
-rw-r--r--requirements.txt10
-rw-r--r--sources/pyside-tools/project_lib/pyproject_toml.py90
-rw-r--r--sources/pyside-tools/pyside_tool.py27
-rw-r--r--sources/pyside-tools/requirements-android.txt1
-rw-r--r--sources/pyside6/PySide6/QtCore/glue/core_snippets.cpp92
-rw-r--r--sources/pyside6/PySide6/QtCore/glue/core_snippets_p.h10
-rw-r--r--sources/pyside6/PySide6/QtCore/typesystem_core_common.xml10
-rw-r--r--sources/pyside6/PySide6/QtWidgets/typesystem_widgets_common.xml7
-rw-r--r--sources/pyside6/PySide6/QtXml/typesystem_xml.xml4
-rw-r--r--sources/pyside6/PySide6/glue/qtcore.cpp57
-rw-r--r--sources/pyside6/libpyside/CMakeLists.txt2
-rw-r--r--sources/pyside6/libpyside/pysidemetafunction.cpp8
-rw-r--r--sources/pyside6/libpyside/pysideproperty.cpp25
-rw-r--r--sources/pyside6/libpyside/pysidevariantutils.cpp212
-rw-r--r--sources/pyside6/libpyside/pysidevariantutils.h37
-rw-r--r--sources/pyside6/libpyside/signalmanager.cpp71
-rw-r--r--sources/pyside6/libpyside/signalmanager.h8
-rw-r--r--sources/pyside6/tests/QtCore/deepcopy_test.py6
-rw-r--r--sources/pyside6/tests/QtCore/qobject_property_test.py34
-rw-r--r--sources/pyside6/tests/QtCore/repr_test.py6
-rw-r--r--sources/pyside6/tests/QtWidgets/CMakeLists.txt1
-rw-r--r--sources/pyside6/tests/QtWidgets/bug_433.py2
-rw-r--r--sources/pyside6/tests/QtWidgets/bug_919.py9
-rw-r--r--sources/pyside6/tests/QtWidgets/pyside3069.py51
-rw-r--r--sources/pyside6/tests/QtWidgets/qapp_issue_585.py10
-rw-r--r--sources/pyside6/tests/QtWidgets/qgraphicsobjectreimpl_test.py1
-rw-r--r--sources/pyside6/tests/QtWidgets/qmenu_test.py18
-rw-r--r--sources/pyside6/tests/QtXml/qdomdocument_test.py23
-rw-r--r--sources/pyside6/tests/tools/pyside6-android-deploy/test_pyside6_android_deploy.py2
-rw-r--r--sources/pyside6/tests/tools/pyside6-project/example_project/subproject/pyproject.toml2
-rw-r--r--sources/pyside6/tests/tools/pyside6-project/example_project/subproject/subproject.pyproject2
-rw-r--r--sources/pyside6/tests/tools/pyside6-project/existing_pyproject_toml/expected_pyproject.toml5
-rw-r--r--sources/pyside6/tests/tools/pyside6-project/test_pyside6_project.py2
-rw-r--r--sources/shiboken6/ApiExtractor/abstractmetabuilder.cpp20
-rw-r--r--sources/shiboken6/ApiExtractor/apiextractor.cpp1
-rw-r--r--sources/shiboken6/ApiExtractor/clangparser/clangparser.cpp20
-rw-r--r--sources/shiboken6/ApiExtractor/clangparser/compilersupport.cpp63
-rw-r--r--sources/shiboken6/ApiExtractor/clangparser/compilersupport.h8
-rw-r--r--sources/shiboken6/ApiExtractor/customconversion.cpp3
-rw-r--r--sources/shiboken6/ApiExtractor/parser/typeinfo.cpp38
-rw-r--r--sources/shiboken6/ApiExtractor/parser/typeinfo.h5
-rw-r--r--sources/shiboken6/ApiExtractor/smartpointertypeentry.h5
-rw-r--r--sources/shiboken6/ApiExtractor/typesystem.cpp19
-rw-r--r--sources/shiboken6/ApiExtractor/typesystemparser.cpp8
-rw-r--r--sources/shiboken6/cmake/ShibokenHelpers.cmake25
-rw-r--r--sources/shiboken6/doc/typesystem_conversionrule.rst15
-rw-r--r--sources/shiboken6/doc/typesystem_converters.rst36
-rw-r--r--sources/shiboken6/doc/typesystem_specifying_types.rst6
-rw-r--r--sources/shiboken6/doc/typesystem_variables.rst2
-rw-r--r--sources/shiboken6/generator/shiboken/cppgenerator.cpp44
-rw-r--r--sources/shiboken6/generator/shiboken/cppgenerator.h8
-rw-r--r--sources/shiboken6/generator/shiboken/cppgenerator_smartpointer.cpp11
-rw-r--r--sources/shiboken6/generator/shiboken/headergenerator.cpp6
-rw-r--r--sources/shiboken6/libshiboken/basewrapper.cpp76
-rw-r--r--sources/shiboken6/libshiboken/basewrapper.h5
-rw-r--r--sources/shiboken6/libshiboken/bindingmanager.cpp15
-rw-r--r--sources/shiboken6/libshiboken/sbkconverter.cpp13
-rw-r--r--sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/layout.py10
-rw-r--r--sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/lib/pyi_generator.py6
-rw-r--r--sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/mapping.py1
-rw-r--r--sources/shiboken6/tests/libsample/bucket.cpp14
-rw-r--r--sources/shiboken6/tests/libsample/bucket.h6
-rw-r--r--sources/shiboken6/tests/libsample/samplenamespace.cpp9
-rw-r--r--sources/shiboken6/tests/libsample/samplenamespace.h4
-rw-r--r--sources/shiboken6/tests/samplebinding/sample_test.py7
-rw-r--r--sources/shiboken6/tests/samplebinding/typesystem_sample.xml19
-rw-r--r--testing/runner.py10
-rw-r--r--testing/wheel_tester.py6
80 files changed, 1205 insertions, 430 deletions
diff --git a/build_scripts/config.py b/build_scripts/config.py
index efafc7688..af4dabbf3 100644
--- a/build_scripts/config.py
+++ b/build_scripts/config.py
@@ -82,7 +82,7 @@ class Config(metaclass=Singleton):
self.setup_kwargs['author'] = _author["name"]
self.setup_kwargs['author_email'] = _author["email"]
self.setup_kwargs['url'] = _pyproject_data["urls"]["Homepage"]
- self.setup_kwargs['license'] = _pyproject_data["license"]["text"]
+ self.setup_kwargs['license'] = _pyproject_data["license"]
self.setup_kwargs['python_requires'] = _pyproject_data["requires-python"]
self.classifiers = _pyproject_data["classifiers"]
diff --git a/build_scripts/wheel_files.py b/build_scripts/wheel_files.py
index 4cbf12aa2..e84cf8cd2 100644
--- a/build_scripts/wheel_files.py
+++ b/build_scripts/wheel_files.py
@@ -1037,6 +1037,7 @@ def module_QtVirtualKeyboard() -> ModuleData:
data = ModuleData("VirtualKeyboard")
data.plugins.append("virtualkeyboard")
data.qtlib.append("libQt6VirtualKeyboardSettings")
+ data.qtlib.append("libQt6VirtualKeyboardQml")
return data
diff --git a/coin/instructions/common_environment.yaml b/coin/instructions/common_environment.yaml
index adb67b56d..f54eb1ab3 100644
--- a/coin/instructions/common_environment.yaml
+++ b/coin/instructions/common_environment.yaml
@@ -198,6 +198,18 @@ instructions:
property: target.compiler
equals_value: ICC_18
- type: EnvironmentVariable
+ variableName: PYTHON3_PATH
+ variableValue: "{{ index .Env \"PYTHON3.10.0-64_PATH\"}}"
+ enable_if:
+ condition: and
+ conditions:
+ - condition: property
+ property: host.os
+ equals_value: Windows
+ - condition: property
+ property: host.arch
+ equals_value: X86_64
+ - type: EnvironmentVariable
variableName: ICC64_18_PATH # Seems a bit hard to maintain
variableValue: /opt/intel/compilers_and_libraries_2018.1.163/linux/bin/intel64:/opt/intel/bin
enable_if:
diff --git a/doc/changelogs/changes-6.9.1 b/doc/changelogs/changes-6.9.1
new file mode 100644
index 000000000..6f4ef508b
--- /dev/null
+++ b/doc/changelogs/changes-6.9.1
@@ -0,0 +1,64 @@
+Qt for Python 6.9.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-841] An example showing an audio graph using Qt Graphs has been
+ added.
+ - [PYSIDE-2193] A crash retrieving a Python type QObject property
+ (encapsulated in a QVariant) been fixed.
+ - [PYSIDE-3052] class QMessageLogger has been added for handling debug,
+ info, warning, critical, and fatal messages,
+ including support for QLoggingCategory.
+ - [PYSIDE-3012] type hints: The type signature for the Property class
+ has been fixed.
+ - [PYSIDE-3021] type-hints: The QMessagebox annotations has been fixed.
+ - [PYSIDE-3029] type-hints: The hints for properties on __init__ functions
+ has been fixed.
+ - [PYSIDE-3041] type hints: The str parameter of QLineEdit.setText() has
+ been made optional.
+ - [PYSIDE-3050] type hints: Type signature for
+ QProgressDialog.setCancelButton has been fixed.
+ - [PYSIDE-3055] type hints: Type signature for QTabBar.setTabButton has
+ been fixed.
+ - [PYSIDE-3056] type hints: Type signature for qtTrId has been fixed.
+ - [PYSIDE-3057] type hints: Type signature for QTreeWidget.setItemWidget
+ has been fixed.
+ - [PYSIDE-3058] type hints: Return value for QTreeWidget.topLevelItem and
+ QTreeWidget.takeTopLevelItem has been fixed.
+ - [PYSIDE-3059] type hints: The type signature for QObject class has been
+ fixed.
+ - [PYSIDE-3061] The building of .pyi files in debug mode on Windows has
+ been fixed.
+ - [PYSIDE-3067] A crash when entering a Qt message handler with a Python
+ error set has been fixed.
+ - [PYSIDE-3069] A crash retrieving a QGraphicsProxyObject from a QVariant
+ has been fixed.
+ - [PYSIDE-3078] type hints: The parent widget parameter of the
+ QInputDialog get() methods has been made optional.
+ - [PYSIDE-3087] The dependency of pyside6-project on tomlkit has been
+ removed.
+ - [PYSIDE-3089] An error in pyside6-metaobjectdump when encountering
+ @Slot(result=None) has been fixed.
+
+****************************************************************************
+* Shiboken6 *
+****************************************************************************
+
+ - [PYSIDE-3081] A bug in the clang parser causing errors when parsing a
+ lambda contained in a function parameter default value has
+ been fixed.
diff --git a/examples/graphs/2d/graphsaudio/GraphsAudio/Main.qml b/examples/graphs/2d/graphsaudio/GraphsAudio/Main.qml
new file mode 100644
index 000000000..51bf3ef12
--- /dev/null
+++ b/examples/graphs/2d/graphsaudio/GraphsAudio/Main.qml
@@ -0,0 +1,50 @@
+// Copyright (C) 2025 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+import QtQuick.Controls
+import QtGraphs
+
+ApplicationWindow {
+ visible: true
+ width: 1000
+ height: 800
+ title: "Data from the microphone (" + device_name + ")"
+
+ GraphsView {
+ id: graph
+ anchors.fill: parent
+
+ LineSeries {
+ id: audio_series
+ width: 2
+ color: "#007acc"
+ }
+
+ axisX: ValueAxis {
+ min: 0
+ max: 2000
+ tickInterval : 500
+ labelFormat: "%g"
+ titleText: "Samples"
+ }
+
+ axisY: ValueAxis {
+ min: -1
+ max: 1
+ tickInterval : 0.5
+ labelFormat: "%0.1f"
+ titleText: "Audio level"
+ }
+ }
+
+ Connections {
+ target: audio_bridge
+ function onDataUpdated(buffer) {
+ audio_series.clear()
+ for (let i = 0; i < buffer.length; ++i) {
+ audio_series.append(buffer[i])
+ }
+ }
+ }
+}
diff --git a/examples/graphs/2d/graphsaudio/GraphsAudio/qmldir b/examples/graphs/2d/graphsaudio/GraphsAudio/qmldir
new file mode 100644
index 000000000..cc5408a66
--- /dev/null
+++ b/examples/graphs/2d/graphsaudio/GraphsAudio/qmldir
@@ -0,0 +1,2 @@
+module GraphsAudio
+Main 1.0 Main.qml
diff --git a/examples/graphs/2d/graphsaudio/doc/graphsaudio.rst b/examples/graphs/2d/graphsaudio/doc/graphsaudio.rst
new file mode 100644
index 000000000..f19b28caf
--- /dev/null
+++ b/examples/graphs/2d/graphsaudio/doc/graphsaudio.rst
@@ -0,0 +1,8 @@
+GraphsAudio Example
+===================
+
+This example shows the drawing of dynamic data (microphone input) using QtGraphs and Qml.
+
+.. image:: graphsaudio.webp
+ :width: 400
+ :alt: GraphsAudio Screenshot
diff --git a/examples/graphs/2d/graphsaudio/doc/graphsaudio.webp b/examples/graphs/2d/graphsaudio/doc/graphsaudio.webp
new file mode 100644
index 000000000..bb57b18e5
--- /dev/null
+++ b/examples/graphs/2d/graphsaudio/doc/graphsaudio.webp
Binary files differ
diff --git a/examples/graphs/2d/graphsaudio/graphsaudio.pyproject b/examples/graphs/2d/graphsaudio/graphsaudio.pyproject
new file mode 100644
index 000000000..eff791919
--- /dev/null
+++ b/examples/graphs/2d/graphsaudio/graphsaudio.pyproject
@@ -0,0 +1,3 @@
+{
+ "files": ["main.py", "GraphsAudio/Main.qml", "GraphsAudio/qmldir"]
+}
diff --git a/examples/graphs/2d/graphsaudio/main.py b/examples/graphs/2d/graphsaudio/main.py
new file mode 100644
index 000000000..239aee036
--- /dev/null
+++ b/examples/graphs/2d/graphsaudio/main.py
@@ -0,0 +1,80 @@
+# Copyright (C) 2025 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+from __future__ import annotations
+
+import sys
+from pathlib import Path
+from PySide6.QtCore import QObject, QPointF, Slot, Signal
+from PySide6.QtMultimedia import QAudioFormat, QAudioSource, QMediaDevices
+from PySide6.QtWidgets import QMessageBox
+from PySide6.QtQml import QQmlApplicationEngine
+from PySide6.QtGui import QGuiApplication
+
+
+SAMPLE_COUNT = 2000
+RESOLUTION = 4
+
+
+class Audio(QObject):
+ dataUpdated = Signal(list)
+
+ def __init__(self, device):
+ super().__init__()
+
+ format_audio = QAudioFormat()
+ format_audio.setSampleRate(8000)
+ format_audio.setChannelCount(1)
+ format_audio.setSampleFormat(QAudioFormat.UInt8)
+
+ self.device_name = device.description()
+
+ self._audio_input = QAudioSource(device, format_audio, self)
+ self._io_device = self._audio_input.start()
+ self._io_device.readyRead.connect(self._readyRead)
+
+ self._buffer = [QPointF(x, 0) for x in range(SAMPLE_COUNT)]
+
+ def closeEvent(self, event):
+ if self._audio_input is not None:
+ self._audio_input.stop()
+ event.accept()
+
+ @Slot()
+ def _readyRead(self):
+ data = self._io_device.readAll()
+ available_samples = data.size() // RESOLUTION
+ start = 0
+ if (available_samples < SAMPLE_COUNT):
+ start = SAMPLE_COUNT - available_samples
+ for s in range(start):
+ self._buffer[s].setY(self._buffer[s + available_samples].y())
+
+ data_index = 0
+ for s in range(start, SAMPLE_COUNT):
+ value = (ord(data[data_index]) - 128) / 128
+ self._buffer[s].setY(value)
+ data_index = data_index + RESOLUTION
+
+ self.dataUpdated.emit(self._buffer)
+
+
+if __name__ == '__main__':
+ app = QGuiApplication(sys.argv)
+ engine = QQmlApplicationEngine()
+
+ input_devices = QMediaDevices.audioInputs()
+ if not input_devices:
+ QMessageBox.warning(None, "audio", "There is no audio input device available.")
+ sys.exit(-1)
+
+ audio_bridge = Audio(input_devices[0])
+ engine.rootContext().setContextProperty("audio_bridge", audio_bridge)
+
+ device = input_devices[0]
+ device_name = device.description()
+ engine.rootContext().setContextProperty("device_name", device_name)
+
+ engine.addImportPath(Path(__file__).parent)
+ engine.loadFromModule("GraphsAudio", "Main")
+
+ sys.exit(app.exec())
diff --git a/examples/xml/dombookmarks/dombookmarks.py b/examples/xml/dombookmarks/dombookmarks.py
index f5afdeef8..f02251849 100644
--- a/examples/xml/dombookmarks/dombookmarks.py
+++ b/examples/xml/dombookmarks/dombookmarks.py
@@ -7,7 +7,7 @@ from __future__ import annotations
import sys
-from PySide6.QtCore import QDir, QFile, Qt, QTextStream
+from PySide6.QtCore import QDir, QFile, QObject, Qt, QTextStream
from PySide6.QtGui import QAction, QIcon, QKeySequence
from PySide6.QtWidgets import (QApplication, QFileDialog, QHeaderView,
QMainWindow, QMessageBox, QStyle, QTreeWidget,
@@ -93,6 +93,7 @@ class XbelTree(QTreeWidget):
def __init__(self, parent=None):
super().__init__(parent)
+ self._update_conn_id = None
self.header().setSectionResizeMode(QHeaderView.ResizeMode.Stretch)
self.setHeaderLabels(("Title", "Location"))
@@ -111,11 +112,12 @@ class XbelTree(QTreeWidget):
self._bookmark_icon.addPixmap(style.standardPixmap(QStyle.StandardPixmap.SP_FileIcon))
def read(self, device):
- ok, errorStr, errorLine, errorColumn = self._dom_document.setContent(device, True)
- if not ok:
+ result = self._dom_document.setContent(device,
+ QDomDocument.ParseOption.UseNamespaceProcessing)
+ if not result:
QMessageBox.information(self.window(), "DOM Bookmarks",
- f"Parse error at line {errorLine}, "
- f"column {errorColumn}:\n{errorStr}")
+ f"Parse error at line {result.errorLine}, "
+ f"column {result.errorColumn}:\n{result.errorMessage}")
return False
root = self._dom_document.documentElement()
@@ -131,17 +133,15 @@ class XbelTree(QTreeWidget):
self.clear()
# It might not be connected.
- try:
- self.itemChanged.disconnect(self.update_dom_element)
- except RuntimeError:
- pass
+ if self._update_conn_id:
+ QObject.disconnect(self._update_conn_id)
child = root.firstChildElement('folder')
while not child.isNull():
self.parse_folder_element(child)
child = child.nextSiblingElement('folder')
- self.itemChanged.connect(self.update_dom_element)
+ self._update_conn_id = self.itemChanged.connect(self.update_dom_element)
return True
diff --git a/requirements-coin.txt b/requirements-coin.txt
index 5ee2bac0d..a5dabc834 100644
--- a/requirements-coin.txt
+++ b/requirements-coin.txt
@@ -1,5 +1,5 @@
-pip>=24.2
-setuptools==72.1.0
+pip>=25
+setuptools==78.1.0
importlib_metadata>=6
importlib_resources>=5.10.2
packaging>=24
diff --git a/requirements.txt b/requirements.txt
index 534c29e03..feb5a01aa 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,13 +1,11 @@
# Build dependencies
-setuptools==72.1.0
-packaging==24.1
-build==1.2.1
+setuptools==78.1.0
+packaging==24.2
+build==1.2.2.post1
wheel==0.43.0
distro==1.9.0; sys_platform == 'linux'
patchelf==0.17.2; sys_platform == 'linux'
# 2.0.2 is the last version that supports Python 3.9
numpy<=2.0.2; python_version <= '3.9'
numpy==2.1.3; python_version > '3.9'
-mypy>=1.14.0; platform_machine == 'ARM64' and sys_platform == 'win32'
-mypy[faster-cache]>=1.14.0; platform_machine != 'ARM64' and sys_platform == 'win32'
-mypy[faster-cache]>=1.14.0; sys_platform != 'win32'
+mypy>=1.15.0 # note: 3.13 with disable-gil is not compiled yet
diff --git a/sources/pyside-tools/project_lib/pyproject_toml.py b/sources/pyside-tools/project_lib/pyproject_toml.py
index fafe0d67d..6da7b455e 100644
--- a/sources/pyside-tools/project_lib/pyproject_toml.py
+++ b/sources/pyside-tools/project_lib/pyproject_toml.py
@@ -2,6 +2,7 @@
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
from __future__ import annotations
+import os
import sys
# TODO: Remove this import when Python 3.11 is the minimum supported version
if sys.version_info >= (3, 11):
@@ -48,19 +49,19 @@ def _parse_toml_content(content: str) -> dict:
return result
-def _write_toml_content(data: dict) -> str:
+def _write_base_toml_content(data: dict) -> str:
"""
Write minimal TOML content with project and tool.pyside6-project sections.
"""
lines = []
- if 'project' in data and data['project']:
+ if data.get('project'):
lines.append('[project]')
for key, value in sorted(data['project'].items()):
if isinstance(value, str):
lines.append(f'{key} = "{value}"')
- if 'tool' in data and 'pyside6-project' in data['tool']:
+ if data.get("tool") and data['tool'].get('pyside6-project'):
lines.append('\n[tool.pyside6-project]')
for key, value in sorted(data['tool']['pyside6-project'].items()):
if isinstance(value, list):
@@ -115,7 +116,7 @@ def parse_pyproject_toml(pyproject_toml_file: Path) -> PyProjectParseResult:
def write_pyproject_toml(pyproject_file: Path, project_name: str, project_files: list[str]):
"""
- Create or update a pyproject.toml file with the specified content.
+ Create or overwrite a pyproject.toml file with the specified content.
"""
data = {
"project": {"name": project_name},
@@ -124,13 +125,33 @@ def write_pyproject_toml(pyproject_file: Path, project_name: str, project_files:
}
}
+ content = _write_base_toml_content(data)
try:
- content = _write_toml_content(data)
pyproject_file.write_text(content, encoding='utf-8')
except Exception as e:
raise ValueError(f"Error writing TOML file: {str(e)}")
+def robust_relative_to_posix(target_path: Path, base_path: Path) -> str:
+ """
+ Calculates the relative path from base_path to target_path.
+ Uses Path.relative_to first, falls back to os.path.relpath if it fails.
+ Returns the result as a POSIX path string.
+ """
+ # Ensure both paths are absolute for reliable calculation, although in this specific code,
+ # project_folder and paths in output_files are expected to be resolved/absolute already.
+ abs_target = target_path.resolve() if not target_path.is_absolute() else target_path
+ abs_base = base_path.resolve() if not base_path.is_absolute() else base_path
+
+ try:
+ return abs_target.relative_to(abs_base).as_posix()
+ except ValueError:
+ # Fallback to os.path.relpath which is more robust for paths that are not direct subpaths.
+ relative_str = os.path.relpath(str(abs_target), str(abs_base))
+ # Convert back to Path temporarily to get POSIX format
+ return Path(relative_str).as_posix()
+
+
def migrate_pyproject(pyproject_file: Path | str = None) -> int:
"""
Migrate a project *.pyproject JSON file to the new pyproject.toml format.
@@ -170,7 +191,7 @@ def migrate_pyproject(pyproject_file: Path | str = None) -> int:
project_name = project_files[0].stem
# The project files that will be written to the pyproject.toml file
- output_files = set()
+ output_files: set[Path] = set()
for project_file in project_files:
project_data = parse_pyproject_json(project_file)
if project_data.errors:
@@ -185,39 +206,58 @@ def migrate_pyproject(pyproject_file: Path | str = None) -> int:
project_name = project_folder.name
pyproject_toml_file = project_folder / "pyproject.toml"
- if pyproject_toml_file.exists():
- already_existing_file = True
+
+ relative_files = sorted(
+ robust_relative_to_posix(p, project_folder) for p in output_files
+ )
+
+ if not (already_existing_file := pyproject_toml_file.exists()):
+ # Create new pyproject.toml file
+ data = {
+ "project": {"name": project_name},
+ "tool": {
+ "pyside6-project": {"files": relative_files}
+ }
+ }
+ updated_content = _write_base_toml_content(data)
+ else:
+ # For an already existing file, append our tool.pyside6-project section
+ # If the project section is missing, add it
try:
content = pyproject_toml_file.read_text(encoding='utf-8')
- data = _parse_toml_content(content)
except Exception as e:
- raise ValueError(f"Error parsing TOML: {str(e)}")
- else:
- already_existing_file = False
- data = {"project": {}, "tool": {"pyside6-project": {}}}
+ print(f"Error processing existing TOML file: {str(e)}", file=sys.stderr)
+ return 1
- # Update project name if not present
- if "name" not in data["project"]:
- data["project"]["name"] = project_name
+ append_content = []
- # Update files list
- data["tool"]["pyside6-project"]["files"] = sorted(
- p.relative_to(project_folder).as_posix() for p in output_files
- )
+ if '[project]' not in content:
+ # Add project section if needed
+ append_content.append('\n[project]')
+ append_content.append(f'name = "{project_name}"')
+
+ if '[tool.pyside6-project]' not in content:
+ # Add tool.pyside6-project section
+ append_content.append('\n[tool.pyside6-project]')
+ items = [f'"{item}"' for item in relative_files]
+ append_content.append(f'files = [{", ".join(items)}]')
- # Generate TOML content
- toml_content = _write_toml_content(data)
+ if append_content:
+ updated_content = content.rstrip() + '\n' + '\n'.join(append_content)
+ else:
+ # No changes needed
+ print("pyproject.toml already contains [project] and [tool.pyside6-project] sections")
+ return 0
- if already_existing_file:
print(f"WARNING: A pyproject.toml file already exists at \"{pyproject_toml_file}\"")
print("The file will be updated with the following content:")
- print(toml_content)
+ print(updated_content)
response = input("Proceed? [Y/n] ")
if response.lower().strip() not in {"yes", "y"}:
return 0
try:
- pyproject_toml_file.write_text(toml_content)
+ pyproject_toml_file.write_text(updated_content, encoding='utf-8')
except Exception as e:
print(f"Error writing to \"{pyproject_toml_file}\": {str(e)}", file=sys.stderr)
return 1
diff --git a/sources/pyside-tools/pyside_tool.py b/sources/pyside-tools/pyside_tool.py
index 887f2bdda..5c009859d 100644
--- a/sources/pyside-tools/pyside_tool.py
+++ b/sources/pyside-tools/pyside_tool.py
@@ -198,6 +198,18 @@ def metaobjectdump():
pyside_script_wrapper("metaobjectdump.py")
+def _check_requirements(requirements_file):
+ """Check if all required packages are installed."""
+ missing_packages = []
+ with open(requirements_file, 'r', encoding='UTF-8') as file:
+ for line in file:
+ # versions
+ package = line.strip().split('==')[0]
+ if not importlib.util.find_spec(package):
+ missing_packages.append(line.strip())
+ return missing_packages
+
+
def project():
pyside_script_wrapper("project.py")
@@ -220,12 +232,15 @@ def android_deploy():
file=sys.stderr)
else:
android_requirements_file = Path(__file__).parent / "requirements-android.txt"
- with open(android_requirements_file, 'r', encoding='UTF-8') as file:
- while line := file.readline():
- dependent_package = line.rstrip()
- if not bool(importlib.util.find_spec(dependent_package)):
- command = [sys.executable, "-m", "pip", "install", dependent_package]
- subprocess.run(command)
+ if android_requirements_file.exists():
+ missing_packages = _check_requirements(android_requirements_file)
+ if missing_packages:
+ print("The following packages are required but not installed:")
+ for package in missing_packages:
+ print(f" - {package}")
+ print("Please install them using:")
+ print(f" pip install -r {android_requirements_file}")
+ sys.exit(1)
pyside_script_wrapper("android_deploy.py")
diff --git a/sources/pyside-tools/requirements-android.txt b/sources/pyside-tools/requirements-android.txt
index 9ed5d8427..1a247f6c1 100644
--- a/sources/pyside-tools/requirements-android.txt
+++ b/sources/pyside-tools/requirements-android.txt
@@ -1,3 +1,4 @@
jinja2
pkginfo
tqdm
+packaging==24.1
diff --git a/sources/pyside6/PySide6/QtCore/glue/core_snippets.cpp b/sources/pyside6/PySide6/QtCore/glue/core_snippets.cpp
index c073c8bc1..9b2b40e82 100644
--- a/sources/pyside6/PySide6/QtCore/glue/core_snippets.cpp
+++ b/sources/pyside6/PySide6/QtCore/glue/core_snippets.cpp
@@ -19,98 +19,6 @@
#include <QtCore/QObject>
#include <QtCore/QRegularExpression>
#include <QtCore/QStack>
-#include <QtCore/QVariant>
-
-// Helpers for QVariant conversion
-
-QMetaType QVariant_resolveMetaType(PyTypeObject *type)
-{
- if (!PyObject_TypeCheck(type, SbkObjectType_TypeF()))
- return {};
- const char *typeName = Shiboken::ObjectType::getOriginalName(type);
- if (!typeName)
- return {};
- const bool valueType = '*' != typeName[qstrlen(typeName) - 1];
- // Do not convert user type of value
- if (valueType && Shiboken::ObjectType::isUserType(type))
- return {};
- QMetaType metaType = QMetaType::fromName(typeName);
- if (metaType.isValid())
- return metaType;
- // Do not resolve types to value type
- if (valueType)
- return {};
- // Find in base types. First check tp_bases, and only after check tp_base, because
- // tp_base does not always point to the first base class, but rather to the first
- // that has added any python fields or slots to its object layout.
- // See https://mail.python.org/pipermail/python-list/2009-January/520733.html
- if (type->tp_bases) {
- for (Py_ssize_t i = 0, size = PyTuple_Size(type->tp_bases); i < size; ++i) {
- auto baseType = reinterpret_cast<PyTypeObject *>(PyTuple_GetItem(type->tp_bases, i));
- const QMetaType derived = QVariant_resolveMetaType(baseType);
- if (derived.isValid())
- return derived;
- }
- } else if (type->tp_base) {
- return QVariant_resolveMetaType(type->tp_base);
- }
- return {};
-}
-
-QVariant QVariant_convertToValueList(PyObject *list)
-{
- if (PySequence_Size(list) < 0) {
- // clear the error if < 0 which means no length at all
- PyErr_Clear();
- return {};
- }
-
- Shiboken::AutoDecRef element(PySequence_GetItem(list, 0));
-
- auto *type = reinterpret_cast<PyTypeObject *>(element.object());
- QMetaType metaType = QVariant_resolveMetaType(type);
- if (!metaType.isValid())
- return {};
-
- const QByteArray listTypeName = QByteArrayLiteral("QList<") + metaType.name() + '>';
- metaType = QMetaType::fromName(listTypeName);
- if (!metaType.isValid())
- return {};
-
- Shiboken::Conversions::SpecificConverter converter(listTypeName);
- if (!converter) {
- qWarning("Type converter for: %s not registered.", listTypeName.constData());
- return {};
- }
-
- QVariant var(metaType);
- converter.toCpp(list, &var);
- return var;
-}
-
-bool QVariant_isStringList(PyObject *list)
-{
- if (!PySequence_Check(list)) {
- // If it is not a list or a derived list class
- // we assume that will not be a String list neither.
- return false;
- }
-
- if (PySequence_Size(list) < 0) {
- // clear the error if < 0 which means no length at all
- PyErr_Clear();
- return false;
- }
-
- Shiboken::AutoDecRef fast(PySequence_Fast(list, "Failed to convert QVariantList"));
- const Py_ssize_t size = PySequence_Size(fast.object());
- for (Py_ssize_t i = 0; i < size; ++i) {
- Shiboken::AutoDecRef item(PySequence_GetItem(fast.object(), i));
- if (PyUnicode_Check(item) == 0)
- return false;
- }
- return true;
-}
// Helpers for qAddPostRoutine
diff --git a/sources/pyside6/PySide6/QtCore/glue/core_snippets_p.h b/sources/pyside6/PySide6/QtCore/glue/core_snippets_p.h
index 11e84b291..4c1867a1a 100644
--- a/sources/pyside6/PySide6/QtCore/glue/core_snippets_p.h
+++ b/sources/pyside6/PySide6/QtCore/glue/core_snippets_p.h
@@ -14,10 +14,8 @@
QT_FORWARD_DECLARE_CLASS(QGenericArgument)
QT_FORWARD_DECLARE_CLASS(QGenericReturnArgument)
-QT_FORWARD_DECLARE_CLASS(QMetaType)
QT_FORWARD_DECLARE_CLASS(QObject)
QT_FORWARD_DECLARE_CLASS(QRegularExpression)
-QT_FORWARD_DECLARE_CLASS(QVariant);
QT_BEGIN_NAMESPACE
namespace QtCoreHelper {
@@ -26,14 +24,6 @@ class QGenericReturnArgumentHolder;
}
QT_END_NAMESPACE
-// Helpers for QVariant conversion
-
-QMetaType QVariant_resolveMetaType(PyTypeObject *type);
-
-QVariant QVariant_convertToValueList(PyObject *list);
-
-bool QVariant_isStringList(PyObject *list);
-
// Helpers for qAddPostRoutine
namespace PySide {
void globalPostRoutineCallback();
diff --git a/sources/pyside6/PySide6/QtCore/typesystem_core_common.xml b/sources/pyside6/PySide6/QtCore/typesystem_core_common.xml
index 207844c56..c3130ccb2 100644
--- a/sources/pyside6/PySide6/QtCore/typesystem_core_common.xml
+++ b/sources/pyside6/PySide6/QtCore/typesystem_core_common.xml
@@ -16,6 +16,7 @@
<extra-includes>
<include file-name="pysidemetatype.h" location="global"/>
<include file-name="pysideutils.h" location="global"/> <!-- QString conversion -->
+ <include file-name="pysidevariantutils.h" location="global"/> <!-- QVariant conversion -->
<include file-name="signalmanager.h" location="global"/>
<include file-name="sbkerrors.h" location="global"/>
<!-- QtCoreHelper::QGenericReturnArgumentHolder -->
@@ -320,6 +321,7 @@
<extra-includes>
<include file-name="optional" location="global"/>
<include file-name="pysideqenum.h" location="global"/>
+ <include file-name="pysidevariantutils.h" location="global"/>
</extra-includes>
<conversion-rule>
<native-to-target file="../glue/qtcore.cpp" snippet="return-qvariant"/>
@@ -391,6 +393,9 @@
</object-type>
<primitive-type name="QJsonObject">
+ <extra-includes>
+ <include file-name="pysidevariantutils.h" location="global"/>
+ </extra-includes>
<conversion-rule>
<native-to-target file="../glue/qtcore.cpp" snippet="return-qjsonobject"/>
<target-to-native>
@@ -934,9 +939,12 @@
<inject-code class="target" position="beginning" file="../glue/qtcore.cpp" snippet="qdatetime-2"/>
</add-function>
<!-- PYSIDE-1735: Qt::TimeSpec is no more compatible with int -->
- <add-function signature="QDateTime(int@year@,int@month@,int@day@,int@h@,int@m@,int@s@,int@ms@,Qt::TimeSpec@spec@=Qt::LocalTime)">
+ <add-function signature="QDateTime(int@year@,int@month@,int@day@,int@h@,int@m@,int@s@,int@ms@,Qt::TimeSpec@spec@)">
<inject-code class="target" position="beginning" file="../glue/qtcore.cpp" snippet="qdatetime-3"/>
</add-function>
+ <add-function signature="QDateTime(int@year@,int@month@,int@day@,int@h@,int@m@,int@s@,int@ms@,QTimeZone@spec@=QTimeZone::LocalTime)" since="6.5">
+ <inject-code class="target" position="beginning" file="../glue/qtcore.cpp" snippet="qdatetime-4"/>
+ </add-function>
<add-function signature="__repr__" return-type="str">
<inject-code class="target" position="beginning">
<insert-template name="repr_code">
diff --git a/sources/pyside6/PySide6/QtWidgets/typesystem_widgets_common.xml b/sources/pyside6/PySide6/QtWidgets/typesystem_widgets_common.xml
index 509459ec6..c6e93cfd7 100644
--- a/sources/pyside6/PySide6/QtWidgets/typesystem_widgets_common.xml
+++ b/sources/pyside6/PySide6/QtWidgets/typesystem_widgets_common.xml
@@ -1489,6 +1489,7 @@
<enum-type name="InputMode"/>
<modify-function signature="getInt(QWidget*,const QString&amp;,const QString&amp;,int,int,int,int,bool*,QFlags&lt;Qt::WindowType&gt;)" allow-thread="yes">
+ <modify-argument index="1" pyi-type="Optional[PySide6.QtWidgets.QWidget]"/>
<modify-argument index="return" pyi-type="Tuple[int, bool]"/>
<modify-argument index="8">
<remove-default-expression/>
@@ -1500,6 +1501,7 @@
</modify-function>
<modify-function signature="getItem(QWidget*,const QString&amp;,const QString&amp;,const QStringList&amp;,int,bool,bool*,QFlags&lt;Qt::WindowType&gt;,QFlags&lt;Qt::InputMethodHint&gt;)" allow-thread="yes">
+ <modify-argument index="1" pyi-type="Optional[PySide6.QtWidgets.QWidget]"/>
<modify-argument index="return" pyi-type="Tuple[str, bool]"/>
<modify-argument index="7">
<remove-default-expression/>
@@ -1511,6 +1513,7 @@
</modify-function>
<modify-function signature="getMultiLineText(QWidget*,const QString&amp;,const QString&amp;,const QString&amp;,bool*,QFlags&lt;Qt::WindowType&gt;,QFlags&lt;Qt::InputMethodHint&gt;)" allow-thread="yes">
+ <modify-argument index="1" pyi-type="Optional[PySide6.QtWidgets.QWidget]"/>
<modify-argument index="return" pyi-type="Tuple[str, bool]"/>
<modify-argument index="5">
<remove-default-expression/>
@@ -1522,6 +1525,7 @@
</modify-function>
<modify-function signature="getText(QWidget*,const QString&amp;,const QString&amp;,QLineEdit::EchoMode,const QString&amp;,bool*,QFlags&lt;Qt::WindowType&gt;,QFlags&lt;Qt::InputMethodHint&gt;)" allow-thread="yes">
+ <modify-argument index="1" pyi-type="Optional[PySide6.QtWidgets.QWidget]"/>
<modify-argument index="return" pyi-type="Tuple[str, bool]"/>
<modify-argument index="6">
<remove-default-expression/>
@@ -1533,6 +1537,7 @@
</modify-function>
<modify-function signature="getDouble(QWidget*,const QString&amp;,const QString&amp;,double,double,double,int,bool*,QFlags&lt;Qt::WindowType&gt;,double)" allow-thread="yes">
+ <modify-argument index="1" pyi-type="Optional[PySide6.QtWidgets.QWidget]"/>
<modify-argument index="return" pyi-type="Tuple[float, bool]"/>
<modify-argument index="8">
<remove-default-expression/>
@@ -3358,7 +3363,7 @@
<enum-type name="PixmapPadMode"/>
</object-type>
- <object-type name="QGraphicsObject" default-superclass="QGraphicsItem"/>
+ <object-type name="QGraphicsObject"/>
<object-type name="QGraphicsOpacityEffect"/>
<object-type name="QGraphicsRotation"/>
<object-type name="QGraphicsScale"/>
diff --git a/sources/pyside6/PySide6/QtXml/typesystem_xml.xml b/sources/pyside6/PySide6/QtXml/typesystem_xml.xml
index 089978b6d..93d3c1f56 100644
--- a/sources/pyside6/PySide6/QtXml/typesystem_xml.xml
+++ b/sources/pyside6/PySide6/QtXml/typesystem_xml.xml
@@ -20,7 +20,7 @@
<enum-type name="ParseOption" flags="ParseOptions" since="6.5"/>
<!-- will be replaced in inject code -->
- <value-type name="ParseResult"/>
+ <value-type name="ParseResult" operator-bool="yes"/>
<modify-function signature="setContent(const QByteArray&amp;,bool,QString*,int*,int*)">
<modify-argument index="3">
@@ -130,6 +130,8 @@
</modify-argument>
<inject-code class="target" position="beginning" file="../glue/qtxml.cpp" snippet="qdomdocument-setcontent" />
</modify-function>
+ <declare-function signature="setContent(const QByteArray&amp;@data@, QDomDocument::ParseOptions@options@=QDomDocument::ParseOption::Default)"
+ return-type="QDomDocument::ParseResult" since="6.8" />
</value-type>
<value-type name="QDomDocumentFragment"/>
diff --git a/sources/pyside6/PySide6/glue/qtcore.cpp b/sources/pyside6/PySide6/glue/qtcore.cpp
index 78a25f0a1..cd1462676 100644
--- a/sources/pyside6/PySide6/glue/qtcore.cpp
+++ b/sources/pyside6/PySide6/glue/qtcore.cpp
@@ -225,49 +225,6 @@ return %out;
// @snippet conversion-qmetatype-pytypeobject
// @snippet qvariant-conversion
-static QVariant QVariant_convertToVariantMap(PyObject *map)
-{
- Py_ssize_t pos = 0;
- Shiboken::AutoDecRef keys(PyDict_Keys(map));
- if (!QVariant_isStringList(keys))
- return {};
- PyObject *key{};
- PyObject *value{};
- QMap<QString,QVariant> ret;
- while (PyDict_Next(map, &pos, &key, &value)) {
- QString cppKey = %CONVERTTOCPP[QString](key);
- QVariant cppValue = %CONVERTTOCPP[QVariant](value);
- ret.insert(cppKey, cppValue);
- }
- return QVariant(ret);
-}
-static QVariant QVariant_convertToVariantList(PyObject *list)
-{
- if (QVariant_isStringList(list)) {
- QList<QString > lst = %CONVERTTOCPP[QList<QString>](list);
- return QVariant(QStringList(lst));
- }
- QVariant valueList = QVariant_convertToValueList(list);
- if (valueList.isValid())
- return valueList;
-
- if (PySequence_Size(list) < 0) {
- // clear the error if < 0 which means no length at all
- PyErr_Clear();
- return {};
- }
-
- QList<QVariant> lst;
- Shiboken::AutoDecRef fast(PySequence_Fast(list, "Failed to convert QVariantList"));
- const Py_ssize_t size = PySequence_Size(fast.object());
- for (Py_ssize_t i = 0; i < size; ++i) {
- Shiboken::AutoDecRef pyItem(PySequence_GetItem(fast.object(), i));
- QVariant item = %CONVERTTOCPP[QVariant](pyItem);
- lst.append(item);
- }
- return QVariant(lst);
-}
-
using SpecificConverter = Shiboken::Conversions::SpecificConverter;
static std::optional<SpecificConverter> converterForQtType(const char *typeNameC)
@@ -535,6 +492,12 @@ QTime time(%4, %5, %6, %7);
Shiboken::Warnings::warnDeprecated("QDateTime", "QDateTime(..., Qt::TimeSpec spec)");
// @snippet qdatetime-3
+// @snippet qdatetime-4
+QDate date(%1, %2, %3);
+QTime time(%4, %5, %6, %7);
+%0 = new %TYPE(date, time, QTimeZone(%8));
+// @snippet qdatetime-4
+
// @snippet qdatetime-topython
QDate date = %CPPSELF.date();
QTime time = %CPPSELF.time();
@@ -1538,7 +1501,7 @@ if (Shiboken::Enum::check(%in)) {
metaType = QMetaType::fromName(typeName);
}
if (!metaType.isValid())
- metaType = QVariant_resolveMetaType(Py_TYPE(%in));
+ metaType = PySide::Variant::resolveMetaType(Py_TYPE(%in));
bool ok = false;
if (metaType.isValid()) {
@@ -1560,12 +1523,12 @@ if (!ok)
// @snippet conversion-sbkobject
// @snippet conversion-pydict
-QVariant ret = QVariant_convertToVariantMap(%in);
+QVariant ret = PySide::Variant::convertToVariantMap(%in);
%out = ret.isValid() ? ret : QVariant::fromValue(PySide::PyObjectWrapper(%in));
// @snippet conversion-pydict
// @snippet conversion-pylist
-QVariant ret = QVariant_convertToVariantList(%in);
+QVariant ret = PySide::Variant::convertToVariantList(%in);
%out = ret.isValid() ? ret : QVariant::fromValue(PySide::PyObjectWrapper(%in));
// @snippet conversion-pylist
@@ -1575,7 +1538,7 @@ QVariant ret = QVariant_convertToVariantList(%in);
// @snippet conversion-pyobject
// @snippet conversion-qjsonobject-pydict
-QVariant dict = QVariant_convertToVariantMap(%in);
+QVariant dict = PySide::Variant::convertToVariantMap(%in);
QJsonValue val = QJsonValue::fromVariant(dict);
%out = val.toObject();
// @snippet conversion-qjsonobject-pydict
diff --git a/sources/pyside6/libpyside/CMakeLists.txt b/sources/pyside6/libpyside/CMakeLists.txt
index 539f1f329..15ab47494 100644
--- a/sources/pyside6/libpyside/CMakeLists.txt
+++ b/sources/pyside6/libpyside/CMakeLists.txt
@@ -40,6 +40,7 @@ set(libpyside_HEADERS # installed below
pysideslot_p.h
pysidestaticstrings.h
pysideutils.h
+ pysidevariantutils.h
pysideweakref.h
qobjectconnect.h
signalmanager.h
@@ -59,6 +60,7 @@ set(libpyside_SRC
pysidesignal.cpp
pysideslot.cpp
pysideproperty.cpp
+ pysidevariantutils.cpp
pysideweakref.cpp
pyside.cpp
pyside_numpy.cpp
diff --git a/sources/pyside6/libpyside/pysidemetafunction.cpp b/sources/pyside6/libpyside/pysidemetafunction.cpp
index 48aba3c7b..7a496c4b7 100644
--- a/sources/pyside6/libpyside/pysidemetafunction.cpp
+++ b/sources/pyside6/libpyside/pysidemetafunction.cpp
@@ -4,6 +4,8 @@
#include "pysidemetafunction.h"
#include "pysidemetafunction_p.h"
+#include <signalmanager.h>
+
#include <autodecref.h>
#include <basewrapper.h>
#include <sbkconverter.h>
@@ -12,6 +14,8 @@
#include <QtCore/qmetaobject.h>
+using namespace Qt::StringLiterals;
+
extern "C"
{
@@ -164,6 +168,10 @@ bool call(QObject *self, int methodIndex, PyObject *args, PyObject **retVal)
QString tmp;
converter.toCpp(obj, &tmp);
methValues[i] = tmp;
+ } else if (metaType.id() == PyObjectWrapper::metaTypeId()) {
+ // Manual conversion, see PyObjectWrapper converter registration
+ methValues[i] = QVariant::fromValue(PyObjectWrapper(obj.object()));
+ methArgs[i] = methValues[i].data();
} else {
converter.toCpp(obj, methArgs[i]);
}
diff --git a/sources/pyside6/libpyside/pysideproperty.cpp b/sources/pyside6/libpyside/pysideproperty.cpp
index e8ea2fa03..69d347043 100644
--- a/sources/pyside6/libpyside/pysideproperty.cpp
+++ b/sources/pyside6/libpyside/pysideproperty.cpp
@@ -6,6 +6,7 @@
#include "pysideproperty_p.h"
#include "pysidesignal.h"
#include "pysidesignal_p.h"
+#include "signalmanager.h"
#include <autodecref.h>
#include <pep384ext.h>
@@ -17,6 +18,8 @@
using namespace Shiboken;
+using namespace Qt::StringLiterals;
+
extern "C"
{
@@ -148,16 +151,20 @@ void PySidePropertyPrivate::metaCall(PyObject *source, QMetaObject::Call call, v
switch (call) {
case QMetaObject::ReadProperty: {
AutoDecRef value(getValue(source));
- auto *obValue = value.object();
- if (obValue) {
- Conversions::SpecificConverter converter(typeName);
- if (converter) {
- converter.toCpp(obValue, args[0]);
- } else {
- // PYSIDE-2160: Report an unknown type name to the caller `qtPropertyMetacall`.
- PyErr_SetObject(PyExc_StopIteration, obValue);
- }
+ if (value.isNull())
+ return;
+ if (typeName == "PyObject"_ba) {
+ // Manual conversion, see PyObjectWrapper converter registration
+ auto *pw = reinterpret_cast<PySide::PyObjectWrapper *>(args[0]);
+ pw->reset(value.object());
+ return;
+ }
+ if (Conversions::SpecificConverter converter(typeName); converter) {
+ converter.toCpp(value.object(), args[0]);
+ return;
}
+ // PYSIDE-2160: Report an unknown type name to the caller `qtPropertyMetacall`.
+ PyErr_SetObject(PyExc_StopIteration, value.object());
}
break;
diff --git a/sources/pyside6/libpyside/pysidevariantutils.cpp b/sources/pyside6/libpyside/pysidevariantutils.cpp
new file mode 100644
index 000000000..7dbfb3afc
--- /dev/null
+++ b/sources/pyside6/libpyside/pysidevariantutils.cpp
@@ -0,0 +1,212 @@
+// Copyright (C) 2025 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
+
+#include "pysidevariantutils.h"
+#include "pysideutils.h"
+
+#include <QtCore/qvariantmap.h>
+
+#include <autodecref.h>
+#include <sbkconverter.h>
+#include <basewrapper.h>
+
+using namespace Qt::StringLiterals;
+
+static const char qVariantTypeName[] = "QVariant";
+
+static void warnConverter(const char *name)
+{
+ qWarning("Type converter for: %s not registered.", name);
+}
+
+// Helper converting each item of a non-empty list using the "QVariant" converter
+static std::optional<QVariantList> pyListToVariantListHelper(PyObject *list, Py_ssize_t size)
+{
+ Q_ASSERT(size > 0);
+ QVariantList result;
+ result.reserve(size);
+ Shiboken::Conversions::SpecificConverter converter(qVariantTypeName);
+ if (!converter) {
+ warnConverter(qVariantTypeName);
+ return std::nullopt;
+ }
+ for (Py_ssize_t i = 0; i < size; ++i) {
+ Shiboken::AutoDecRef pyItem(PySequence_GetItem(list, i));
+ QVariant item;
+ converter.toCpp(pyItem.object(), &item);
+ result.append(item);
+ }
+ return result;
+}
+
+// Helper checking for a sequence of Unicode objects
+static bool isStringList(PyObject *list)
+{
+ const Py_ssize_t size = PySequence_Size(list);
+ if (size == 0)
+ return false;
+ for (Py_ssize_t i = 0; i < size; ++i) {
+ Shiboken::AutoDecRef item(PySequence_GetItem(list, i));
+ if (PyUnicode_Check(item) == 0)
+ return false;
+ }
+ return true;
+}
+
+// Helper to convert to a QStringList
+static std::optional<QStringList> listToStringList(PyObject *list)
+{
+ static const char listType[] = "QList<QString>";
+ Shiboken::Conversions::SpecificConverter converter(listType);
+ if (!converter) {
+ warnConverter(listType);
+ return std::nullopt;
+ }
+ QStringList result;
+ converter.toCpp(list, &result);
+ return result;
+}
+
+// Helper to convert a non-empty, homogenous list using the converter of the first item
+static QVariant convertToValueList(PyObject *list)
+{
+ Q_ASSERT(PySequence_Size(list) >= 0);
+
+ Shiboken::AutoDecRef element(PySequence_GetItem(list, 0));
+
+ auto *type = reinterpret_cast<PyTypeObject *>(element.object());
+ QMetaType metaType = PySide::Variant::resolveMetaType(type);
+ if (!metaType.isValid())
+ return {};
+
+ const QByteArray listTypeName = QByteArrayLiteral("QList<") + metaType.name() + '>';
+ metaType = QMetaType::fromName(listTypeName);
+ if (!metaType.isValid())
+ return {};
+
+ Shiboken::Conversions::SpecificConverter converter(listTypeName);
+ if (!converter) {
+ warnConverter(listTypeName.constData());
+ return {};
+ }
+
+ QVariant var(metaType);
+ converter.toCpp(list, &var);
+ return var;
+}
+
+namespace PySide::Variant
+{
+
+QMetaType resolveMetaType(PyTypeObject *type)
+{
+ if (!PyObject_TypeCheck(type, SbkObjectType_TypeF()))
+ return {};
+ const char *typeName = Shiboken::ObjectType::getOriginalName(type);
+ if (!typeName)
+ return {};
+ const bool valueType = '*' != typeName[qstrlen(typeName) - 1];
+ // Do not convert user type of value
+ if (valueType && Shiboken::ObjectType::isUserType(type))
+ return {};
+ QMetaType metaType = QMetaType::fromName(typeName);
+ if (metaType.isValid())
+ return metaType;
+ // Do not resolve types to value type
+ if (valueType)
+ return {};
+ // Find in base types. First check tp_bases, and only after check tp_base, because
+ // tp_base does not always point to the first base class, but rather to the first
+ // that has added any python fields or slots to its object layout.
+ // See https://mail.python.org/pipermail/python-list/2009-January/520733.html
+ if (type->tp_bases) {
+ const auto size = PyTuple_Size(type->tp_bases);
+ Py_ssize_t i = 0;
+ // PYSIDE-1887, PYSIDE-86: Skip QObject base class of QGraphicsObject;
+ // it needs to use always QGraphicsItem as a QVariant type for
+ // QGraphicsItem::itemChange() to work.
+ if (qstrcmp(typeName, "QGraphicsObject*") == 0)
+ ++i;
+ for ( ; i < size; ++i) {
+ auto baseType = reinterpret_cast<PyTypeObject *>(PyTuple_GetItem(type->tp_bases, i));
+ const QMetaType derived = resolveMetaType(baseType);
+ if (derived.isValid())
+ return derived;
+ }
+ return {};
+ }
+ if (type->tp_base != nullptr)
+ return resolveMetaType(type->tp_base);
+ return {};
+}
+
+std::optional<QVariantList> pyListToVariantList(PyObject *list)
+{
+ if (list == nullptr || PySequence_Check(list) == 0)
+ return std::nullopt;
+ const auto size = PySequence_Size(list);
+ if (size < 0) { // Some infinite (I/O read) thing? - bail out
+ PyErr_Clear();
+ return std::nullopt;
+ }
+ if (size == 0)
+ return QVariantList{};
+ return pyListToVariantListHelper(list, size);
+}
+
+QVariant convertToVariantList(PyObject *list)
+{
+ const auto size = PySequence_Size(list);
+ if (size < 0) { // Some infinite (I/O read) thing? - bail out
+ PyErr_Clear();
+ return {};
+ }
+ if (size == 0)
+ return QVariantList{};
+
+ if (isStringList(list)) {
+ auto stringListO = listToStringList(list);
+ if (stringListO.has_value())
+ return {stringListO.value()};
+ }
+
+ if (QVariant valueList = convertToValueList(list); valueList.isValid())
+ return valueList;
+
+ if (auto vlO = pyListToVariantListHelper(list, size); vlO.has_value())
+ return vlO.value();
+
+ return {};
+}
+
+QVariant convertToVariantMap(PyObject *map)
+{
+ if (map == nullptr || PyDict_Check(map) == 0)
+ return {};
+
+ QVariantMap result;
+ if (PyDict_Size(map) == 0)
+ return result;
+
+ Py_ssize_t pos = 0;
+ Shiboken::AutoDecRef keys(PyDict_Keys(map));
+ if (!isStringList(keys))
+ return {};
+
+ Shiboken::Conversions::SpecificConverter converter(qVariantTypeName);
+ if (!converter) {
+ warnConverter(qVariantTypeName);
+ return {};
+ }
+
+ PyObject *key{};
+ PyObject *value{};
+ while (PyDict_Next(map, &pos, &key, &value)) {
+ QVariant cppValue;
+ converter.toCpp(value, &cppValue);
+ result.insert(PySide::pyUnicodeToQString(key), cppValue);
+ }
+ return result;
+}
+
+} // namespace PySide::Variant
diff --git a/sources/pyside6/libpyside/pysidevariantutils.h b/sources/pyside6/libpyside/pysidevariantutils.h
new file mode 100644
index 000000000..b53f7ce82
--- /dev/null
+++ b/sources/pyside6/libpyside/pysidevariantutils.h
@@ -0,0 +1,37 @@
+// Copyright (C) 2025 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
+
+#ifndef PYSIDEVARIANTUTILS_H
+#define PYSIDEVARIANTUTILS_H
+
+#include <sbkpython.h>
+
+#include <pysidemacros.h>
+
+#include <QtCore/qvariant.h>
+#include <QtCore/qvariantlist.h>
+
+#include <optional>
+
+namespace PySide::Variant
+{
+
+/// Return a QMetaType for a PyTypeObject for purposes of
+/// converting to a QVariant.
+PYSIDE_API QMetaType resolveMetaType(PyTypeObject *type);
+
+/// Convert a heterogenous Python list to a QVariantList by converting each
+/// item using the QVariant converter.
+PYSIDE_API std::optional<QVariantList> pyListToVariantList(PyObject *list);
+
+/// Converts a list to a QVariant following the PySide semantics:
+/// - A list of strings is returned as QVariant<QStringList>
+/// - A list of convertible values is returned as QVariant<QList<Value>>
+/// - Remaining types are returned as QVariant(QVariantList)
+PYSIDE_API QVariant convertToVariantList(PyObject *list);
+
+/// Converts a map to a QVariantMap (string keys and QVariant values)
+PYSIDE_API QVariant convertToVariantMap(PyObject *map);
+} // namespace PySide::Variant
+
+#endif // PYSIDEVARIANTUTILS_H
diff --git a/sources/pyside6/libpyside/signalmanager.cpp b/sources/pyside6/libpyside/signalmanager.cpp
index 933edd318..305d2f5c3 100644
--- a/sources/pyside6/libpyside/signalmanager.cpp
+++ b/sources/pyside6/libpyside/signalmanager.cpp
@@ -24,9 +24,12 @@
#include <QtCore/qcoreevent.h>
#include <QtCore/qdebug.h>
#include <QtCore/qhash.h>
+#include <QtCore/qmetatype.h>
#include <QtCore/qscopedpointer.h>
+#include <climits>
#include <memory>
+#include <utility>
using namespace Qt::StringLiterals;
@@ -36,6 +39,8 @@ using namespace Qt::StringLiterals;
static PyObject *metaObjectAttr = nullptr;
+static int pyObjectWrapperMetaTypeId = QMetaType::UnknownType;
+
static void destroyMetaObject(PyObject *obj)
{
void *ptr = PyCapsule_GetPointer(obj, nullptr);
@@ -125,6 +130,17 @@ PyObjectWrapper::PyObjectWrapper(const PyObjectWrapper &other)
Py_XINCREF(m_me);
}
+PyObjectWrapper::PyObjectWrapper(PyObjectWrapper &&other) noexcept
+ : m_me{std::exchange(other.m_me, nullptr)}
+{
+}
+
+PyObjectWrapper &PyObjectWrapper::operator=(PyObjectWrapper &&other) noexcept
+{
+ m_me = std::exchange(other.m_me, nullptr);
+ return *this;
+}
+
PyObjectWrapper::~PyObjectWrapper()
{
// Check that Python is still initialized as sometimes this is called by a static destructor
@@ -156,6 +172,10 @@ PyObjectWrapper::operator PyObject *() const
return m_me;
}
+int PyObjectWrapper::metaTypeId()
+{
+ return pyObjectWrapperMetaTypeId;
+}
int PyObjectWrapper::toInt() const
{
@@ -221,7 +241,29 @@ QDataStream &operator>>(QDataStream &in, PyObjectWrapper &myObj)
return in;
}
-};
+PYSIDE_API QDebug operator<<(QDebug debug, const PyObjectWrapper &myObj)
+{
+ QDebugStateSaver saver(debug);
+ debug.noquote();
+ debug.nospace();
+ // Do not repeat the type name as it is typically called from the QVariant debug
+ // operator, which outputs the type.
+ debug << '<';
+ if (PyObject *ob = myObj) {
+ const auto refs = Py_REFCNT(ob);
+ debug << Py_TYPE(ob)->tp_name << " at " << ob;
+ if (refs == UINT_MAX) // _Py_IMMORTAL_REFCNT
+ debug << ", immortal";
+ else
+ debug << ", refs=" << refs;
+ } else {
+ debug << '0';
+ }
+ debug << '>';
+ return debug;
+}
+
+} // namespace PySide
using namespace PySide;
@@ -238,19 +280,11 @@ struct SignalManagerPrivate
SignalManager::QmlMetaCallErrorHandler
SignalManagerPrivate::m_qmlMetaCallErrorHandler = nullptr;
-static void PyObject_PythonToCpp_PyObject_PTR(PyObject *pyIn, void *cppOut)
-{
- *reinterpret_cast<PyObject **>(cppOut) = pyIn;
-}
-static PythonToCppFunc is_PyObject_PythonToCpp_PyObject_PTR_Convertible(PyObject * /* pyIn */)
-{
- return PyObject_PythonToCpp_PyObject_PTR;
-}
-static PyObject *PyObject_PTR_CppToPython_PyObject(const void *cppIn)
+static PyObject *CopyCppToPythonPyObject(const void *cppIn)
{
- auto *pyOut = reinterpret_cast<PyObject *>(const_cast<void *>(cppIn));
- if (pyOut)
- Py_INCREF(pyOut);
+ const auto *wrapper = reinterpret_cast<const PyObjectWrapper *>(cppIn);
+ PyObject *pyOut = *wrapper;
+ Py_XINCREF(pyOut);
return pyOut;
}
@@ -260,13 +294,16 @@ void SignalManager::init()
using namespace Shiboken;
// Register PyObject type to use in queued signal and slot connections
- qRegisterMetaType<PyObjectWrapper>("PyObject");
+ pyObjectWrapperMetaTypeId = qRegisterMetaType<PyObjectWrapper>("PyObject");
// Register QVariant(enum) conversion to QVariant(int)
QMetaType::registerConverter<PyObjectWrapper, int>(&PyObjectWrapper::toInt);
- SbkConverter *converter = Shiboken::Conversions::createConverter(&PyBaseObject_Type, nullptr);
- Shiboken::Conversions::setCppPointerToPythonFunction(converter, PyObject_PTR_CppToPython_PyObject);
- Shiboken::Conversions::setPythonToCppPointerFunctions(converter, PyObject_PythonToCpp_PyObject_PTR, is_PyObject_PythonToCpp_PyObject_PTR_Convertible);
+ // Register a shiboken converter for PyObjectWrapper->Python (value conversion).
+ // Python->PyObjectWrapper is not registered since the converters do not work for
+ // non-SbkObject types (falling back to plain pointer pass through).
+ // This conversion needs to be done manually via QVariant.
+ SbkConverter *converter = Shiboken::Conversions::createConverter(&PyBaseObject_Type,
+ CopyCppToPythonPyObject);
Shiboken::Conversions::registerConverterName(converter, "PyObject");
Shiboken::Conversions::registerConverterName(converter, "object");
Shiboken::Conversions::registerConverterName(converter, "PyObjectWrapper");
diff --git a/sources/pyside6/libpyside/signalmanager.h b/sources/pyside6/libpyside/signalmanager.h
index d5c007dbe..9fe56efc2 100644
--- a/sources/pyside6/libpyside/signalmanager.h
+++ b/sources/pyside6/libpyside/signalmanager.h
@@ -14,6 +14,7 @@
#include <optional>
QT_FORWARD_DECLARE_CLASS(QDataStream)
+QT_FORWARD_DECLARE_CLASS(QDebug)
namespace PySide
{
@@ -22,13 +23,13 @@ namespace PySide
class PYSIDE_API PyObjectWrapper
{
public:
- PyObjectWrapper(PyObjectWrapper&&) = delete;
- PyObjectWrapper& operator=(PyObjectWrapper &&) = delete;
PyObjectWrapper();
explicit PyObjectWrapper(PyObject* me);
PyObjectWrapper(const PyObjectWrapper &other);
PyObjectWrapper& operator=(const PyObjectWrapper &other);
+ PyObjectWrapper(PyObjectWrapper&&) noexcept;
+ PyObjectWrapper &operator=(PyObjectWrapper &&) noexcept;
void reset(PyObject *o);
@@ -43,12 +44,15 @@ public:
// The proper fix would be to associate PyObjectWrapper to the corresponding C++ Enum.
int toInt() const;
+ static int metaTypeId();
+
private:
PyObject* m_me;
};
PYSIDE_API QDataStream &operator<<(QDataStream& out, const PyObjectWrapper& myObj);
PYSIDE_API QDataStream &operator>>(QDataStream& in, PyObjectWrapper& myObj);
+PYSIDE_API QDebug operator<<(QDebug debug, const PyObjectWrapper &myObj);
class PYSIDE_API SignalManager
{
diff --git a/sources/pyside6/tests/QtCore/deepcopy_test.py b/sources/pyside6/tests/QtCore/deepcopy_test.py
index 8b211a979..64b001e6d 100644
--- a/sources/pyside6/tests/QtCore/deepcopy_test.py
+++ b/sources/pyside6/tests/QtCore/deepcopy_test.py
@@ -12,8 +12,8 @@ sys.path.append(os.fspath(Path(__file__).resolve().parents[1]))
from init_paths import init_test_paths
init_test_paths(False)
-from PySide6.QtCore import QByteArray, QDate, QDateTime, QTime, QLine, QLineF
-from PySide6.QtCore import Qt, QSize, QSizeF, QRect, QRectF, QDir, QPoint, QPointF
+from PySide6.QtCore import QByteArray, QDate, QDateTime, QTime, QLine, QLineF, QTimeZone
+from PySide6.QtCore import QSize, QSizeF, QRect, QRectF, QDir, QPoint, QPointF
try:
from PySide6.QtCore import QUuid
HAVE_Q = True
@@ -45,7 +45,7 @@ class QTimeDeepCopy(DeepCopyHelper, unittest.TestCase):
class QDateTimeDeepCopy(DeepCopyHelper, unittest.TestCase):
def setUp(self):
- self.original = QDateTime(2010, 5, 18, 10, 24, 45, 223, Qt.TimeSpec.LocalTime)
+ self.original = QDateTime(2010, 5, 18, 10, 24, 45, 223, QTimeZone(QTimeZone.LocalTime))
class QSizeDeepCopy(DeepCopyHelper, unittest.TestCase):
diff --git a/sources/pyside6/tests/QtCore/qobject_property_test.py b/sources/pyside6/tests/QtCore/qobject_property_test.py
index 37936205e..80387ec77 100644
--- a/sources/pyside6/tests/QtCore/qobject_property_test.py
+++ b/sources/pyside6/tests/QtCore/qobject_property_test.py
@@ -32,6 +32,26 @@ class MyObjectWithNotifyProperty(QObject):
myProperty = Property(int, readP, fset=writeP, notify=notifyP)
+class OtherClass:
+ """Helper for QObjectWithOtherClassPropertyTest."""
+ pass
+
+
+class MyObjectWithOtherClassProperty(QObject):
+ """Helper for QObjectWithOtherClassPropertyTest."""
+ def __init__(self, parent=None):
+ super().__init__(parent)
+ self._otherclass = None
+
+ def _get_otherclass(self):
+ return self._otherclass
+
+ def _set_otherclass(self, o):
+ self._otherclass = o
+
+ otherclass = Property(OtherClass, fget=_get_otherclass, fset=_set_otherclass)
+
+
class PropertyWithNotify(unittest.TestCase):
def called(self):
self.called_ = True
@@ -50,5 +70,19 @@ class PropertyWithNotify(unittest.TestCase):
self.assertEqual(o.property("myProperty"), 10)
+class QObjectWithOtherClassPropertyTest(unittest.TestCase):
+ """PYSIDE-2193: For properties of custom classes not wrapped by shiboken,
+ QVariant<PyObjectWrapper> is used, which had refcount issues causing crashes.
+ Exercise the QVariant conversion by setting and retrieving via the
+ QVariant-based property()/setProperty() API."""
+ def testNotify(self):
+ obj = MyObjectWithOtherClassProperty()
+ obj.setProperty("otherclass", OtherClass())
+ for i in range(10):
+ pv = obj.property("otherclass")
+ print(pv) # Exercise repr
+ self.assertTrue(type(pv) is OtherClass)
+
+
if __name__ == '__main__':
unittest.main()
diff --git a/sources/pyside6/tests/QtCore/repr_test.py b/sources/pyside6/tests/QtCore/repr_test.py
index 084b69f87..52a426d31 100644
--- a/sources/pyside6/tests/QtCore/repr_test.py
+++ b/sources/pyside6/tests/QtCore/repr_test.py
@@ -13,8 +13,8 @@ init_test_paths(False)
# for 'self.original'
import PySide6 # noqa
-from PySide6.QtCore import QByteArray, QDate, QDateTime, QTime, QLine, QLineF
-from PySide6.QtCore import Qt, QSize, QSizeF, QRect, QRectF, QPoint, QPointF
+from PySide6.QtCore import QByteArray, QDate, QDateTime, QTime, QLine, QLineF, QTimeZone
+from PySide6.QtCore import QSize, QSizeF, QRect, QRectF, QPoint, QPointF
try:
from PySide6.QtCore import QUuid
HAVE_Q = True
@@ -46,7 +46,7 @@ class QTimeReprCopy(ReprCopyHelper, unittest.TestCase):
class QDateTimeReprCopy(ReprCopyHelper, unittest.TestCase):
def setUp(self):
- self.original = QDateTime(2010, 5, 18, 10, 24, 45, 223, Qt.TimeSpec.LocalTime)
+ self.original = QDateTime(2010, 5, 18, 10, 24, 45, 223, QTimeZone(QTimeZone.LocalTime))
class QSizeReprCopy(ReprCopyHelper, unittest.TestCase):
diff --git a/sources/pyside6/tests/QtWidgets/CMakeLists.txt b/sources/pyside6/tests/QtWidgets/CMakeLists.txt
index 01b7d08ea..9bb2fad67 100644
--- a/sources/pyside6/tests/QtWidgets/CMakeLists.txt
+++ b/sources/pyside6/tests/QtWidgets/CMakeLists.txt
@@ -84,6 +84,7 @@ PYSIDE_TEST(qapp_issue_585.py)
PYSIDE_TEST(qapp_test.py)
PYSIDE_TEST(qapplication_test.py)
PYSIDE_TEST(qapplication_exit_segfault_test.py)
+PYSIDE_TEST(pyside3069.py)
PYSIDE_TEST(qdialog_test.py)
PYSIDE_TEST(qdynamic_signal.py)
# TODO: This passes, but requires manual button clicking (at least on mac)
diff --git a/sources/pyside6/tests/QtWidgets/bug_433.py b/sources/pyside6/tests/QtWidgets/bug_433.py
index 5adcacccc..6d5c4333a 100644
--- a/sources/pyside6/tests/QtWidgets/bug_433.py
+++ b/sources/pyside6/tests/QtWidgets/bug_433.py
@@ -26,4 +26,4 @@ a = QApplication(sys.argv)
t = Test()
t.show()
QTimer.singleShot(0, t.close)
-sys.exit(a.exec_())
+sys.exit(a.exec())
diff --git a/sources/pyside6/tests/QtWidgets/bug_919.py b/sources/pyside6/tests/QtWidgets/bug_919.py
index 6f2fc128d..a95d3aa37 100644
--- a/sources/pyside6/tests/QtWidgets/bug_919.py
+++ b/sources/pyside6/tests/QtWidgets/bug_919.py
@@ -26,10 +26,11 @@ class MyWidget(QPushButton):
self.paintReceived.emit()
def paintEvent(self, e):
- p = QPainter(self)
- style = QApplication.style()
- option = QStyleOptionButton()
- style.drawControl(QStyle.ControlElement.CE_PushButton, option, p)
+ with QPainter(self) as p:
+ style = QApplication.style()
+ option = QStyleOptionButton()
+ self.initStyleOption(option)
+ style.drawControl(QStyle.ControlElement.CE_PushButton, option, p)
self._painted = True
QTimer.singleShot(0, self._emitPainted)
diff --git a/sources/pyside6/tests/QtWidgets/pyside3069.py b/sources/pyside6/tests/QtWidgets/pyside3069.py
new file mode 100644
index 000000000..62ad73038
--- /dev/null
+++ b/sources/pyside6/tests/QtWidgets/pyside3069.py
@@ -0,0 +1,51 @@
+# Copyright (C) 2025 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+from __future__ import annotations
+
+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 # noqa: E402
+init_test_paths(False)
+
+from PySide6.QtCore import Qt # noqa: E402
+from PySide6.QtWidgets import QApplication, QComboBox, QGraphicsScene, QGraphicsView # noqa: E402
+
+from helper.usesqapplication import UsesQApplication # noqa: E402
+
+
+class BugTest(UsesQApplication):
+ """PYSIDE-3069: Test that the conversion of an element of a list
+ QGraphicsItem* to QGraphicsProxyWidget* (inheriting QObject/QGraphicsItem)
+ works correctly without crash.
+
+ For this, we need a QGraphicsProxyWidget for which no wrapper exists,
+ created in C++. So, we populate a combo, add it to the scene and show its
+ popup, which creates a top level that is automatically wrapped by
+ another QGraphicsProxyWidget. This, we print from the list of items().
+
+ See also PYSIDE-86, PYSIDE-1887."""
+ def test(self):
+ qApp.setEffectEnabled(Qt.UI_AnimateCombo, False) # noqa: F821
+ cb = QComboBox()
+ cb.addItem("i1")
+ cb.addItem("i2")
+ scene = QGraphicsScene()
+ scene.addWidget(cb)
+ view = QGraphicsView(scene)
+ view.show()
+ cb.showPopup()
+ while not view.windowHandle().isExposed():
+ QApplication.processEvents()
+ items = scene.items()
+ self.assertEqual(len(items), 2) # Combo and its popup, created in C++
+ for i in items:
+ print(i)
+ view.close()
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/sources/pyside6/tests/QtWidgets/qapp_issue_585.py b/sources/pyside6/tests/QtWidgets/qapp_issue_585.py
index ec8a47569..1300ea4aa 100644
--- a/sources/pyside6/tests/QtWidgets/qapp_issue_585.py
+++ b/sources/pyside6/tests/QtWidgets/qapp_issue_585.py
@@ -40,15 +40,15 @@ import sys
from pathlib import Path
sys.path.append(os.fspath(Path(__file__).resolve().parents[1]))
sys.path.append(os.fspath(Path(__file__).resolve().parents[1] / "util"))
-from init_paths import init_test_paths
+from init_paths import init_test_paths # noqa: E402
init_test_paths()
-from PySide6.QtCore import QTimer
-from PySide6.QtWidgets import QApplication
+from PySide6.QtCore import QTimer # noqa: E402
+from PySide6.QtWidgets import QApplication # noqa: E402
app_instance = QApplication([])
# If the following line is commented, application doesn't crash on exit anymore.
app_instance2 = app_instance
-QTimer.singleShot(0, qApp.quit)
-app_instance.exec_()
+QTimer.singleShot(0, qApp.quit) # noqa: F821
+app_instance.exec()
diff --git a/sources/pyside6/tests/QtWidgets/qgraphicsobjectreimpl_test.py b/sources/pyside6/tests/QtWidgets/qgraphicsobjectreimpl_test.py
index 71aba9941..91b405aaf 100644
--- a/sources/pyside6/tests/QtWidgets/qgraphicsobjectreimpl_test.py
+++ b/sources/pyside6/tests/QtWidgets/qgraphicsobjectreimpl_test.py
@@ -50,6 +50,7 @@ class QGraphicsObjectReimpl(UsesQApplication):
# and then the QVariant was not associated with
# a QGraphicsItem but a QObjectItem because the base
# class was a QObject.
+ # See also PYSIDE-1887, PYSIDE-3069
gobjA = GObjA()
gobjA.setParentItem(w)
self.assertIs(type(w), type(gobjA.parentItem()))
diff --git a/sources/pyside6/tests/QtWidgets/qmenu_test.py b/sources/pyside6/tests/QtWidgets/qmenu_test.py
index 7d1d262e4..8bd5d1624 100644
--- a/sources/pyside6/tests/QtWidgets/qmenu_test.py
+++ b/sources/pyside6/tests/QtWidgets/qmenu_test.py
@@ -33,16 +33,16 @@ class QMenuAddAction(UsesQApplication):
def testAddActionWithoutKeySequenceCallable(self):
# bug #280
- action = self.menu.addAction(self.app.tr('aaa'), lambda: 1)
+ action = self.menu.addAction(self.app.tr('aaa'), lambda: 1) # noqa: F841
def testAddActionKeySequenceCallable(self):
# bug #228
- action = self.menu.addAction(self.app.tr('aaa'), lambda: 1,
+ action = self.menu.addAction(self.app.tr('aaa'), lambda: 1, # noqa: F841
QKeySequence(self.app.tr('Ctrl+O')))
def testAddActionKeySequenceSlot(self):
- action = self.menu.addAction('Quit', self.app, SLOT('quit()'),
- QKeySequence('Ctrl+O'))
+ action = self.menu.addAction('Quit', QKeySequence('Ctrl+O'), # noqa: F841
+ self.app, SLOT('quit()'))
class QMenuAddActionWithIcon(UsesQApplication):
@@ -61,16 +61,16 @@ class QMenuAddActionWithIcon(UsesQApplication):
def testAddActionWithoutKeySequenceCallable(self):
# bug #280
- action = self.menu.addAction(self.icon, self.app.tr('aaa'), lambda: 1)
+ action = self.menu.addAction(self.icon, self.app.tr('aaa'), lambda: 1) # noqa: F841
def testAddActionKeySequenceCallable(self):
# bug #228
- action = self.menu.addAction(self.icon, self.app.tr('aaa'), lambda: 1,
- QKeySequence(self.app.tr('Ctrl+O')))
+ action = self.menu.addAction(self.icon, self.app.tr('aaa'), lambda: 1, # noqa: F841
+ QKeySequence(self.app.tr('Ctrl+O'))) # noqa: F841
def testAddActionKeySequenceSlot(self):
- action = self.menu.addAction(self.icon, 'Quit', self.app, SLOT('quit()'),
- QKeySequence('Ctrl+O'))
+ action = self.menu.addAction(self.icon, 'Quit', QKeySequence('Ctrl+O'), # noqa: F841
+ self.app, SLOT('quit()')) # noqa: F841
if __name__ == '__main__':
diff --git a/sources/pyside6/tests/QtXml/qdomdocument_test.py b/sources/pyside6/tests/QtXml/qdomdocument_test.py
index 8fe4f6e17..b321b1bdf 100644
--- a/sources/pyside6/tests/QtXml/qdomdocument_test.py
+++ b/sources/pyside6/tests/QtXml/qdomdocument_test.py
@@ -44,18 +44,20 @@ class QDomDocumentTest(unittest.TestCase):
def testQDomDocumentSetContentWithBadXmlData(self):
'''Sets invalid xml as the QDomDocument contents.'''
- ok, errorStr, errorLine, errorColumn = self.dom.setContent(self.badXmlData, True)
- self.assertFalse(ok)
- self.assertEqual(errorStr, 'Opening and ending tag mismatch.')
- self.assertEqual(errorLine, 4)
+ parseResult = self.dom.setContent(self.badXmlData,
+ QDomDocument.ParseOption.UseNamespaceProcessing)
+ self.assertFalse(parseResult)
+ self.assertEqual(parseResult.errorMessage, 'Opening and ending tag mismatch.')
+ self.assertEqual(parseResult.errorLine, 4)
def testQDomDocumentSetContentWithGoodXmlData(self):
'''Sets valid xml as the QDomDocument contents.'''
- ok, errorStr, errorLine, errorColumn = self.dom.setContent(self.goodXmlData, True)
- self.assertTrue(ok)
- self.assertEqual(errorStr, '')
- self.assertEqual(errorLine, 0)
- self.assertEqual(errorColumn, 0)
+ parseResult = self.dom.setContent(self.goodXmlData,
+ QDomDocument.ParseOption.UseNamespaceProcessing)
+ self.assertTrue(parseResult)
+ self.assertEqual(parseResult.errorMessage, '')
+ self.assertEqual(parseResult.errorLine, 0)
+ self.assertEqual(parseResult.errorColumn, 0)
def testQDomDocumentData(self):
'''Checks the QDomDocument elements for the valid xml contents.'''
@@ -66,7 +68,8 @@ class QDomDocumentTest(unittest.TestCase):
self.assertTrue(element.hasAttribute(attribute))
self.assertEqual(element.attribute(attribute), value)
- ok, errorStr, errorLine, errorColumn = self.dom.setContent(self.goodXmlData, True)
+ parseResult = self.dom.setContent(self.goodXmlData, # noqa F:841
+ QDomDocument.ParseOption.UseNamespaceProcessing)
root = self.dom.documentElement()
self.assertEqual(root.tagName(), 'typesystem')
checkAttribute(root, 'package', 'PySide6.QtXml')
diff --git a/sources/pyside6/tests/tools/pyside6-android-deploy/test_pyside6_android_deploy.py b/sources/pyside6/tests/tools/pyside6-android-deploy/test_pyside6_android_deploy.py
index 120c54af3..14330726a 100644
--- a/sources/pyside6/tests/tools/pyside6-android-deploy/test_pyside6_android_deploy.py
+++ b/sources/pyside6/tests/tools/pyside6-android-deploy/test_pyside6_android_deploy.py
@@ -36,7 +36,7 @@ class DeployTestBase(unittest.TestCase):
android_requirements_file = pyside_tools / "requirements-android.txt"
with open(android_requirements_file, 'r', encoding='UTF-8') as file:
while line := file.readline():
- dependent_package = line.rstrip()
+ dependent_package = line.rstrip().split('==')[0]
if not bool(importlib.util.find_spec(dependent_package)):
command = [sys.executable, "-m", "pip", "install", dependent_package]
subprocess.run(command)
diff --git a/sources/pyside6/tests/tools/pyside6-project/example_project/subproject/pyproject.toml b/sources/pyside6/tests/tools/pyside6-project/example_project/subproject/pyproject.toml
index 1ceb0ac0b..b6f16e698 100644
--- a/sources/pyside6/tests/tools/pyside6-project/example_project/subproject/pyproject.toml
+++ b/sources/pyside6/tests/tools/pyside6-project/example_project/subproject/pyproject.toml
@@ -2,4 +2,4 @@
name = "subproject"
[tool.pyside6-project]
-files = ["subproject_button.py"]
+files = ["subproject_button.py", "../main.py"]
diff --git a/sources/pyside6/tests/tools/pyside6-project/example_project/subproject/subproject.pyproject b/sources/pyside6/tests/tools/pyside6-project/example_project/subproject/subproject.pyproject
index abfa1f461..688f07c33 100644
--- a/sources/pyside6/tests/tools/pyside6-project/example_project/subproject/subproject.pyproject
+++ b/sources/pyside6/tests/tools/pyside6-project/example_project/subproject/subproject.pyproject
@@ -1,3 +1,3 @@
{
- "files": ["subproject_button.py"]
+ "files": ["subproject_button.py", "../main.py"]
}
diff --git a/sources/pyside6/tests/tools/pyside6-project/existing_pyproject_toml/expected_pyproject.toml b/sources/pyside6/tests/tools/pyside6-project/existing_pyproject_toml/expected_pyproject.toml
index be8aa949f..c0adb0c76 100644
--- a/sources/pyside6/tests/tools/pyside6-project/existing_pyproject_toml/expected_pyproject.toml
+++ b/sources/pyside6/tests/tools/pyside6-project/existing_pyproject_toml/expected_pyproject.toml
@@ -15,8 +15,9 @@ target-version = ["py38"]
# Another comment
-[tool.pyside6-project]
-files = ["main.py", "zzz.py"]
[build-system]
requires = ["setuptools >=42"]
build-backend = "setuptools.build_meta"
+
+[tool.pyside6-project]
+files = ["main.py", "zzz.py"]
diff --git a/sources/pyside6/tests/tools/pyside6-project/test_pyside6_project.py b/sources/pyside6/tests/tools/pyside6-project/test_pyside6_project.py
index d66395251..cbacc4e48 100644
--- a/sources/pyside6/tests/tools/pyside6-project/test_pyside6_project.py
+++ b/sources/pyside6/tests/tools/pyside6-project/test_pyside6_project.py
@@ -1,5 +1,7 @@
# 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 __future__ import annotations
+
import contextlib
import difflib
import importlib
diff --git a/sources/shiboken6/ApiExtractor/abstractmetabuilder.cpp b/sources/shiboken6/ApiExtractor/abstractmetabuilder.cpp
index 27570ec4f..fb5399baa 100644
--- a/sources/shiboken6/ApiExtractor/abstractmetabuilder.cpp
+++ b/sources/shiboken6/ApiExtractor/abstractmetabuilder.cpp
@@ -2664,7 +2664,7 @@ std::optional<AbstractMetaType>
qsizetype i = d ? d->m_scopes.size() - 1 : -1;
while (i >= 0) {
typeInfo = TypeInfo::resolveType(_typei, d->m_scopes.at(i--));
- if (typeInfo.qualifiedName().join(u"::"_s) != _typei.qualifiedName().join(u"::"_s))
+ if (typeInfo.qualifiedName() != _typei.qualifiedName())
break;
}
@@ -2757,12 +2757,22 @@ std::optional<AbstractMetaType>
// 4. Special case QFlags (include instantiation in name)
if (qualifiedName == u"QFlags") {
- qualifiedName = typeInfo.toString();
+ qualifiedName = typeInfo.qualifiedInstantationName();
typeInfo.clearInstantiations();
}
TypeEntryCList types = findTypeEntries(qualifiedName, name, flags,
currentClass, d, errorMessageIn);
+ if (types.isEmpty() && !typeInfo.instantiations().isEmpty()) {
+ // Allow for specifying template specializations as primitive types
+ // with converters ('std::optional<int>' or similar).
+ auto pt = TypeDatabase::instance()->findPrimitiveType(typeInfo.qualifiedInstantationName());
+ if (pt) {
+ types.append(pt);
+ typeInfo.clearInstantiations();
+ }
+ }
+
if (!flags.testFlag(AbstractMetaBuilder::TemplateArgument)) {
// Avoid clashes between QByteArray and enum value QMetaType::QByteArray
// unless we are looking for template arguments.
@@ -2796,7 +2806,7 @@ std::optional<AbstractMetaType>
// For non-type template parameters, create a dummy type entry on the fly
// as is done for classes.
if (!targType.has_value()) {
- const QString value = ti.qualifiedName().join(u"::"_s);
+ const QString value = ti.qualifiedNameString();
if (isNumber(value)) {
auto module = typeSystemTypeEntry(type);
TypeDatabase::instance()->addConstantValueTypeEntry(value, module);
@@ -3082,7 +3092,7 @@ AbstractMetaClassPtr
QString prefix = i > 0 ? QStringList(scope.mid(0, i)).join(u"::"_s) + u"::"_s : QString();
QString completeName = prefix + name;
const TypeInfo parsed = TypeParser::parse(completeName, &errorMessage);
- QString qualifiedName = parsed.qualifiedName().join(u"::"_s);
+ QString qualifiedName = parsed.qualifiedNameString();
if (qualifiedName.isEmpty()) {
qWarning().noquote().nospace() << "Unable to parse type \"" << completeName
<< "\" while looking for template \"" << name << "\": " << errorMessage;
@@ -3196,7 +3206,7 @@ static std::optional<AbstractMetaType>
const AbstractMetaClassCPtr &templateClass,
const TypeInfo &info, QString *errorMessage)
{
- QString typeName = info.qualifiedName().join("::"_L1);
+ QString typeName = info.qualifiedNameString();
TypeDatabase *typeDb = TypeDatabase::instance();
TypeEntryPtr t;
// Check for a non-type template integer parameter, that is, for a base
diff --git a/sources/shiboken6/ApiExtractor/apiextractor.cpp b/sources/shiboken6/ApiExtractor/apiextractor.cpp
index 90f4f5dd9..38dedfd14 100644
--- a/sources/shiboken6/ApiExtractor/apiextractor.cpp
+++ b/sources/shiboken6/ApiExtractor/apiextractor.cpp
@@ -370,6 +370,7 @@ bool ApiExtractorPrivate::runHelper(ApiExtractorFlags flags)
bool addCompilerSupportArguments = true;
if (clangOptionsSize > 0) {
+ clang::setTargetTriple(m_clangOptions);
qsizetype i = 0;
if (m_clangOptions.at(i) == u"-") {
++i;
diff --git a/sources/shiboken6/ApiExtractor/clangparser/clangparser.cpp b/sources/shiboken6/ApiExtractor/clangparser/clangparser.cpp
index 54a1a2c8b..747937ede 100644
--- a/sources/shiboken6/ApiExtractor/clangparser/clangparser.cpp
+++ b/sources/shiboken6/ApiExtractor/clangparser/clangparser.cpp
@@ -275,6 +275,24 @@ static CXTranslationUnit createTranslationUnit(CXIndex index,
return tu;
}
+static void setupTarget(CXTranslationUnit translationUnit)
+{
+ const CXTargetInfo targetInfo = clang_getTranslationUnitTargetInfo(translationUnit);
+ const auto tripleCS = clang_TargetInfo_getTriple(targetInfo);
+ clang::setPointerSize(clang_TargetInfo_getPointerWidth(targetInfo));
+ clang::setTargetTriple(QString::fromUtf8(clang_getCString(tripleCS)));
+ clang_disposeString(tripleCS);
+
+ QString message;
+ {
+ QTextStream str(&message);
+ str << "CLANG v" << CINDEX_VERSION_MAJOR << '.' << CINDEX_VERSION_MINOR
+ << " targeting \"" << targetTriple() << "\", " << pointerSize() << "bit.";
+ }
+ qCInfo(lcShiboken, "%s", qPrintable(message));
+ ReportHandler::addGeneralMessage(message + u'\n');
+}
+
/* clangFlags are flags to clang_parseTranslationUnit2() such as
* CXTranslationUnit_KeepGoing (from CINDEX_VERSION_MAJOR/CINDEX_VERSION_MINOR 0.35)
*/
@@ -295,6 +313,8 @@ bool parse(const QByteArrayList &clangArgs, bool addCompilerSupportArguments,
if (!translationUnit)
return false;
+ setupTarget(translationUnit);
+
CXCursor rootCursor = clang_getTranslationUnitCursor(translationUnit);
clang_visitChildren(rootCursor, visitorCallback, reinterpret_cast<CXClientData>(&bv));
diff --git a/sources/shiboken6/ApiExtractor/clangparser/compilersupport.cpp b/sources/shiboken6/ApiExtractor/clangparser/compilersupport.cpp
index ed8057b62..4d93a084f 100644
--- a/sources/shiboken6/ApiExtractor/clangparser/compilersupport.cpp
+++ b/sources/shiboken6/ApiExtractor/clangparser/compilersupport.cpp
@@ -59,6 +59,9 @@ bool setCompiler(const QString &name)
QString _compilerPath; // Pre-defined compiler path (from command line)
+static unsigned _pointerSize = QT_POINTER_SIZE * 8;
+static QString _targetTriple;
+
const QString &compilerPath()
{
return _compilerPath;
@@ -153,14 +156,15 @@ static void filterHomebrewHeaderPaths(HeaderPaths &headerPaths)
if (homebrewPrefix.isEmpty())
return;
- qCInfo(lcShiboken) << "Found HOMEBREW_OPT with value:" << homebrewPrefix
- << "Assuming homebrew build environment.";
+ ReportHandler::addGeneralMessage("Found HOMEBREW_OPT with value:"_L1
+ + QString::fromUtf8(homebrewPrefix)
+ + "\nAssuming homebrew build environment."_L1);
HeaderPaths::iterator it = headerPaths.begin();
while (it != headerPaths.end()) {
if (it->path.startsWith(homebrewPrefix)) {
- qCInfo(lcShiboken) << "Filtering out homebrew include path: "
- << it->path;
+ ReportHandler::addGeneralMessage("Filtering out homebrew include path: "_L1
+ + QString::fromUtf8(it->path));
it = headerPaths.erase(it);
} else {
++it;
@@ -186,12 +190,6 @@ static HeaderPaths gppInternalIncludePaths(const QString &compiler)
const QByteArrayList stdErrLines = stdErr.split('\n');
bool isIncludeDir = false;
- if (ReportHandler::isDebug(ReportHandler::MediumDebug))
- qCInfo(lcShiboken()).noquote().nospace()
- << "gppInternalIncludePaths:\n compiler: " << compiler
- << "\n stdOut: " << stdOut
- << "\n stdErr: " << stdErr;
-
for (const QByteArray &line : stdErrLines) {
if (isIncludeDir) {
if (line.startsWith(QByteArrayLiteral("End of search list"))) {
@@ -212,6 +210,17 @@ static HeaderPaths gppInternalIncludePaths(const QString &compiler)
if (platform() == Platform::macOS)
filterHomebrewHeaderPaths(result);
+ QString message;
+ {
+ QTextStream str(&message);
+ str << "gppInternalIncludePaths:\n compiler: " << compiler << '\n';
+ for (const auto &h : result)
+ str << " " << h.path << '\n';
+ if (ReportHandler::isDebug(ReportHandler::MediumDebug))
+ str << " stdOut: " << stdOut << "\n stdErr: " << stdErr;
+ }
+ ReportHandler::addGeneralMessage(message);
+
return result;
}
@@ -365,11 +374,10 @@ static void appendClangBuiltinIncludes(HeaderPaths *p)
"(neither by checking the environment variables LLVM_INSTALL_DIR, CLANG_INSTALL_DIR "
" nor running llvm-config). This may lead to parse errors.");
} else {
- qCInfo(lcShiboken, "CLANG v%d.%d, builtins includes directory: %s",
- CINDEX_VERSION_MAJOR, CINDEX_VERSION_MINOR,
- qPrintable(clangBuiltinIncludesDir));
p->append(HeaderPath{QFile::encodeName(clangBuiltinIncludesDir),
HeaderType::System});
+ ReportHandler::addGeneralMessage("CLANG builtins includes directory: "_L1
+ + clangBuiltinIncludesDir);
}
}
@@ -451,4 +459,33 @@ LanguageLevel languageLevelFromOption(const char *o)
return LanguageLevel::Default;
}
+unsigned pointerSize()
+{
+ return _pointerSize;
+}
+
+void setPointerSize(unsigned ps)
+{
+ _pointerSize = ps;
+}
+
+QString targetTriple()
+{
+ return _targetTriple;
+
+}
+void setTargetTriple(const QString &t)
+{
+ _targetTriple = t;
+}
+
+void setTargetTriple(const QStringList &clangOptions)
+{
+ static constexpr auto targetOption = "--target="_L1;
+ auto targetOptionPred = [](const QString &o) { return o.startsWith(targetOption); };
+ const auto it = std::find_if(clangOptions.cbegin(), clangOptions.cend(), targetOptionPred);
+ if (it != clangOptions.cend())
+ _targetTriple = it->sliced(targetOption.size());
+}
+
} // namespace clang
diff --git a/sources/shiboken6/ApiExtractor/clangparser/compilersupport.h b/sources/shiboken6/ApiExtractor/clangparser/compilersupport.h
index 1fa980998..0e12ca137 100644
--- a/sources/shiboken6/ApiExtractor/clangparser/compilersupport.h
+++ b/sources/shiboken6/ApiExtractor/clangparser/compilersupport.h
@@ -51,6 +51,14 @@ void setCompilerPath(const QString &name);
Platform platform();
bool setPlatform(const QString &name);
+
+unsigned pointerSize(); // (bit)
+void setPointerSize(unsigned ps); // Set by parser
+
+QString targetTriple();
+void setTargetTriple(const QStringList &clangOptions); // Set from cmd line before parsing
+void setTargetTriple(const QString &t); // Updated by clang parser while parsing
+
} // namespace clang
#endif // COMPILERSUPPORT_H
diff --git a/sources/shiboken6/ApiExtractor/customconversion.cpp b/sources/shiboken6/ApiExtractor/customconversion.cpp
index 4cfd1b974..7b65bcf3a 100644
--- a/sources/shiboken6/ApiExtractor/customconversion.cpp
+++ b/sources/shiboken6/ApiExtractor/customconversion.cpp
@@ -5,6 +5,7 @@
#include "containertypeentry.h"
#include "customtypenentry.h"
#include "primitivetypeentry.h"
+#include "smartpointertypeentry.h"
#include "valuetypeentry.h"
#include <QtCore/qdebug.h>
@@ -139,6 +140,8 @@ CustomConversionPtr CustomConversion::getCustomConversion(const TypeEntryCPtr &t
return std::static_pointer_cast<const ContainerTypeEntry>(type)->customConversion();
if (type->isValue())
return std::static_pointer_cast<const ValueTypeEntry>(type)->customConversion();
+ if (type->isSmartPointer())
+ return std::static_pointer_cast<const SmartPointerTypeEntry>(type)->customConversion();
return {};
}
diff --git a/sources/shiboken6/ApiExtractor/parser/typeinfo.cpp b/sources/shiboken6/ApiExtractor/parser/typeinfo.cpp
index 845a0f916..93627e6d5 100644
--- a/sources/shiboken6/ApiExtractor/parser/typeinfo.cpp
+++ b/sources/shiboken6/ApiExtractor/parser/typeinfo.cpp
@@ -110,6 +110,28 @@ QStringList TypeInfo::qualifiedName() const
return d->m_qualifiedName;
}
+QString TypeInfo::qualifiedNameString() const
+{
+ return d->m_qualifiedName.join("::"_L1);
+}
+
+QString TypeInfo::qualifiedInstantationName() const
+{
+ QString result = qualifiedNameString();
+ if (const auto instantiationCount = d->m_instantiations.size()) {
+ result += u'<';
+ for (qsizetype i = 0; i < instantiationCount; ++i) {
+ if (i)
+ result += ", "_L1;
+ result += d->m_instantiations.at(i).toString();
+ }
+ if (result.endsWith(u'>'))
+ result += u' ';
+ result += u'>';
+ }
+ return result;
+}
+
void TypeInfo::setQualifiedName(const QStringList &qualified_name)
{
if (d->m_qualifiedName != qualified_name)
@@ -300,7 +322,7 @@ TypeInfo TypeInfo::resolveType(const CodeModelItem &__item, TypeInfo const &__ty
// typedef struct xcb_connection_t xcb_connection_t;
if (nextItem.get() ==__item.get()) {
std::cerr << "** WARNING Bailing out recursion of " << __FUNCTION__
- << "() on " << qPrintable(__type.qualifiedName().join(u"::"_s))
+ << "() on " << qPrintable(__type.qualifiedNameString())
<< '\n';
return otherType;
}
@@ -385,19 +407,7 @@ QString TypeInfo::toString() const
if (isVolatile())
tmp += u"volatile "_s;
- tmp += d->m_qualifiedName.join(u"::"_s);
-
- if (const auto instantiationCount = d->m_instantiations.size()) {
- tmp += u'<';
- for (qsizetype i = 0; i < instantiationCount; ++i) {
- if (i)
- tmp += u", "_s;
- tmp += d->m_instantiations.at(i).toString();
- }
- if (tmp.endsWith(u'>'))
- tmp += u' ';
- tmp += u'>';
- }
+ tmp += qualifiedInstantationName();
for (Indirection i : d->m_indirections)
tmp.append(indirectionKeyword(i));
diff --git a/sources/shiboken6/ApiExtractor/parser/typeinfo.h b/sources/shiboken6/ApiExtractor/parser/typeinfo.h
index 9cd746e85..6f75b5737 100644
--- a/sources/shiboken6/ApiExtractor/parser/typeinfo.h
+++ b/sources/shiboken6/ApiExtractor/parser/typeinfo.h
@@ -39,6 +39,11 @@ public:
QStringList qualifiedName() const;
void setQualifiedName(const QStringList &qualified_name);
+ // Returns "std::list"
+ QString qualifiedNameString() const;
+ // Returns qualifiedName() with instantions ("std::list<int>")
+ QString qualifiedInstantationName() const;
+
void addName(const QString &);
bool isVoid() const;
diff --git a/sources/shiboken6/ApiExtractor/smartpointertypeentry.h b/sources/shiboken6/ApiExtractor/smartpointertypeentry.h
index d704210f7..7b67647b9 100644
--- a/sources/shiboken6/ApiExtractor/smartpointertypeentry.h
+++ b/sources/shiboken6/ApiExtractor/smartpointertypeentry.h
@@ -5,6 +5,7 @@
#define SMARTPOINTERTYPEENTRY_H
#include "complextypeentry.h"
+#include "customconversion_typedefs.h"
class SmartPointerTypeEntryPrivate;
@@ -51,6 +52,10 @@ public:
QString getTargetName(const AbstractMetaType &metaType) const;
+ bool hasCustomConversion() const;
+ void setCustomConversion(const CustomConversionPtr &customConversion);
+ CustomConversionPtr customConversion() const;
+
#ifndef QT_NO_DEBUG_STREAM
void formatDebug(QDebug &d) const override;
#endif
diff --git a/sources/shiboken6/ApiExtractor/typesystem.cpp b/sources/shiboken6/ApiExtractor/typesystem.cpp
index 0820150f8..46e88291d 100644
--- a/sources/shiboken6/ApiExtractor/typesystem.cpp
+++ b/sources/shiboken6/ApiExtractor/typesystem.cpp
@@ -2154,6 +2154,7 @@ public:
QString m_resetMethod;
SmartPointerTypeEntry::Instantiations m_instantiations;
TypeEntryCList m_excludedInstantiations;
+ CustomConversionPtr m_customConversion;
TypeSystem::SmartPointerType m_smartPointerType;
};
@@ -2306,6 +2307,24 @@ QString SmartPointerTypeEntry::getTargetName(const AbstractMetaType &metaType) c
return fixSmartPointerName(name);
}
+bool SmartPointerTypeEntry::hasCustomConversion() const
+{
+ S_D(const SmartPointerTypeEntry);
+ return bool(d->m_customConversion);
+}
+
+void SmartPointerTypeEntry::setCustomConversion(const CustomConversionPtr &customConversion)
+{
+ S_D(SmartPointerTypeEntry);
+ d->m_customConversion = customConversion;
+}
+
+CustomConversionPtr SmartPointerTypeEntry::customConversion() const
+{
+ S_D(const SmartPointerTypeEntry);
+ return d->m_customConversion;
+}
+
// ----------------- NamespaceTypeEntry
class NamespaceTypeEntryPrivate : public ComplexTypeEntryPrivate
{
diff --git a/sources/shiboken6/ApiExtractor/typesystemparser.cpp b/sources/shiboken6/ApiExtractor/typesystemparser.cpp
index 8b2ad08de..7ae25e48c 100644
--- a/sources/shiboken6/ApiExtractor/typesystemparser.cpp
+++ b/sources/shiboken6/ApiExtractor/typesystemparser.cpp
@@ -2348,9 +2348,10 @@ bool TypeSystemParser::parseCustomConversion(const ConditionalStreamReader &,
if (topElement != StackElement::ModifyArgument
&& topElement != StackElement::ValueTypeEntry
&& topElement != StackElement::PrimitiveTypeEntry
- && topElement != StackElement::ContainerTypeEntry) {
+ && topElement != StackElement::ContainerTypeEntry
+ && topElement != StackElement::SmartPointerTypeEntry) {
m_error = u"Conversion rules can only be specified for argument modification, "
- "value-type, primitive-type or container-type conversion."_s;
+ "value-type, primitive-type, or container-type or smartpointer-type conversion."_s;
return false;
}
@@ -2415,6 +2416,9 @@ bool TypeSystemParser::parseCustomConversion(const ConditionalStreamReader &,
std::static_pointer_cast<ContainerTypeEntry>(top->entry)->setCustomConversion(customConversion);
else if (top->entry->isValue())
std::static_pointer_cast<ValueTypeEntry>(top->entry)->setCustomConversion(customConversion);
+ else if (top->entry->isSmartPointer())
+ std::static_pointer_cast<SmartPointerTypeEntry>(top->entry)->setCustomConversion(customConversion);
+
customConversionsForReview.append(customConversion);
return true;
}
diff --git a/sources/shiboken6/cmake/ShibokenHelpers.cmake b/sources/shiboken6/cmake/ShibokenHelpers.cmake
index bcaf31e89..c6414b2ca 100644
--- a/sources/shiboken6/cmake/ShibokenHelpers.cmake
+++ b/sources/shiboken6/cmake/ShibokenHelpers.cmake
@@ -869,28 +869,9 @@ endfunction()
# Get path to libclang.dll/libclang.so depending on the platform
macro(find_libclang)
- if(CMAKE_HOST_WIN32)
- set(libclang_directory_suffix "bin")
- set(libclang_suffix ".dll")
- else()
- set(libclang_directory_suffix "lib")
- if(CMAKE_HOST_APPLE)
- set(libclang_suffix ".dylib")
- else()
- set(libclang_suffix ".so")
- endif()
- endif()
-
- set(libclang_lib_dir "")
- if(DEFINED ENV{LLVM_INSTALL_DIR})
- set(libclang_lib_dir "$ENV{LLVM_INSTALL_DIR}/${libclang_directory_suffix}")
- elseif(DEFINED ENV{CLANG_INSTALL_DIR})
- set(libclang_lib_dir "$ENV{CLANG_INSTALL_DIR}/${libclang_directory_suffix}")
- else()
- message(WARNING
- "Couldn't find libclang${libclang_suffix} "
- "You will likely need to add it manually to PATH to ensure the build succeeds.")
- endif()
+ find_package(Clang CONFIG REQUIRED)
+ get_target_property(libclang_location libclang LOCATION)
+ get_filename_component(libclang_lib_dir "${libclang_location}" DIRECTORY)
endmacro()
# Allow setting a shiboken debug level from the the build system or from the environment
diff --git a/sources/shiboken6/doc/typesystem_conversionrule.rst b/sources/shiboken6/doc/typesystem_conversionrule.rst
index cee45bfb3..f6ce18345 100644
--- a/sources/shiboken6/doc/typesystem_conversionrule.rst
+++ b/sources/shiboken6/doc/typesystem_conversionrule.rst
@@ -12,9 +12,13 @@ The **conversion-rule** tag specifies how a **primitive-type**, a **container-ty
or a **value-type** may be converted to and from the native C++ language types to the
target language types (see also :ref:`user-defined-type-conversion`).
-It is a child of the :ref:`container-type`, :ref:`primitive-type` or
-:ref:`value-type` and may contain :ref:`native-to-target` or
-:ref:`native-to-target` child nodes.
+It may be a child of the :ref:`container-type` and :ref:`primitive-type` nodes,
+where conversions have to be provided for both directions using the
+:ref:`native-to-target` and :ref:`target-to-native` child nodes.
+
+It may also appear as a child of :ref:`value-type` or :ref:`smart-pointer-type`
+where additional conversions from other target language types can be provided
+using the :ref:`target-to-native` child node.
.. code-block:: xml
@@ -70,8 +74,9 @@ an input value an does what's needed to convert it to the output value.
</conversion-rule>
Use the replace node to modify the template code.
-Notice that the generator must provide type system variables for the input
-and output values and types, namely **%in**, **%out**, **%INTYPE** and
+Notice that the generator provides type system variables for the input
+and output values and types (see :ref:`converter_variables_and_functions`).
+The most important ones are **%in**, **%out**, **%INTYPE** and
**%OUTTYPE**. In the case of container types, **%INTYPE** refers to the
full container type (e.g. **"list<int>"**) and **%INTYPE_0**, **%INTYPE_1**,
**%INTYPE_#**, should be replaced by the types used in the container template
diff --git a/sources/shiboken6/doc/typesystem_converters.rst b/sources/shiboken6/doc/typesystem_converters.rst
index ab6fba930..f34f5b829 100644
--- a/sources/shiboken6/doc/typesystem_converters.rst
+++ b/sources/shiboken6/doc/typesystem_converters.rst
@@ -16,7 +16,8 @@ C++ and vice-versa.
// C++ class
struct Complex {
- Complex(double real, double imag);
+ explicit Complex(double real, double imag);
+
double real() const;
double imag() const;
};
@@ -82,8 +83,9 @@ Here's how to do it:
<!-- Code injection at module level. -->
<inject-code class="native" position="beginning">
- static bool Check2TupleOfNumbers(PyObject* pyIn) {
- if (!PySequence_Check(pyIn) || !(PySequence_Size(pyIn) == 2))
+ static bool Check2TupleOfNumbers(PyObject *pyIn)
+ {
+ if (PySequence_Check(pyIn) == 0 || PySequence_Size(pyIn) != 2)
return false;
Shiboken::AutoDecRef pyReal(PySequence_GetItem(pyIn, 0));
if (!PyNumber_Check(pyReal))
@@ -134,7 +136,8 @@ Container Conversions
Converters for :ref:`container-type <container-type>` are pretty much the same as for other type,
except that they make use of the type system variables
-:ref:`%INTYPE_# <intype_n>` and :ref:`%OUTTYPE_# <outtype_n>`.
+:ref:`%INTYPE_# <intype_n>` and :ref:`%OUTTYPE_# <outtype_n>` denoting the
+template parameters.
|project| combines the conversion code for containers with the conversion
defined (or automatically generated) for the containers.
@@ -147,13 +150,12 @@ defined (or automatically generated) for the containers.
<native-to-target>
PyObject* %out = PyDict_New();
- %INTYPE::const_iterator it = %in.begin();
- for (; it != %in.end(); ++it) {
- %INTYPE_0 key = it->first;
- %INTYPE_1 value = it->second;
- PyDict_SetItem(%out,
+ for (auto it = %in.cbegin(), end = %in.cend(); it != end; ++it) {
+ const auto &amp;key = it->first;
+ const auto &amp;value = it->second;
+ PyDict_SetItem(%out,
%CONVERTTOPYTHON[%INTYPE_0](key),
- %CONVERTTOPYTHON[%INTYPE_1](value));
+ %CONVERTTOPYTHON[%INTYPE_1](value));
}
return %out;
</native-to-target>
@@ -161,8 +163,8 @@ defined (or automatically generated) for the containers.
<target-to-native>
<add-conversion type="PyDict">
- PyObject* key;
- PyObject* value;
+ PyObject *key{};
+ PyObject *value{};
Py_ssize_t pos = 0;
while (PyDict_Next(%in, &amp;pos, &amp;key, &amp;value)) {
%OUTTYPE_0 cppKey = %CONVERTTOCPP[%OUTTYPE_0](key);
@@ -183,10 +185,10 @@ defined (or automatically generated) for the containers.
For this case, a number of pre-defined conversion templates
are provided (see :ref:`predefined_templates`).
-.. _variables_and_functions:
+.. _converter_variables_and_functions:
-Variables & Functions
-=====================
+Converter Variables & Functions
+===============================
.. _in:
@@ -212,7 +214,7 @@ Variables & Functions
.. _intype_n:
**%INTYPE_#**
- Replaced by the name of the #th type used in a container.
+ Replaced by the name of the #th template parameter type used in a container.
.. _outtype:
@@ -225,7 +227,7 @@ Variables & Functions
.. _outtype_n:
**%OUTTYPE_#**
- Replaced by the name of the #th type used in a container.
+ Replaced by the name of the #th template parameter type used in a container.
.. _checktype:
diff --git a/sources/shiboken6/doc/typesystem_specifying_types.rst b/sources/shiboken6/doc/typesystem_specifying_types.rst
index c03d203b9..798ef5719 100644
--- a/sources/shiboken6/doc/typesystem_specifying_types.rst
+++ b/sources/shiboken6/doc/typesystem_specifying_types.rst
@@ -208,6 +208,9 @@ can be generated for them. Instead, an instance of the viewed class should
be instantiated and passed to functions using the view class
for argument types.
+It is also possible to specify template specializations
+like "std::optional<int>" as primitive types with converters.
+
See :ref:`predefined_templates` for built-in templates for standard type
conversion rules.
@@ -726,7 +729,8 @@ The ``smart pointer`` type node indicates that the given class is a smart pointe
and requires inserting calls to **getter** to access the pointeee.
Currently, the usage is limited to function return values.
**ref-count-method** specifies the name of the method used to do reference counting.
-It is a child of the :ref:`typesystem_details` node or other type nodes.
+It is a child of the :ref:`typesystem_details` node or other type nodes
+and may contain :ref:`conversion-rule` nodes.
The *optional* attribute **instantiations** specifies for which instantiations
of the smart pointer wrappers will be generated (comma-separated list).
diff --git a/sources/shiboken6/doc/typesystem_variables.rst b/sources/shiboken6/doc/typesystem_variables.rst
index 5eb5d5abe..6dfd1f801 100644
--- a/sources/shiboken6/doc/typesystem_variables.rst
+++ b/sources/shiboken6/doc/typesystem_variables.rst
@@ -16,6 +16,8 @@ implementation specifics.
Variables
=========
+In addition to the below listed variables, there are some variables specific to type
+conversion code (see :ref:`converter_variables_and_functions`).
.. _cpp_return_argument:
diff --git a/sources/shiboken6/generator/shiboken/cppgenerator.cpp b/sources/shiboken6/generator/shiboken/cppgenerator.cpp
index 21e167b53..9092768a3 100644
--- a/sources/shiboken6/generator/shiboken/cppgenerator.cpp
+++ b/sources/shiboken6/generator/shiboken/cppgenerator.cpp
@@ -997,7 +997,7 @@ void CppGenerator::writeCacheResetNative(TextStream &s, const GeneratorContext &
{
s << "void " << classContext.wrapperName()
<< "::resetPyMethodCache()\n{\n" << indent
- << "std::fill_n(m_PyMethodCache, sizeof(m_PyMethodCache) / sizeof(m_PyMethodCache[0]), false);\n"
+ << "std::fill(m_PyMethodCache.begin(), m_PyMethodCache.end(), nullptr);\n"
<< outdent << "}\n\n";
}
@@ -2120,6 +2120,24 @@ void CppGenerator::writeCustomConverterRegister(TextStream &s,
}
}
+void CppGenerator::writeTemplateCustomConverterRegister(TextStream &s,
+ const AbstractMetaType &type,
+ QString converter)
+{
+ auto customConversion = CustomConversion::getCustomConversion(type.typeEntry());
+ if (!customConversion || customConversion->targetToNativeConversions().isEmpty())
+ return;
+ if (converter.isEmpty())
+ converter = converterVar;
+ const QString typeName = fixedCppTypeName(type);
+ for (const auto &conv : customConversion->targetToNativeConversions()) {
+ const QString &sourceTypeName = conv.sourceTypeName();
+ QString toCpp = pythonToCppFunctionName(sourceTypeName, typeName);
+ QString isConv = convertibleToCppFunctionName(sourceTypeName, typeName);
+ writeAddPythonToCppConversion(s, converter, toCpp, isConv);
+ }
+}
+
void CppGenerator::writeContainerConverterFunctions(TextStream &s,
const AbstractMetaType &containerType) const
{
@@ -3600,13 +3618,15 @@ void CppGenerator::writePythonToCppConversionFunctions(TextStream &s,
writeIsPythonConvertibleToCppFunction(s, sourceTypeName, targetTypeName, typeCheck);
}
-void CppGenerator::writePythonToCppConversionFunctions(TextStream &s, const AbstractMetaType &containerType) const
+void CppGenerator::writePythonToCppConversionFunctions(TextStream &s,
+ const AbstractMetaType &templateType) const
{
- Q_ASSERT(containerType.typeEntry()->isContainer());
- const auto cte = std::static_pointer_cast<const ContainerTypeEntry>(containerType.typeEntry());
- const auto customConversion = cte->customConversion();
- for (const auto &conv : customConversion->targetToNativeConversions())
- writePythonToCppConversionFunction(s, containerType, conv);
+ const auto customConversion = CustomConversion::getCustomConversion(templateType.typeEntry());
+ if (customConversion) {
+ const auto &conversions = customConversion->targetToNativeConversions();
+ for (const auto &conv : conversions)
+ writePythonToCppConversionFunction(s, templateType, conv);
+ }
}
void CppGenerator::writePythonToCppConversionFunction(TextStream &s,
@@ -4438,8 +4458,7 @@ QString CppGenerator::writeContainerConverterInitialization(TextStream &s,
s << '&' << targetTypeName << "_Type";
}
- const QString typeName = fixedCppTypeName(type);
- s << ", " << cppToPythonFunctionName(typeName, targetTypeName) << ");\n";
+ s << ", " << cppToPythonFunctionName(fixedCppTypeName(type), targetTypeName) << ");\n";
s << registerConverterName(cppSignature, converter);
if (usePySideExtensions() && cppSignature.startsWith("const "_L1)
@@ -4448,12 +4467,7 @@ QString CppGenerator::writeContainerConverterInitialization(TextStream &s,
s << registerConverterName(underlyingType, converter);
}
- for (const auto &conv : typeEntry->customConversion()->targetToNativeConversions()) {
- const QString &sourceTypeName = conv.sourceTypeName();
- QString toCpp = pythonToCppFunctionName(sourceTypeName, typeName);
- QString isConv = convertibleToCppFunctionName(sourceTypeName, typeName);
- writeAddPythonToCppConversion(s, converter, toCpp, isConv);
- }
+ writeTemplateCustomConverterRegister(s, type, converter);
auto typedefItPair = api.typedefTargetToName().equal_range(type.cppSignature());
if (typedefItPair.first != typedefItPair.second) {
diff --git a/sources/shiboken6/generator/shiboken/cppgenerator.h b/sources/shiboken6/generator/shiboken/cppgenerator.h
index 0cf204234..441a9f781 100644
--- a/sources/shiboken6/generator/shiboken/cppgenerator.h
+++ b/sources/shiboken6/generator/shiboken/cppgenerator.h
@@ -114,6 +114,9 @@ private:
static void writeCustomConverterRegister(TextStream &s,
const CustomConversionPtr &customConversion,
const QString &converterVar);
+ static void writeTemplateCustomConverterRegister(TextStream &s,
+ const AbstractMetaType &type,
+ QString converter = {});
void writeContainerConverterFunctions(TextStream &s,
const AbstractMetaType &containerType) const;
@@ -361,9 +364,10 @@ private:
const TargetToNativeConversion &toNative,
const TypeEntryCPtr &targetType) const;
- /// Writes a pair of Python to C++ conversion and check functions for instantiated container types.
+ /// Writes a pair of Python to C++ conversion and check functions for instantiated
+ /// template (smart pointer/container types).
void writePythonToCppConversionFunctions(TextStream &s,
- const AbstractMetaType &containerType) const;
+ const AbstractMetaType &templateType) const;
void writePythonToCppConversionFunction(TextStream &s,
const AbstractMetaType &containerType,
diff --git a/sources/shiboken6/generator/shiboken/cppgenerator_smartpointer.cpp b/sources/shiboken6/generator/shiboken/cppgenerator_smartpointer.cpp
index 48c4ed5d2..1d3280e36 100644
--- a/sources/shiboken6/generator/shiboken/cppgenerator_smartpointer.cpp
+++ b/sources/shiboken6/generator/shiboken/cppgenerator_smartpointer.cpp
@@ -232,13 +232,16 @@ void CppGenerator::generateSmartPointerClass(TextStream &s,
void CppGenerator::writeSmartPointerConverterFunctions(TextStream &s,
const AbstractMetaType &smartPointerType) const
{
+ auto smartPointerTypeEntry =
+ std::static_pointer_cast<const SmartPointerTypeEntry>(smartPointerType.typeEntry());
+
+ if (smartPointerTypeEntry->hasCustomConversion())
+ writePythonToCppConversionFunctions(s, smartPointerType);
+
const auto baseClasses = findSmartPointeeBaseClasses(api(), smartPointerType);
if (baseClasses.isEmpty())
return;
- auto smartPointerTypeEntry =
- std::static_pointer_cast<const SmartPointerTypeEntry>(smartPointerType.typeEntry());
-
// TODO: Missing conversion to smart pointer pointer type:
s << "// Register smartpointer conversion for all derived classes\n";
@@ -290,6 +293,8 @@ void CppGenerator::writeSmartPointerConverterInitialization(TextStream &s,
writeAddPythonToCppConversion(s, targetConverter, toCpp, isConv);
};
+ writeTemplateCustomConverterRegister(s, type);
+
const auto classes = findSmartPointeeBaseClasses(api(), type);
if (classes.isEmpty())
return;
diff --git a/sources/shiboken6/generator/shiboken/headergenerator.cpp b/sources/shiboken6/generator/shiboken/headergenerator.cpp
index 965d80dad..7616fa5c3 100644
--- a/sources/shiboken6/generator/shiboken/headergenerator.cpp
+++ b/sources/shiboken6/generator/shiboken/headergenerator.cpp
@@ -185,6 +185,7 @@ void HeaderGenerator::writeWrapperClass(TextStream &s,
for( const auto &includeGroup : includeGroups)
s << includeGroup;
}
+ s << "#include <sbkpython.h>\n\n#include <array>\n";
s << "namespace Shiboken { class AutoDecRef; class GilState; }\n\n";
@@ -288,9 +289,10 @@ void *qt_metacast(const char *_clname) override;
}
if (needsMethodCache) {
- s << "mutable bool m_PyMethodCache[" << maxOverrides << "] = {false";
+ s << "mutable std::array<PyObject *, " << maxOverrides
+ << "> m_PyMethodCache = {nullptr";
for (int i = 1; i < maxOverrides; ++i)
- s << ", false";
+ s << ", nullptr";
s << "};\n";
}
diff --git a/sources/shiboken6/libshiboken/basewrapper.cpp b/sources/shiboken6/libshiboken/basewrapper.cpp
index 3e26b4605..039cd3085 100644
--- a/sources/shiboken6/libshiboken/basewrapper.cpp
+++ b/sources/shiboken6/libshiboken/basewrapper.cpp
@@ -21,6 +21,7 @@
#include "voidptr.h"
#include <algorithm>
+#include <cctype>
#include <cstddef>
#include <cstring>
#include <iostream>
@@ -786,32 +787,57 @@ static PyObject *overrideMethodName(PyObject *pySelf, const char *methodName,
// The virtual function call
PyObject *Sbk_GetPyOverride(const void *voidThis, PyTypeObject *typeObject,
Shiboken::GilState &gil, const char *funcName,
- bool &resultCache, PyObject **nameCache)
-{
- PyObject *pyOverride{};
- if (!resultCache) {
- gil.acquire();
- auto &bindingManager = Shiboken::BindingManager::instance();
- SbkObject *wrapper = bindingManager.retrieveWrapper(voidThis, typeObject);
- // The refcount can be 0 if the object is dieing and someone called
- // a virtual method from the destructor
- if (wrapper == nullptr)
- return nullptr;
- auto *pySelf = reinterpret_cast<PyObject *>(wrapper);
- if (Py_REFCNT(pySelf) == 0)
- return nullptr;
- PyObject *pyMethodName = overrideMethodName(pySelf, funcName, nameCache);
- pyOverride = Shiboken::BindingManager::getOverride(wrapper, pyMethodName);
- if (pyOverride == nullptr) {
- resultCache = true;
- gil.release();
- } else if (Shiboken::Errors::occurred() != nullptr) {
- // Give up.
- Py_XDECREF(pyOverride);
- pyOverride = nullptr;
- }
+ PyObject *&resultCache, PyObject **nameCache)
+{
+ if (Py_IsInitialized() == 0 || resultCache == Py_None)
+ return nullptr; // Bail out, execute C++ call (wrappers may outlive Python).
+
+ auto &bindingManager = Shiboken::BindingManager::instance();
+ SbkObject *wrapper = bindingManager.retrieveWrapper(voidThis, typeObject);
+ // The refcount can be 0 if the object is dieing and someone called
+ // a virtual method from the destructor
+ if (wrapper == nullptr)
+ return nullptr;
+ auto *pySelf = reinterpret_cast<PyObject *>(wrapper);
+ if (Py_REFCNT(pySelf) == 0)
+ return nullptr;
+
+ gil.acquire();
+
+ if (resultCache != nullptr) // recreate the callable from function/self
+ return PepExt_Type_CallDescrGet(resultCache, pySelf, nullptr);
+
+ PyObject *pyMethodName = overrideMethodName(pySelf, funcName, nameCache);
+ auto *wrapper_dict = SbkObject_GetDict_NoRef(pySelf);
+
+ // Note: This special case was implemented for duck-punching, which happens
+ // in the instance dict. It does not work with properties.
+ // This is not cached to avoid leaking. FIXME PYSIDE 7: Remove (PYSIDE-2916)?
+ if (PyObject *method = PyDict_GetItem(wrapper_dict, pyMethodName)) {
+ Py_INCREF(method);
+ return method;
+ }
+
+ auto *pyOverride = Shiboken::BindingManager::getOverride(wrapper, pyMethodName);
+ if (pyOverride == nullptr) {
+ resultCache = Py_None;
+ Py_INCREF(resultCache);
+ gil.release();
+ return nullptr; // No override, execute C++ call
}
- return pyOverride;
+
+ if (Shiboken::Errors::occurred() != nullptr) {
+ // Give up.
+ Py_XDECREF(pyOverride);
+ resultCache = Py_None;
+ Py_INCREF(resultCache);
+ gil.release();
+ return nullptr; // // Give up.
+ }
+
+ resultCache = pyOverride;
+ // recreate the callable from function/self
+ return PepExt_Type_CallDescrGet(resultCache, pySelf, nullptr);
}
namespace
diff --git a/sources/shiboken6/libshiboken/basewrapper.h b/sources/shiboken6/libshiboken/basewrapper.h
index 9eea89540..426298bcf 100644
--- a/sources/shiboken6/libshiboken/basewrapper.h
+++ b/sources/shiboken6/libshiboken/basewrapper.h
@@ -123,9 +123,8 @@ LIBSHIBOKEN_API PyObject *Sbk_ReturnFromPython_Self(PyObject *self);
} // extern "C"
LIBSHIBOKEN_API PyObject *Sbk_GetPyOverride(const void *voidThis, PyTypeObject *typeObject,
- Shiboken::GilState &gil,
- const char *funcName, bool &resultCache,
- PyObject **nameCache);
+ Shiboken::GilState &gil, const char *funcName,
+ PyObject *&resultCache, PyObject **nameCache);
namespace Shiboken
{
diff --git a/sources/shiboken6/libshiboken/bindingmanager.cpp b/sources/shiboken6/libshiboken/bindingmanager.cpp
index 25cc5c00a..ca509aefd 100644
--- a/sources/shiboken6/libshiboken/bindingmanager.cpp
+++ b/sources/shiboken6/libshiboken/bindingmanager.cpp
@@ -369,13 +369,6 @@ SbkObject *BindingManager::retrieveWrapper(const void *cptr, PyTypeObject *typeO
PyObject *BindingManager::getOverride(SbkObject *wrapper, PyObject *pyMethodName)
{
auto *obWrapper = reinterpret_cast<PyObject *>(wrapper);
- auto *wrapper_dict = SbkObject_GetDict_NoRef(obWrapper);
- if (PyObject *method = PyDict_GetItem(wrapper_dict, pyMethodName)) {
- // Note: This special case was implemented for duck-punching, which happens
- // in the instance dict. It does not work with properties.
- Py_INCREF(method);
- return method;
- }
Shiboken::AutoDecRef method(PyObject_GetAttr(obWrapper, pyMethodName));
if (method.isNull())
@@ -387,9 +380,9 @@ PyObject *BindingManager::getOverride(SbkObject *wrapper, PyObject *pyMethodName
// crude check for them.
// PYSIDE-535: This macro is redefined in a compatible way in pep384
if (PyMethod_Check(method) != 0) {
- if (PyMethod_GET_SELF(method) != obWrapper)
+ if (PyMethod_Self(method) != obWrapper)
return nullptr;
- function = PyMethod_GET_FUNCTION(method);
+ function = PyMethod_Function(method);
} else if (isCompiledMethod(method)) {
Shiboken::AutoDecRef im_self(PyObject_GetAttr(method, PyName::im_self()));
// Not retaining a reference inline with what PyMethod_GET_SELF does.
@@ -413,13 +406,13 @@ PyObject *BindingManager::getOverride(SbkObject *wrapper, PyObject *pyMethodName
if (PyObject *defaultMethod = PyDict_GetItem(parentDict.object(), pyMethodName)) {
defaultFound = true;
if (function != defaultMethod)
- return method.release();
+ return function;
}
}
}
// PYSIDE-2255: If no default method was found, use the method.
if (!defaultFound)
- return method.release();
+ return function;
return nullptr;
}
diff --git a/sources/shiboken6/libshiboken/sbkconverter.cpp b/sources/shiboken6/libshiboken/sbkconverter.cpp
index 94bb1e2b4..1f79402b7 100644
--- a/sources/shiboken6/libshiboken/sbkconverter.cpp
+++ b/sources/shiboken6/libshiboken/sbkconverter.cpp
@@ -451,8 +451,11 @@ void nonePythonToCppNullPtr(PyObject *, void *cppOut)
void *cppPointer(PyTypeObject *desiredType, SbkObject *pyIn)
{
assert(pyIn);
- if (!ObjectType::checkType(desiredType))
+ if (!ObjectType::checkType(desiredType)) {
+ std::cerr << __FUNCTION__ << ": Conversion to non SbkObject type " << desiredType->tp_name
+ << " requested, falling back to pass-through.\n";
return pyIn;
+ }
auto *inType = Py_TYPE(pyIn);
if (ObjectType::hasCast(inType))
return ObjectType::cast(inType, pyIn, desiredType);
@@ -485,8 +488,14 @@ static void _pythonToCppCopy(const SbkConverter *converter, PyObject *pyIn, void
assert(pyIn);
assert(cppOut);
PythonToCppFunc toCpp = IsPythonToCppConvertible(converter, pyIn);
- if (toCpp)
+ if (toCpp) {
toCpp(pyIn, cppOut);
+ } else {
+ std::cerr << __FUNCTION__ << ": Cannot copy-convert " << pyIn;
+ if (pyIn)
+ std::cerr << " (" << Py_TYPE(pyIn)->tp_name << ')';
+ std::cerr << " to C++.\n";
+ }
}
void pythonToCppCopy(PyTypeObject *type, PyObject *pyIn, void *cppOut)
diff --git a/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/layout.py b/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/layout.py
index 3f83b0816..8eea431c3 100644
--- a/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/layout.py
+++ b/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/layout.py
@@ -30,6 +30,7 @@ from textwrap import dedent
from shibokensupport.signature.mapping import ellipsis, missing_optional_return, PlaceholderType
from shibokensupport.signature.parser import using_snake_case
from shibokensupport.signature import make_snake_case_name
+from collections.abc import Sequence, Iterable
DEFAULT_PARAM_KIND = inspect.Parameter.POSITIONAL_ONLY
@@ -134,9 +135,14 @@ _KEYWORD_ONLY = inspect.Parameter.KEYWORD_ONLY # noqa E:201
_VAR_KEYWORD = inspect.Parameter.VAR_KEYWORD # noqa E:201
_empty = inspect.Parameter.empty # noqa E:201
-
+# PYSIDE-3098: Iterable and Sequence can occur together in an overload.
+# This needs sorting in order of generality.
+# This happened in the NumPy support of PySide 6.10. Note that the ordering
+# of methods there is completely different and unrelated to this mypy sorting.
default_weights = {
typing.Any: 1000, # noqa E:241
+ Iterable: 500, # noqa E:241
+ Sequence: 400, # noqa E:241
bool: 101, # noqa E:241
int: 102, # noqa E:241
float: 103, # noqa E:241
@@ -346,6 +352,7 @@ def create_signature_union(props, key):
if last == _VAR_POSITIONAL:
kind = _KEYWORD_ONLY
if default is None:
+ ann = typing.Union[ann]
ann = typing.Optional[ann]
if default is not _empty and layout.ellipsis:
default = ellipsis
@@ -367,6 +374,7 @@ def create_signature_union(props, key):
ret_anno = annotations.get('return', _empty)
if ret_anno is not _empty and props["fullname"] in missing_optional_return:
+ ret_anno = typing.Union[ret_anno]
ret_anno = typing.Optional[ret_anno]
return inspect.Signature(params, return_annotation=ret_anno,
__validate_parameters__=False)
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 bfb68a1b6..1031e0bb0 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
@@ -69,12 +69,12 @@ class Formatter(Writer):
backup = inspect.formatannotation
@classmethod
- def formatannotation(cls, annotation, base_module=None):
+ def formatannotation(cls, annotation, base_module=None, *args, **kwargs):
if getattr(annotation, '__module__', None) == 'typing':
# do not remove the prefix!
return repr(annotation)
# do the normal action.
- return cls.backup(annotation, base_module)
+ return cls.backup(annotation, base_module, *args, **kwargs)
@classmethod
def fix_typing_prefix(cls, signature):
@@ -241,7 +241,7 @@ FROM_IMPORTS = [
(None, ["os"]),
(None, ["enum"]),
(None, ["typing"]),
- (None, ["collections"]),
+ (None, ["collections.abc"]),
("PySide6.QtCore", ["PyClassProperty", "Signal", "SignalInstance"]),
("shiboken6", ["Shiboken"]),
]
diff --git a/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/mapping.py b/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/mapping.py
index 2bb3c9a87..84cb15feb 100644
--- a/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/mapping.py
+++ b/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/mapping.py
@@ -509,6 +509,7 @@ def init_PySide6_QtCore():
"PySide6.QtCore.QUrl.ComponentFormattingOptions":
PySide6.QtCore.QUrl.ComponentFormattingOption, # mismatch option/enum, why???
"PyUnicode": typing.Text,
+ "QByteArray": typing.Union[PySide6.QtCore.QByteArray, bytes, bytearray, memoryview],
"QByteArrayView": PySide6.QtCore.QByteArray,
"Q_NULLPTR": None,
"QCalendar.Unspecified": PySide6.QtCore.QCalendar.Unspecified,
diff --git a/sources/shiboken6/tests/libsample/bucket.cpp b/sources/shiboken6/tests/libsample/bucket.cpp
index cafd382a9..43a32f414 100644
--- a/sources/shiboken6/tests/libsample/bucket.cpp
+++ b/sources/shiboken6/tests/libsample/bucket.cpp
@@ -40,15 +40,21 @@ bool Bucket::empty()
void Bucket::lock()
{
- m_locked = true;
- while (m_locked) {
- SLEEP(300);
+ bool expected = false;
+ if (m_locked.compare_exchange_strong(expected, true)) {
+ while (m_locked) {
+ SLEEP(300);
+ }
+ } else {
+ std::cerr << __FUNCTION__ << " Attempt to lock twice.\n";
}
}
void Bucket::unlock()
{
- m_locked = false;
+ bool expected = true;
+ if (!m_locked.compare_exchange_strong(expected, false))
+ std::cerr << __FUNCTION__ << " Attempt to unlock twice.\n";
}
bool Bucket::virtualBlockerMethod()
diff --git a/sources/shiboken6/tests/libsample/bucket.h b/sources/shiboken6/tests/libsample/bucket.h
index 73e8edd78..677069fe7 100644
--- a/sources/shiboken6/tests/libsample/bucket.h
+++ b/sources/shiboken6/tests/libsample/bucket.h
@@ -7,6 +7,7 @@
#include "libsamplemacros.h"
#include "objecttype.h"
+#include <atomic>
#include <list>
class ObjectType;
@@ -19,7 +20,7 @@ public:
int pop();
bool empty();
void lock();
- inline bool locked() { return m_locked; }
+ bool locked() { return m_locked.load(); }
void unlock();
virtual bool virtualBlockerMethod();
@@ -27,8 +28,7 @@ public:
private:
std::list<int> m_data;
-
- volatile bool m_locked = false;
+ std::atomic<bool> m_locked{false};
};
#endif // BUCKET_H
diff --git a/sources/shiboken6/tests/libsample/samplenamespace.cpp b/sources/shiboken6/tests/libsample/samplenamespace.cpp
index 18a18d28d..3836f43aa 100644
--- a/sources/shiboken6/tests/libsample/samplenamespace.cpp
+++ b/sources/shiboken6/tests/libsample/samplenamespace.cpp
@@ -97,6 +97,15 @@ int passReferenceToObjectType(const ObjectType &obj, int multiplier)
return obj.objectName().size() * multiplier;
}
+// Exercise specifying complete template specializations as primitive types.
+std::optional<long> optionalMultiply(const std::optional<long> &v1,
+ const std::optional<long> &v2)
+{
+ if (!v1.has_value() || !v2.has_value())
+ return std::nullopt;
+ return v1.value() * v2.value();
+}
+
int variableInNamespace = 42;
} // namespace SampleNamespace
diff --git a/sources/shiboken6/tests/libsample/samplenamespace.h b/sources/shiboken6/tests/libsample/samplenamespace.h
index 99a0787ee..63dc2f316 100644
--- a/sources/shiboken6/tests/libsample/samplenamespace.h
+++ b/sources/shiboken6/tests/libsample/samplenamespace.h
@@ -9,6 +9,7 @@
#include "point.h"
#include "objecttype.h"
+#include <optional>
#include <list>
// Anonymous global enum
@@ -157,6 +158,9 @@ LIBSAMPLE_API double passReferenceToValueType(const Point &point, double multipl
// Add a new signature on type system with only a ObjectType pointer as parameter.
LIBSAMPLE_API int passReferenceToObjectType(const ObjectType &obj, int multiplier);
+LIBSAMPLE_API std::optional<long> optionalMultiply(const std::optional<long> &v1,
+ const std::optional<long> &v2);
+
extern LIBSAMPLE_API int variableInNamespace;
} // namespace SampleNamespace
diff --git a/sources/shiboken6/tests/samplebinding/sample_test.py b/sources/shiboken6/tests/samplebinding/sample_test.py
index c003ad398..43b84d36e 100644
--- a/sources/shiboken6/tests/samplebinding/sample_test.py
+++ b/sources/shiboken6/tests/samplebinding/sample_test.py
@@ -79,6 +79,13 @@ class ModuleTest(unittest.TestCase):
mo2 = sample.MoveOnlyHandler.passMoveOnly(mo)
self.assertEqual(mo2.value(), v)
+ def testOptionalLong(self):
+ v1 = 2
+ v2 = 3
+ self.assertEqual(sample.SampleNamespace.optionalMultiply(v1, v2), 6)
+ self.assertIsNone(sample.SampleNamespace.optionalMultiply(v1, None))
+ self.assertIsNone(sample.SampleNamespace.optionalMultiply(None, v2))
+
if __name__ == '__main__':
unittest.main()
diff --git a/sources/shiboken6/tests/samplebinding/typesystem_sample.xml b/sources/shiboken6/tests/samplebinding/typesystem_sample.xml
index 3f1b2e96f..391a0804d 100644
--- a/sources/shiboken6/tests/samplebinding/typesystem_sample.xml
+++ b/sources/shiboken6/tests/samplebinding/typesystem_sample.xml
@@ -120,6 +120,25 @@
</conversion-rule>
</primitive-type>
+ <primitive-type name="std::optional&lt;long&gt;" target-langapi-name="PyObject">
+ <conversion-rule>
+ <native-to-target>
+ if (!%in.has_value())
+ Py_RETURN_NONE;
+ return PyLong_FromLong(%in.value());
+ </native-to-target>
+ <target-to-native>
+ <add-conversion type="Py_None">
+ SBK_UNUSED(%in)
+ %out = %OUTTYPE();
+ </add-conversion>
+ <add-conversion type="PyLong" check="PyLong_CheckExact(%in)">
+ %out = %OUTTYPE(PyLong_AsLong(%in));
+ </add-conversion>
+ </target-to-native>
+ </conversion-rule>
+ </primitive-type>
+
<value-type name="MoveOnly"/>
<object-type name="MoveOnlyHandler"/>
diff --git a/testing/runner.py b/testing/runner.py
index ad1e01d65..4d96fdfc2 100644
--- a/testing/runner.py
+++ b/testing/runner.py
@@ -20,7 +20,7 @@ this_dir = os.path.dirname(this_file)
build_scripts_dir = os.path.abspath(os.path.join(this_dir, ".."))
sys.path.append(build_scripts_dir)
-from build_scripts.utils import detect_clang
+from build_scripts.utils import detect_clang # noqa: E402
class TestRunner:
@@ -78,11 +78,12 @@ class TestRunner:
Helper for _find_ctest() that finds the ctest binary in a build
system file (ninja, Makefile).
"""
- look_for = "--force-new-ctest-process"
+ # Looking for a command ending this way:
+ look_for = "\\ctest.exe" if "win32" in sys.platform else "/ctest"
line = None
with open(file_name) as makefile:
for line in makefile:
- if look_for in line:
+ if look_for in line and line.lstrip().startswith("COMMAND"):
break
else:
# We have probably forgotten to build the tests.
@@ -98,7 +99,8 @@ class TestRunner:
raise RuntimeError(msg)
# the ctest program is on the left to look_for
assert line, f"Did not find {look_for}"
- ctest = re.search(r'(\S+|"([^"]+)")\s+' + look_for, line).groups()
+ look = re.escape(look_for)
+ ctest = re.search(fr'(\S+{look}|"([^"]+{look})")', line).groups()
return ctest[1] or ctest[0]
def _find_ctest(self):
diff --git a/testing/wheel_tester.py b/testing/wheel_tester.py
index 0f6fb04ff..e5b47ffc1 100644
--- a/testing/wheel_tester.py
+++ b/testing/wheel_tester.py
@@ -84,10 +84,10 @@ def package_prefix_names():
# Note: shiboken6_generator is not needed for compile_using_nuitka,
# but building modules with cmake needs it.
if NEW_WHEELS:
- return ["shiboken6", "shiboken6_generator", "PySide6_Essentials", "PySide6_Addons",
- "PySide6"]
+ return ["shiboken6", "shiboken6_generator", "pyside6_essentials", "pyside6_addons",
+ "pyside6"]
else:
- return ["shiboken6", "shiboken6_generator", "PySide6"]
+ return ["shiboken6", "shiboken6_generator", "pyside6"]
def clean_egg_info():